Make OAuth request scopes & roles claim configurable (#689)

* Request OAuth scope "roles" by default
* Make OAuth request scopes configurable
* Make roles claim configurable
This commit is contained in:
Simon
2025-09-01 13:50:28 +02:00
committed by GitHub
parent 345bd6cd1e
commit a2abac0698
4 changed files with 29 additions and 4 deletions
@@ -49,7 +49,7 @@ function SsoManagementLayout({getConfig, formik, setSaveMessage}: any) {
<ConfigFormField configElement={getConfig("sso.oidc.enabled")}/> <ConfigFormField configElement={getConfig("sso.oidc.enabled")}/>
<Section title="SSO user handling"/> <Section title="SSO user handling"/>
<div className="flex flex-row items-baseline"> <div className="flex flex-row items-baseline mb-4">
<CheckboxGroup className="flex flex-col flex-1 items-baseline gap-2" <CheckboxGroup className="flex flex-col flex-1 items-baseline gap-2"
value={["auto-register-new-users"]}> value={["auto-register-new-users"]}>
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
@@ -70,6 +70,13 @@ function SsoManagementLayout({getConfig, formik, setSaveMessage}: any) {
!formik.values.sso.oidc["auto-register-new-users"]}/> !formik.values.sso.oidc["auto-register-new-users"]}/>
</div> </div>
<div className="flex flex-row items-center gap-4">
<ConfigFormField configElement={getConfig("sso.oidc.roles-claim")}
isDisabled={!formik.values.sso.oidc.enabled}/>
<ConfigFormField configElement={getConfig("sso.oidc.oauth-scopes")}
isDisabled={!formik.values.sso.oidc.enabled}/>
</div>
<Section title="SSO provider configuration"/> <Section title="SSO provider configuration"/>
<ConfigFormField configElement={getConfig("sso.oidc.client-id")} <ConfigFormField configElement={getConfig("sso.oidc.client-id")}
isDisabled={!formik.values.sso.oidc.enabled}/> isDisabled={!formik.values.sso.oidc.enabled}/>
@@ -147,6 +147,20 @@ sealed class ConfigProperties<T : Serializable>(
true true
) )
data object RolesClaim : ConfigProperties<String>(
String::class,
"sso.oidc.roles-claim",
"JWT claim to extract roles from",
"roles"
)
data object OAuthScopes : ConfigProperties<Array<String>>(
Array<String>::class,
"sso.oidc.oauth-scopes",
"OAuth2 scopes to request",
arrayOf("openid", "profile", "email", "roles")
)
data object ClientId : ConfigProperties<String>( data object ClientId : ConfigProperties<String>(
String::class, String::class,
"sso.oidc.client-id", "sso.oidc.client-id",
@@ -98,7 +98,7 @@ class SecurityConfig(
val clientRegistration = ClientRegistration.withRegistrationId(SSO_PROVIDER_KEY) val clientRegistration = ClientRegistration.withRegistrationId(SSO_PROVIDER_KEY)
.clientId(config.get(ConfigProperties.SSO.OIDC.ClientId)) .clientId(config.get(ConfigProperties.SSO.OIDC.ClientId))
.clientSecret(config.get(ConfigProperties.SSO.OIDC.ClientSecret)) .clientSecret(config.get(ConfigProperties.SSO.OIDC.ClientSecret))
.scope("openid", "profile", "email") .scope(config.get(ConfigProperties.SSO.OIDC.OAuthScopes)?.toList())
.userNameAttributeName("preferred_username") .userNameAttributeName("preferred_username")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.issuerUri(config.get(ConfigProperties.SSO.OIDC.IssuerUrl)) .issuerUri(config.get(ConfigProperties.SSO.OIDC.IssuerUrl))
@@ -1,5 +1,7 @@
package org.gameyfin.app.users package org.gameyfin.app.users
import org.gameyfin.app.config.ConfigProperties
import org.gameyfin.app.config.ConfigService
import org.gameyfin.app.core.Role import org.gameyfin.app.core.Role
import org.gameyfin.app.users.entities.User import org.gameyfin.app.users.entities.User
import org.gameyfin.app.users.persistence.UserRepository import org.gameyfin.app.users.persistence.UserRepository
@@ -11,7 +13,8 @@ import org.springframework.stereotype.Service
@Service @Service
class RoleService( class RoleService(
private val userRepository: UserRepository private val userRepository: UserRepository,
private val configService: ConfigService
) { ) {
companion object { companion object {
@@ -66,7 +69,8 @@ class RoleService(
.filterIsInstance<OidcUserAuthority>() .filterIsInstance<OidcUserAuthority>()
.flatMap { oidcUserAuthority -> .flatMap { oidcUserAuthority ->
val userInfo = oidcUserAuthority.userInfo val userInfo = oidcUserAuthority.userInfo
val roles = userInfo.getClaim<List<String>>("roles") ?: return@flatMap emptySequence() val rolesClaim = configService.get(ConfigProperties.SSO.OIDC.RolesClaim)
val roles = userInfo.getClaim<List<String>>(rolesClaim) ?: return@flatMap emptySequence()
roles.asSequence().mapNotNull { roles.asSequence().mapNotNull {
if (it.startsWith(SSO_ROLE_PREFIX)) SimpleGrantedAuthority( if (it.startsWith(SSO_ROLE_PREFIX)) SimpleGrantedAuthority(
it.replace(SSO_ROLE_PREFIX, INTERNAL_ROLE_PREFIX) it.replace(SSO_ROLE_PREFIX, INTERNAL_ROLE_PREFIX)