diff --git a/src/main/kotlin/de/grimsi/gameyfin/config/ConfigEndpoint.kt b/src/main/kotlin/de/grimsi/gameyfin/config/ConfigEndpoint.kt index 787fb47..dff83c0 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/config/ConfigEndpoint.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/config/ConfigEndpoint.kt @@ -43,11 +43,11 @@ class ConfigEndpoint( @PermitAll fun isSsoEnabled(): Boolean? { - return config.get(ConfigProperties.SsoEnabled) + return config.get(ConfigProperties.SSO.OIDC.Enabled) } @PermitAll fun getLogoutUrl(): String? { - return config.get(ConfigProperties.SsoLogoutUrl) + return config.get(ConfigProperties.SSO.OIDC.LogoutUrl) } } diff --git a/src/main/kotlin/de/grimsi/gameyfin/config/ConfigProperties.kt b/src/main/kotlin/de/grimsi/gameyfin/config/ConfigProperties.kt index 9c8e036..161832f 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/config/ConfigProperties.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/config/ConfigProperties.kt @@ -13,119 +13,133 @@ sealed class ConfigProperties( ) { /** Libraries */ - data object LibraryAllowPublicAccess : ConfigProperties( - Boolean::class, - "library.allow-public-access", - "Allow access to game libraries without login", - false - ) + sealed class Libraries { + data object AllowPublicAccess : ConfigProperties( + Boolean::class, + "library.allow-public-access", + "Allow access to game libraries without login", + false + ) - data object LibraryEnableFilesystemWatcher : ConfigProperties( - Boolean::class, - "library.scan.enable-filesystem-watcher", - "Enable automatic library scanning using file system watchers", - true - ) + sealed class Scan { + data object EnableFilesystemWatcher : ConfigProperties( + Boolean::class, + "library.scan.enable-filesystem-watcher", + "Enable automatic library scanning using file system watchers", + true + ) + } - data object LibraryMetadataUpdateEnabled : ConfigProperties( - Boolean::class, - "library.metadata.update.enabled", - "Enable periodic refresh of video game metadata", - true - ) + sealed class Metadata { + data object UpdateEnabled : ConfigProperties( + Boolean::class, + "library.metadata.update.enabled", + "Enable periodic refresh of video game metadata", + true + ) - data object LibraryMetadataUpdateSchedule : ConfigProperties( - String::class, - "library.metadata.update.schedule", - "Schedule for periodic metadata refresh in cron format", - "0 0 * * 0" - ) + data object UpdateSchedule : ConfigProperties( + String::class, + "library.metadata.update.schedule", + "Schedule for periodic metadata refresh in cron format", + "0 0 * * 0" + ) + } + } /** User management */ - data object UsersAllowNewSignUps : ConfigProperties( - Boolean::class, - "users.sign-ups.allow", - "Allow new users to sign up by themselves", - false - ) + sealed class Users { + sealed class SignUps { + data object Allow : ConfigProperties( + Boolean::class, + "users.sign-ups.allow", + "Allow new users to sign up by themselves", + false + ) - data object UsersConfirmNewSignUps : ConfigProperties( - Boolean::class, - "users.sign-ups.confirm", - "Admins need to confirm new users", - false - ) + data object Confirm : ConfigProperties( + Boolean::class, + "users.sign-ups.confirm", + "Admins need to confirm new users", + false + ) + } + } /** Single Sign-On */ - data object SsoEnabled : ConfigProperties( - Boolean::class, - "sso.oidc.enabled", - "Enable SSO via OIDC/OAuth2", - false - ) + sealed class SSO { + sealed class OIDC { + data object Enabled : ConfigProperties( + Boolean::class, + "sso.oidc.enabled", + "Enable SSO via OIDC/OAuth2", + false + ) - data object SsoClientId : ConfigProperties( - String::class, - "sso.oidc.client-id", - "Client ID" - ) + data object MatchExistingUsersBy : ConfigProperties( + MatchUsersBy::class, + "sso.oidc.match-existing-users-by", + "Match existing users by", + MatchUsersBy.username, + MatchUsersBy.entries + ) - data object SsoClientSecret : ConfigProperties( - String::class, - "sso.oidc.client-secret", - "Client secret" - ) + data object AutoRegisterNewUsers : ConfigProperties( + Boolean::class, + "sso.oidc.auto-register-new-users", + "Automatically create new users after registration", + true + ) - data object SsoIssuerUrl : ConfigProperties( - String::class, - "sso.oidc.issuer-url", - "Issuer URL" - ) + data object ClientId : ConfigProperties( + String::class, + "sso.oidc.client-id", + "Client ID" + ) - data object SsoAuthorizeUrl : ConfigProperties( - String::class, - "sso.oidc.authorize-url", - "Authorize URL" - ) + data object ClientSecret : ConfigProperties( + String::class, + "sso.oidc.client-secret", + "Client secret" + ) - data object SsoTokenUrl : ConfigProperties( - String::class, - "sso.oidc.token-url", - "Token URL" - ) + data object IssuerUrl : ConfigProperties( + String::class, + "sso.oidc.issuer-url", + "Issuer URL" + ) - data object SsoUserInfoUrl : ConfigProperties( - String::class, - "sso.oidc.userinfo-url", - "Userinfo URL" - ) + data object AuthorizeUrl : ConfigProperties( + String::class, + "sso.oidc.authorize-url", + "Authorize URL" + ) - data object SsoJwksUrl : ConfigProperties( - String::class, - "sso.oidc.jwks-url", - "JWKS URL" - ) + data object TokenUrl : ConfigProperties( + String::class, + "sso.oidc.token-url", + "Token URL" + ) - data object SsoLogoutUrl : ConfigProperties( - String::class, - "sso.oidc.logout-url", - "Logout URL" - ) + data object UserInfoUrl : ConfigProperties( + String::class, + "sso.oidc.userinfo-url", + "Userinfo URL" + ) - data object SsoMatchExistingUsersBy : ConfigProperties( - MatchUsersBy::class, - "sso.oidc.match-existing-users-by", - "Match existing users by", - MatchUsersBy.username, - MatchUsersBy.entries - ) + data object JwksUrl : ConfigProperties( + String::class, + "sso.oidc.jwks-url", + "JWKS URL" + ) - data object SsoAutoRegisterNewUsers : ConfigProperties( - Boolean::class, - "sso.oidc.auto-register-new-users", - "Automatically create new users after registration", - true - ) + data object LogoutUrl : ConfigProperties( + String::class, + "sso.oidc.logout-url", + "Logout URL" + ) + } + } /** Notifications */ sealed class Notifications { @@ -166,58 +180,30 @@ sealed class ConfigProperties( } } - data object NotificationsTemplateNewUser : ConfigProperties( - String::class, - "notifications.templates.new-user", - "Template for new user notifications" - ) - - data object NotificationsTemplateNewInvite : ConfigProperties( - String::class, - "notifications.templates.new-invite", - "Template for new user notifications" - ) - - data object NotificationsTemplateNewPasswordRequest : ConfigProperties( - String::class, - "notifications.templates.new-password-request", - "Template for new password request notifications" - ) - - data object NotificationsTemplateNewGame : ConfigProperties( - String::class, - "notifications.templates.new-game", - "Template for new game notifications" - ) - - data object NotificationsTemplateNewGameRequest : ConfigProperties( - String::class, - "notifications.templates.new-game-request", - "Template for new game request notifications" - ) - /** Logs */ - data object LogsFolder : ConfigProperties( - String::class, - "logs.folder", - "Storage folder for log files", - "./logs" - ) + sealed class Logs { + data object Folder : ConfigProperties( + String::class, + "logs.folder", + "Storage folder for log files", + "./logs" + ) - data object LogsMaxHistoryDays : ConfigProperties( - Int::class, - "logs.max-history-days", - "Log retention in days", - 30 - ) + data object MaxHistoryDays : ConfigProperties( + Int::class, + "logs.max-history-days", + "Log retention in days", + 30 + ) - data object LogsLevel : ConfigProperties( - LogLevel::class, - "logs.level", - "Log level", - LogLevel.INFO, - LogLevel.entries - ) + data object Level : ConfigProperties( + LogLevel::class, + "logs.level", + "Log level", + LogLevel.INFO, + LogLevel.entries + ) + } } enum class MatchUsersBy { diff --git a/src/main/kotlin/de/grimsi/gameyfin/core/annotations/DynamicAccessInterceptor.kt b/src/main/kotlin/de/grimsi/gameyfin/core/annotations/DynamicAccessInterceptor.kt index 53fa020..56800a0 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/core/annotations/DynamicAccessInterceptor.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/core/annotations/DynamicAccessInterceptor.kt @@ -24,7 +24,7 @@ class DynamicAccessInterceptor( // Check if method is annotated with @DynamicPublicAccess if (method.isAnnotationPresent(DynamicPublicAccess::class.java)) { // Check if user is authenticated or public access is enabled - if (request.userPrincipal != null || configService.get(ConfigProperties.LibraryAllowPublicAccess) == true) { + if (request.userPrincipal != null || configService.get(ConfigProperties.Libraries.AllowPublicAccess) == true) { return true } diff --git a/src/main/kotlin/de/grimsi/gameyfin/core/security/SecurityConfig.kt b/src/main/kotlin/de/grimsi/gameyfin/core/security/SecurityConfig.kt index e8a9934..bcbae84 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/core/security/SecurityConfig.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/core/security/SecurityConfig.kt @@ -49,7 +49,7 @@ class SecurityConfig( super.configure(http) - if (config.get(ConfigProperties.SsoEnabled) == true) { + if (config.get(ConfigProperties.SSO.OIDC.Enabled) == true) { setOAuth2LoginPage(http, "/oauth2/authorization/$ssoProviderKey") // Use custom success handler to handle user registration http.oauth2Login { oauth2Login -> oauth2Login.successHandler(ssoAuthenticationSuccessHandler) } @@ -74,16 +74,16 @@ class SecurityConfig( @Conditional(SsoEnabledCondition::class) fun clientRegistrationRepository(): ClientRegistrationRepository? { val clientRegistration = ClientRegistration.withRegistrationId(ssoProviderKey) - .clientId(config.get(ConfigProperties.SsoClientId)) - .clientSecret(config.get(ConfigProperties.SsoClientSecret)) + .clientId(config.get(ConfigProperties.SSO.OIDC.ClientId)) + .clientSecret(config.get(ConfigProperties.SSO.OIDC.ClientSecret)) .scope("openid", "profile", "email") .userNameAttributeName("preferred_username") .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .issuerUri(config.get(ConfigProperties.SsoIssuerUrl)) - .authorizationUri(config.get(ConfigProperties.SsoAuthorizeUrl)) - .tokenUri(config.get(ConfigProperties.SsoTokenUrl)) - .userInfoUri(config.get(ConfigProperties.SsoUserInfoUrl)) - .jwkSetUri(config.get(ConfigProperties.SsoJwksUrl)) + .issuerUri(config.get(ConfigProperties.SSO.OIDC.IssuerUrl)) + .authorizationUri(config.get(ConfigProperties.SSO.OIDC.AuthorizeUrl)) + .tokenUri(config.get(ConfigProperties.SSO.OIDC.TokenUrl)) + .userInfoUri(config.get(ConfigProperties.SSO.OIDC.UserInfoUrl)) + .jwkSetUri(config.get(ConfigProperties.SSO.OIDC.JwksUrl)) .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}") .build() diff --git a/src/main/kotlin/de/grimsi/gameyfin/core/security/SsoAuthenticationSuccessHandler.kt b/src/main/kotlin/de/grimsi/gameyfin/core/security/SsoAuthenticationSuccessHandler.kt index 2573966..c82bcf1 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/core/security/SsoAuthenticationSuccessHandler.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/core/security/SsoAuthenticationSuccessHandler.kt @@ -33,7 +33,7 @@ class SsoAuthenticationSuccessHandler( // If user is not registered via SSO, check if user is already registered by username or email // This is meant to map existing users to SSO users if (matchedUser == null) { - matchedUser = when (config.get(ConfigProperties.SsoMatchExistingUsersBy)) { + matchedUser = when (config.get(ConfigProperties.SSO.OIDC.MatchExistingUsersBy)) { MatchUsersBy.username -> userService.getByUsername(oidcUser.preferredUsername) MatchUsersBy.email -> userService.getByEmail(oidcUser.email) else -> throw IllegalStateException("Unknown 'match users by' configuration") @@ -43,7 +43,7 @@ class SsoAuthenticationSuccessHandler( // User could not be found in the database if (matchedUser == null) { // Check if new user registration is enabled - if (config.get(ConfigProperties.SsoAutoRegisterNewUsers) == false) { + if (config.get(ConfigProperties.SSO.OIDC.AutoRegisterNewUsers) == false) { response.sendRedirect("/") return } diff --git a/src/main/kotlin/de/grimsi/gameyfin/core/security/SsoEnabledCondition.kt b/src/main/kotlin/de/grimsi/gameyfin/core/security/SsoEnabledCondition.kt index fb622f9..23c820b 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/core/security/SsoEnabledCondition.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/core/security/SsoEnabledCondition.kt @@ -21,9 +21,9 @@ class SsoEnabledCondition : Condition { val password = environment.getProperty("spring.datasource.password"); val connection = DriverManager.getConnection(url, user, password); - connection.use { connection -> - val statement = connection.prepareStatement("SELECT \"value\" FROM app_config WHERE \"key\" = ?") - statement.setString(1, ConfigProperties.SsoEnabled.key) + connection.use { c -> + val statement = c.prepareStatement("SELECT \"value\" FROM app_config WHERE \"key\" = ?") + statement.setString(1, ConfigProperties.SSO.OIDC.Enabled.key) val resultSet = statement.executeQuery() if (resultSet.next()) { return resultSet.getBoolean("value") diff --git a/src/main/kotlin/de/grimsi/gameyfin/logs/LogService.kt b/src/main/kotlin/de/grimsi/gameyfin/logs/LogService.kt index efd24ea..a3afccd 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/logs/LogService.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/logs/LogService.kt @@ -41,9 +41,9 @@ class LogService( @EventListener(ApplicationStartedEvent::class) fun configureFileLogging() { return configureFileLogging( - config.get(ConfigProperties.LogsFolder)!!, - config.get(ConfigProperties.LogsMaxHistoryDays)!!, - config.get(ConfigProperties.LogsLevel)!! + config.get(ConfigProperties.Logs.Folder)!!, + config.get(ConfigProperties.Logs.MaxHistoryDays)!!, + config.get(ConfigProperties.Logs.Level)!! ) } diff --git a/src/main/resources/vaadin-featureflags.properties b/src/main/resources/vaadin-featureflags.properties deleted file mode 100644 index e69de29..0000000