diff --git a/cypress/e2e/frontend/manage/config/config-manager.cy.ts b/cypress/e2e/frontend/manage/config/config-manager.cy.ts index b4e3d94b..4ecccf7b 100644 --- a/cypress/e2e/frontend/manage/config/config-manager.cy.ts +++ b/cypress/e2e/frontend/manage/config/config-manager.cy.ts @@ -143,6 +143,7 @@ context('Config Manager', () => { ['New commenters must confirm their email', ''], ['New users must confirm their email', '✔'], ['Enable registration of new users', '✔'], + ['Enable registration of new users via SSO', '✔'], ['Show login dialog for unauthenticated users', '✔'], ['Enable commenter registration via external provider', '✔'], ['Enable local commenter registration', '✔'], @@ -182,6 +183,7 @@ context('Config Manager', () => { cy.get('@configEdit').find('#auth_signup_confirm_commenter') .should('not.be.checked').clickLabel().should('be.checked'); cy.get('@configEdit').find('#auth_signup_confirm_user') .should('be.checked') .clickLabel().should('not.be.checked'); cy.get('@configEdit').find('#auth_signup_enabled') .should('be.checked') .clickLabel().should('not.be.checked'); + cy.get('@configEdit').find('#auth_signup_sso_enabled') .should('be.checked') .clickLabel().should('not.be.checked'); cy.get('@configEdit').find('#domain_defaults_comments_deletion_author') .should('be.checked') .clickLabel().should('not.be.checked'); cy.get('@configEdit').find('#domain_defaults_comments_deletion_moderator').should('be.checked') .clickLabel().should('not.be.checked'); cy.get('@configEdit').find('#domain_defaults_comments_editing_author') .should('be.checked') .clickLabel().should('not.be.checked'); @@ -219,6 +221,7 @@ context('Config Manager', () => { ['New commenters must confirm their email', '✔'], ['New users must confirm their email', ''], ['Enable registration of new users', ''], + ['Enable registration of new users via SSO', ''], ['Show login dialog for unauthenticated users', ''], ['Enable commenter registration via external provider', ''], ['Enable local commenter registration', '✔'], @@ -283,6 +286,7 @@ context('Config Manager', () => { [InstanceConfigKey.authSignupConfirmCommenter]: false, [InstanceConfigKey.authSignupConfirmUser]: false, [InstanceConfigKey.authSignupEnabled]: false, + [InstanceConfigKey.authSsoSignupEnabled]: false, [InstanceConfigKey.domainDefaultsCommentDeletionAuthor]: false, [InstanceConfigKey.domainDefaultsCommentDeletionModerator]: true, [InstanceConfigKey.domainDefaultsCommentEditingAuthor]: true, @@ -311,6 +315,7 @@ context('Config Manager', () => { ['New commenters must confirm their email', ''], ['New users must confirm their email', ''], ['Enable registration of new users', ''], + ['Enable registration of new users via SSO', ''], ['Show login dialog for unauthenticated users', ''], ['Enable commenter registration via external provider', ''], ['Enable local commenter registration', ''], diff --git a/cypress/support/cy-utils.ts b/cypress/support/cy-utils.ts index 4775dce0..c0a87913 100644 --- a/cypress/support/cy-utils.ts +++ b/cypress/support/cy-utils.ts @@ -199,6 +199,7 @@ export enum InstanceConfigKey { authSignupConfirmCommenter = 'auth.signup.confirm.commenter', authSignupConfirmUser = 'auth.signup.confirm.user', authSignupEnabled = 'auth.signup.enabled', + authSsoSignupEnabled = 'auth.signup.sso.enabled', integrationsUseGravatar = 'integrations.useGravatar', operationNewOwnerEnabled = 'operation.newOwner.enabled', // Domain defaults diff --git a/docs/content/configuration/backend/dynamic/auth.signup.enabled.en.md b/docs/content/configuration/backend/dynamic/auth.signup.enabled.en.md index 35acc2ba..9427c016 100644 --- a/docs/content/configuration/backend/dynamic/auth.signup.enabled.en.md +++ b/docs/content/configuration/backend/dynamic/auth.signup.enabled.en.md @@ -7,6 +7,7 @@ tags: - administration - Administration UI seeAlso: + - auth.signup.sso.enabled - domain.defaults.signup.enablefederated - domain.defaults.signup.enablelocal - domain.defaults.signup.enablesso diff --git a/docs/content/configuration/backend/dynamic/auth.signup.sso.enabled.en.md b/docs/content/configuration/backend/dynamic/auth.signup.sso.enabled.en.md new file mode 100644 index 00000000..66748b51 --- /dev/null +++ b/docs/content/configuration/backend/dynamic/auth.signup.sso.enabled.en.md @@ -0,0 +1,23 @@ +--- +title: Enable registration of new users via SSO +description: auth.signup.sso.enabled +tags: + - configuration + - dynamic configuration + - administration + - Administration UI +seeAlso: + - auth.signup.enabled + - domain.defaults.signup.enablefederated + - domain.defaults.signup.enablelocal + - domain.defaults.signup.enablesso +--- + +This [dynamic configuration](/configuration/backend/dynamic) parameter controls whether new users are allowed to register in the Administration UI of Comentario via SSO. + + + +* If set to `On`, new users can register in the Administration UI via SSO. +* If set to `Off`, new user registrations via SSO are forbidden. + +This setting applies only to the Administration UI. diff --git a/e2e/plugin/db-seed.sql b/e2e/plugin/db-seed.sql index 1e02e554..69aaec98 100644 --- a/e2e/plugin/db-seed.sql +++ b/e2e/plugin/db-seed.sql @@ -5,6 +5,7 @@ values ('auth.signup.confirm.commenter', 'false', '0001-01-01 00:00:00.000000'), ('auth.signup.confirm.user', 'true', '0001-01-01 00:00:00.000000'), ('auth.signup.enabled', 'true', '0001-01-01 00:00:00.000000'), + ('auth.signup.sso.enabled', 'true', '0001-01-01 00:00:00.000000'), ('domain.defaults.comments.deletion.author', 'true', '0001-01-01 00:00:00.000000'), ('domain.defaults.comments.deletion.moderator', 'true', '0001-01-01 00:00:00.000000'), ('domain.defaults.comments.editing.author', 'true', '0001-01-01 00:00:00.000000'), diff --git a/frontend/app/_models/config.ts b/frontend/app/_models/config.ts index 11ddd516..0ded8049 100644 --- a/frontend/app/_models/config.ts +++ b/frontend/app/_models/config.ts @@ -30,6 +30,7 @@ export enum InstanceConfigItemKey { authSignupConfirmCommenter = 'auth.signup.confirm.commenter', authSignupConfirmUser = 'auth.signup.confirm.user', authSignupEnabled = 'auth.signup.enabled', + authSsoSignupEnabled = 'auth.signup.sso.enabled', integrationsUseGravatar = 'integrations.useGravatar', operationNewOwnerEnabled = 'operation.newOwner.enabled', // Domain defaults diff --git a/frontend/app/_modules/manage/_pipes/dyn-config-item-name.pipe.spec.ts b/frontend/app/_modules/manage/_pipes/dyn-config-item-name.pipe.spec.ts index b282586c..1eaf1085 100644 --- a/frontend/app/_modules/manage/_pipes/dyn-config-item-name.pipe.spec.ts +++ b/frontend/app/_modules/manage/_pipes/dyn-config-item-name.pipe.spec.ts @@ -21,6 +21,7 @@ describe('DynConfigItemNamePipe', () => { {in: 'auth.signup.confirm.commenter', want: 'New commenters must confirm their email'}, {in: 'auth.signup.confirm.user', want: 'New users must confirm their email'}, {in: 'auth.signup.enabled', want: 'Enable registration of new users'}, + {in: 'auth.signup.sso.enabled', want: 'Enable registration of new users via SSO' }, {in: 'integrations.useGravatar', want: 'Use Gravatar for user avatars'}, {in: 'operation.newOwner.enabled', want: 'Non-owner users can add domains'}, // Domain defaults diff --git a/frontend/app/_modules/manage/_pipes/dyn-config-item-name.pipe.ts b/frontend/app/_modules/manage/_pipes/dyn-config-item-name.pipe.ts index 7d3dc792..723e8149 100644 --- a/frontend/app/_modules/manage/_pipes/dyn-config-item-name.pipe.ts +++ b/frontend/app/_modules/manage/_pipes/dyn-config-item-name.pipe.ts @@ -12,6 +12,7 @@ export class DynConfigItemNamePipe implements PipeTransform { [InstanceConfigItemKey.authSignupConfirmCommenter]: $localize`New commenters must confirm their email`, [InstanceConfigItemKey.authSignupConfirmUser]: $localize`New users must confirm their email`, [InstanceConfigItemKey.authSignupEnabled]: $localize`Enable registration of new users`, + [InstanceConfigItemKey.authSsoSignupEnabled]: $localize`Enable registration of new users via SSO`, [InstanceConfigItemKey.integrationsUseGravatar]: $localize`Use Gravatar for user avatars`, [InstanceConfigItemKey.operationNewOwnerEnabled]: $localize`Non-owner users can add domains`, // Domain defaults diff --git a/internal/api/restapi/handlers/oauth.go b/internal/api/restapi/handlers/oauth.go index 8c5129f2..3837d229 100644 --- a/internal/api/restapi/handlers/oauth.go +++ b/internal/api/restapi/handlers/oauth.go @@ -220,7 +220,7 @@ func AuthOauthCallback(params api_general.AuthOauthCallbackParams) middleware.Re var cfgItem *data.DynConfigItem if domain == nil { // Frontend signup - cfgItem, err = svc.Services.DynConfigService().Get(data.ConfigKeyAuthSignupEnabled) + cfgItem, err = svc.Services.DynConfigService().Get(data.ConfigKeyAuthSsoSignupEnabled) } else if sso { // SSO embed signup @@ -248,9 +248,29 @@ func AuthOauthCallback(params api_general.AuthOauthCallbackParams) middleware.Re return errors.New(errMessage) } + // Check if the superuser claim is set + superuser := false + if fidp, ok := config.FederatedIdProviders[models.FederatedIdpID(idpID)]; ok { + if fidp.SuperuserClaim != "" { + if raw, ok := fedUser.RawData[fidp.SuperuserClaim]; ok { + switch v := raw.(type) { + case bool: + if v { + superuser = true + } + case string: + if v == "true" || v == "1" { + superuser = true + } + } + } + } + } + // Insert a new user user = data.NewUser(fedUser.Email, fedUserName). WithConfirmed(true). // Confirm the user right away as we trust the IdP + WithSuperuser(superuser). WithLangFromReq(params.HTTPRequest). WithSignup(params.HTTPRequest, authSession.Host, !config.ServerConfig.Logging.FullIPs). WithFederated(fedUser.UserID, idpID). diff --git a/internal/config/oauth.go b/internal/config/oauth.go index 10917c44..7ba997d9 100644 --- a/internal/config/oauth.go +++ b/internal/config/oauth.go @@ -177,9 +177,10 @@ func oidcConfigure() error { // Add it to the configured providers map mid := models.FederatedIdpID(qid) FederatedIdProviders[mid] = &data.FederatedIdentityProvider{ - ID: mid, - Name: p.Name, - GothName: qid, + ID: mid, + Name: p.Name, + GothName: qid, + SuperuserClaim: p.SuperuserClaim, } cnt++ } diff --git a/internal/config/secrets.go b/internal/config/secrets.go index d3e2af97..92f33f74 100644 --- a/internal/config/secrets.go +++ b/internal/config/secrets.go @@ -59,10 +59,11 @@ type APIKey struct { // OIDCProvider stores OIDC provider configuration type OIDCProvider struct { - KeySecretURL `yaml:",inline"` - ID string `yaml:"id"` // Unique provider ID, e.g. "keycloak" - Name string `yaml:"name"` // Provider display name, e.g. "Keycloak" - Scopes []string `yaml:"scopes"` // Additional scopes to request + KeySecretURL `yaml:",inline"` + ID string `yaml:"id"` // Unique provider ID, e.g. "keycloak" + Name string `yaml:"name"` // Provider display name, e.g. "Keycloak" + Scopes []string `yaml:"scopes"` // Additional scopes to request + SuperuserClaim string `yaml:"superuserClaim"` // Name of the OIDC claim for superusers } // QualifiedID returns the provider's ID prepended with the common OIDC prefix diff --git a/internal/data/dyn_config.go b/internal/data/dyn_config.go index 0cd0d64e..b4ff042d 100644 --- a/internal/data/dyn_config.go +++ b/internal/data/dyn_config.go @@ -171,6 +171,7 @@ const ( ConfigKeyAuthSignupConfirmCommenter DynConfigItemKey = "auth.signup.confirm.commenter" ConfigKeyAuthSignupConfirmUser DynConfigItemKey = "auth.signup.confirm.user" ConfigKeyAuthSignupEnabled DynConfigItemKey = "auth.signup.enabled" + ConfigKeyAuthSsoSignupEnabled DynConfigItemKey = "auth.signup.sso.enabled" ConfigKeyIntegrationsUseGravatar DynConfigItemKey = "integrations.useGravatar" ConfigKeyOperationNewOwnerEnabled DynConfigItemKey = "operation.newOwner.enabled" ) @@ -204,6 +205,7 @@ var DefaultDynInstanceConfig = DynConfigMap{ ConfigKeyAuthSignupConfirmCommenter: {DefaultValue: "true", Datatype: ConfigDatatypeBool, Section: DynConfigItemSectionAuth}, ConfigKeyAuthSignupConfirmUser: {DefaultValue: "true", Datatype: ConfigDatatypeBool, Section: DynConfigItemSectionAuth}, ConfigKeyAuthSignupEnabled: {DefaultValue: "true", Datatype: ConfigDatatypeBool, Section: DynConfigItemSectionAuth}, + ConfigKeyAuthSsoSignupEnabled: {DefaultValue: "true", Datatype: ConfigDatatypeBool, Section: DynConfigItemSectionAuth}, ConfigKeyIntegrationsUseGravatar: {DefaultValue: "true", Datatype: ConfigDatatypeBool, Section: DynConfigItemSectionIntegrations}, ConfigKeyOperationNewOwnerEnabled: {DefaultValue: "false", Datatype: ConfigDatatypeBool, Section: DynConfigItemSectionMisc}, ConfigKeyDomainDefaultsPrefix + DomainConfigKeyCommentDeletionAuthor: {DefaultValue: "true", Datatype: ConfigDatatypeBool, Section: DynConfigItemSectionComments}, diff --git a/internal/data/models.go b/internal/data/models.go index 4561fad5..0b491724 100644 --- a/internal/data/models.go +++ b/internal/data/models.go @@ -74,9 +74,10 @@ func (sd SortDirection) ToOrderedExpression(ident string) exp.OrderedExpression // FederatedIdentityProvider describes a federated identity provider type FederatedIdentityProvider struct { - ID models.FederatedIdpID // Provider ID - Name string // Provider name - GothName string // Name of the corresponding goth provider + ID models.FederatedIdpID // Provider ID + Name string // Provider name + GothName string // Name of the corresponding goth provider + SuperuserClaim string // Name of the OIDC claim for superusers } // ToDTO converts this model into an API model