From e4bdaf26b00ec63d26a46950200e14d61f9721bc Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Fri, 10 Sep 2021 09:49:49 +0200 Subject: [PATCH] feat: select idp and auto register (#2336) * faet: auto regsiter config on idp * feat: auto register on login * feat: auto register on register * feat: redirect to selected identity provider * fix: test * fix: test * fix: user by id request org id * fix: migration version and test Co-authored-by: Livio Amstutz --- docs/docs/apis/openidoauth/scopes.md | 1 + docs/docs/apis/proto/admin.md | 2 + docs/docs/apis/proto/idp.md | 1 + docs/docs/apis/proto/management.md | 2 + internal/api/grpc/admin/idp_converter.go | 16 ++++---- internal/api/grpc/admin/idp_converter_test.go | 8 ++-- internal/api/grpc/idp/converter.go | 28 +++++++------- internal/api/grpc/management/idp_converter.go | 7 ++-- .../api/grpc/management/idp_converter_test.go | 9 +++-- internal/api/oidc/auth_request_converter.go | 36 ++++++++++++------ internal/api/oidc/client_converter.go | 3 ++ .../eventsourcing/eventstore/auth_request.go | 9 ++++- .../eventstore/auth_request_test.go | 9 +++++ internal/auth_request/model/request.go | 1 + internal/command/iam_converter.go | 11 +++--- internal/command/iam_idp_config.go | 9 +++-- internal/command/iam_idp_config_model.go | 4 ++ internal/command/iam_idp_config_test.go | 37 +++++++++++-------- internal/command/iam_idp_oidc_config_test.go | 3 ++ internal/command/iam_policy_login_test.go | 2 + internal/command/idp_config_model.go | 11 ++++-- internal/command/org_idp_config.go | 12 +++--- internal/command/org_idp_config_model.go | 4 ++ internal/command/org_idp_config_test.go | 37 +++++++++++-------- internal/command/org_idp_oidc_config_test.go | 3 ++ internal/command/org_policy_login_test.go | 2 + .../command/user_human_externalidp_test.go | 2 + internal/domain/idp_config.go | 14 ++++--- internal/domain/next_step.go | 7 ++++ internal/iam/model/idp_config_view.go | 1 + .../iam/repository/view/model/idp_config.go | 2 + internal/query/idp_config_model.go | 5 +++ internal/repository/iam/idp_config.go | 2 + internal/repository/idpconfig/idp_config.go | 36 +++++++++++------- internal/repository/org/idp_config.go | 2 + .../login/handler/external_login_handler.go | 11 +++++- .../handler/external_register_handler.go | 22 ++++++++++- internal/ui/login/handler/renderer.go | 4 +- migrations/cockroach/V1.70__idp.sql | 3 ++ proto/zitadel/admin.proto | 2 + proto/zitadel/idp.proto | 1 + proto/zitadel/management.proto | 2 + 42 files changed, 272 insertions(+), 111 deletions(-) create mode 100644 migrations/cockroach/V1.70__idp.sql diff --git a/docs/docs/apis/openidoauth/scopes.md b/docs/docs/apis/openidoauth/scopes.md index 163b1f6b4..7208cfc19 100644 --- a/docs/docs/apis/openidoauth/scopes.md +++ b/docs/docs/apis/openidoauth/scopes.md @@ -30,5 +30,6 @@ In addition to the standard compliant scopes we utilize the following scopes. | `urn:zitadel:iam:org:project:id:{projectid}:aud` | ZITADEL's Project id is `urn:zitadel:iam:org:project:id:69234237810729019:aud` | By adding this scope, the requested projectid will be added to the audience of the access and id token | | urn:zitadel:iam:user:metadata | `urn:zitadel:iam:user:metadata` | By adding this scope, the metadata of the user will be included in the token. The values are base64 encoded. | | urn:zitadel:iam:user:resourceowner | `urn:zitadel:iam:user:resourceowner` | By adding this scope, the resourceowner (id, name, primary_domain) of the user will be included in the token. | +| urn:zitadel:iam:org:idp:id:{idp_id} | `urn:zitadel:iam:org:idp:id:76625965177954913` | By adding this scope the user will directly be redirected to the identity provider to authenticate. Make sure you also send the primary domain scope if a custom login policy is configured. Otherwise the system will not be able to identify the identity provider. | > If access to ZITADEL's API's is needed with a service user the scope `urn:zitadel:iam:org:project:id:69234237810729019:aud` needs to be used with the JWT Profile request diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md index 907b186e4..57e0fbe72 100644 --- a/docs/docs/apis/proto/admin.md +++ b/docs/docs/apis/proto/admin.md @@ -1201,6 +1201,7 @@ This is an empty request | scopes | repeated string | - | | | display_name_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true
| | username_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true
| +| auto_register | bool | - | | @@ -2887,6 +2888,7 @@ This is an empty request | idp_id | string | - | string.min_len: 1
string.max_len: 200
| | name | string | - | string.min_len: 1
string.max_len: 200
| | styling_type | zitadel.idp.v1.IDPStylingType | - | enum.defined_only: true
| +| auto_register | bool | - | | diff --git a/docs/docs/apis/proto/idp.md b/docs/docs/apis/proto/idp.md index de646c887..8a23275ca 100644 --- a/docs/docs/apis/proto/idp.md +++ b/docs/docs/apis/proto/idp.md @@ -22,6 +22,7 @@ title: zitadel/idp.proto | styling_type | IDPStylingType | - | | | owner | IDPOwnerType | - | | | [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) config.oidc_config | OIDCConfig | - | | +| auto_register | bool | - | | diff --git a/docs/docs/apis/proto/management.md b/docs/docs/apis/proto/management.md index bb12511c7..a59153af5 100644 --- a/docs/docs/apis/proto/management.md +++ b/docs/docs/apis/proto/management.md @@ -3154,6 +3154,7 @@ This is an empty request | scopes | repeated string | - | | | display_name_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true
| | username_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true
| +| auto_register | bool | - | | @@ -7379,6 +7380,7 @@ This is an empty request | idp_id | string | - | string.min_len: 1
string.max_len: 200
| | name | string | - | string.min_len: 1
string.max_len: 200
| | styling_type | zitadel.idp.v1.IDPStylingType | - | enum.defined_only: true
| +| auto_register | bool | - | | diff --git a/internal/api/grpc/admin/idp_converter.go b/internal/api/grpc/admin/idp_converter.go index c74eff76d..98c918acf 100644 --- a/internal/api/grpc/admin/idp_converter.go +++ b/internal/api/grpc/admin/idp_converter.go @@ -12,10 +12,11 @@ import ( func addOIDCIDPRequestToDomain(req *admin_pb.AddOIDCIDPRequest) *domain.IDPConfig { return &domain.IDPConfig{ - Name: req.Name, - OIDCConfig: addOIDCIDPRequestToDomainOIDCIDPConfig(req), - StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType), - Type: domain.IDPConfigTypeOIDC, + Name: req.Name, + OIDCConfig: addOIDCIDPRequestToDomainOIDCIDPConfig(req), + StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType), + Type: domain.IDPConfigTypeOIDC, + AutoRegister: req.AutoRegister, } } @@ -32,9 +33,10 @@ func addOIDCIDPRequestToDomainOIDCIDPConfig(req *admin_pb.AddOIDCIDPRequest) *do func updateIDPToDomain(req *admin_pb.UpdateIDPRequest) *domain.IDPConfig { return &domain.IDPConfig{ - IDPConfigID: req.IdpId, - Name: req.Name, - StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType), + IDPConfigID: req.IdpId, + Name: req.Name, + StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType), + AutoRegister: req.AutoRegister, } } diff --git a/internal/api/grpc/admin/idp_converter_test.go b/internal/api/grpc/admin/idp_converter_test.go index 19b990dc1..d4f625542 100644 --- a/internal/api/grpc/admin/idp_converter_test.go +++ b/internal/api/grpc/admin/idp_converter_test.go @@ -28,6 +28,7 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) { Scopes: []string{"email", "profile"}, DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, + AutoRegister: true, }, }, }, @@ -98,9 +99,10 @@ func Test_updateIDPToDomain(t *testing.T) { name: "all fields filled", args: args{ req: &admin_pb.UpdateIDPRequest{ - IdpId: "13523", - Name: "new name", - StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE, + IdpId: "13523", + Name: "new name", + StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE, + AutoRegister: true, }, }, }, diff --git a/internal/api/grpc/idp/converter.go b/internal/api/grpc/idp/converter.go index 79778d674..a7974a9b2 100644 --- a/internal/api/grpc/idp/converter.go +++ b/internal/api/grpc/idp/converter.go @@ -18,29 +18,31 @@ func IDPViewsToPb(idps []*iam_model.IDPConfigView) []*idp_pb.IDP { func ModelIDPViewToPb(idp *iam_model.IDPConfigView) *idp_pb.IDP { return &idp_pb.IDP{ - Id: idp.IDPConfigID, - State: ModelIDPStateToPb(idp.State), - Name: idp.Name, - StylingType: ModelIDPStylingTypeToPb(idp.StylingType), - Owner: ModelIDPProviderTypeToPb(idp.IDPProviderType), - Config: ModelIDPViewToConfigPb(idp), + Id: idp.IDPConfigID, + State: ModelIDPStateToPb(idp.State), + Name: idp.Name, + StylingType: ModelIDPStylingTypeToPb(idp.StylingType), + AutoRegister: idp.AutoRegister, + Owner: ModelIDPProviderTypeToPb(idp.IDPProviderType), + Config: ModelIDPViewToConfigPb(idp), Details: obj_grpc.ToViewDetailsPb( idp.Sequence, idp.CreationDate, idp.ChangeDate, - "", //TODO: backend + idp.AggregateID, ), } } func IDPViewToPb(idp *domain.IDPConfigView) *idp_pb.IDP { mapped := &idp_pb.IDP{ - Id: idp.AggregateID, - State: IDPStateToPb(idp.State), - Name: idp.Name, - StylingType: IDPStylingTypeToPb(idp.StylingType), - Config: IDPViewToConfigPb(idp), - Details: obj_grpc.ToViewDetailsPb(idp.Sequence, idp.CreationDate, idp.ChangeDate, ""), //TODO: resource owner in view + Id: idp.AggregateID, + State: IDPStateToPb(idp.State), + Name: idp.Name, + StylingType: IDPStylingTypeToPb(idp.StylingType), + AutoRegister: idp.AutoRegister, + Config: IDPViewToConfigPb(idp), + Details: obj_grpc.ToViewDetailsPb(idp.Sequence, idp.CreationDate, idp.ChangeDate, idp.AggregateID), } return mapped } diff --git a/internal/api/grpc/management/idp_converter.go b/internal/api/grpc/management/idp_converter.go index 241e3be77..e90009a20 100644 --- a/internal/api/grpc/management/idp_converter.go +++ b/internal/api/grpc/management/idp_converter.go @@ -32,9 +32,10 @@ func addOIDCIDPRequestToDomainOIDCIDPConfig(req *mgmt_pb.AddOrgOIDCIDPRequest) * func updateIDPToDomain(req *mgmt_pb.UpdateOrgIDPRequest) *domain.IDPConfig { return &domain.IDPConfig{ - IDPConfigID: req.IdpId, - Name: req.Name, - StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType), + IDPConfigID: req.IdpId, + Name: req.Name, + StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType), + AutoRegister: req.AutoRegister, } } diff --git a/internal/api/grpc/management/idp_converter_test.go b/internal/api/grpc/management/idp_converter_test.go index 254808d74..fe5547aea 100644 --- a/internal/api/grpc/management/idp_converter_test.go +++ b/internal/api/grpc/management/idp_converter_test.go @@ -28,6 +28,7 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) { Scopes: []string{"email", "profile"}, DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, + AutoRegister: true, }, }, }, @@ -45,6 +46,7 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) { "OIDCConfig.AuthorizationEndpoint", "OIDCConfig.TokenEndpoint", "Type", //TODO: default (0) is oidc + "AutoRegister", ) }) } @@ -98,9 +100,10 @@ func Test_updateIDPToDomain(t *testing.T) { name: "all fields filled", args: args{ req: &mgmt_pb.UpdateOrgIDPRequest{ - IdpId: "13523", - Name: "new name", - StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE, + IdpId: "13523", + Name: "new name", + StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE, + AutoRegister: true, }, }, }, diff --git a/internal/api/oidc/auth_request_converter.go b/internal/api/oidc/auth_request_converter.go index 52ca9a063..8af7ef836 100644 --- a/internal/api/oidc/auth_request_converter.go +++ b/internal/api/oidc/auth_request_converter.go @@ -3,6 +3,7 @@ package oidc import ( "context" "net" + "strings" "time" "github.com/caos/oidc/pkg/oidc" @@ -10,6 +11,7 @@ import ( "golang.org/x/text/language" http_utils "github.com/caos/zitadel/internal/api/http" + model2 "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/user/model" @@ -113,18 +115,19 @@ func AuthRequestFromBusiness(authReq *domain.AuthRequest) (_ op.AuthRequest, err func CreateAuthRequestToBusiness(ctx context.Context, authReq *oidc.AuthRequest, userAgentID, userID string) *domain.AuthRequest { return &domain.AuthRequest{ - CreationDate: time.Now(), - AgentID: userAgentID, - BrowserInfo: ParseBrowserInfoFromContext(ctx), - ApplicationID: authReq.ClientID, - CallbackURI: authReq.RedirectURI, - TransferState: authReq.State, - Prompt: PromptToBusiness(authReq.Prompt), - PossibleLOAs: ACRValuesToBusiness(authReq.ACRValues), - UiLocales: UILocalesToBusiness(authReq.UILocales), - LoginHint: authReq.LoginHint, - MaxAuthAge: MaxAgeToBusiness(authReq.MaxAge), - UserID: userID, + CreationDate: time.Now(), + AgentID: userAgentID, + BrowserInfo: ParseBrowserInfoFromContext(ctx), + ApplicationID: authReq.ClientID, + CallbackURI: authReq.RedirectURI, + TransferState: authReq.State, + Prompt: PromptToBusiness(authReq.Prompt), + PossibleLOAs: ACRValuesToBusiness(authReq.ACRValues), + UiLocales: UILocalesToBusiness(authReq.UILocales), + LoginHint: authReq.LoginHint, + SelectedIDPConfigID: GetSelectedIDPIDFromScopes(authReq.Scopes), + MaxAuthAge: MaxAgeToBusiness(authReq.MaxAge), + UserID: userID, Request: &domain.AuthRequestOIDC{ Scopes: authReq.Scopes, ResponseType: ResponseTypeToBusiness(authReq.ResponseType), @@ -196,6 +199,15 @@ func UILocalesToBusiness(tags []language.Tag) []string { return locales } +func GetSelectedIDPIDFromScopes(scopes oidc.SpaceDelimitedArray) string { + for _, scope := range scopes { + if strings.HasPrefix(scope, model2.SelectIDPScope) { + return strings.TrimPrefix(scope, model2.SelectIDPScope) + } + } + return "" +} + func MaxAgeToBusiness(maxAge *uint) *time.Duration { if maxAge == nil { return nil diff --git a/internal/api/oidc/client_converter.go b/internal/api/oidc/client_converter.go index 17e1424be..a285b8017 100644 --- a/internal/api/oidc/client_converter.go +++ b/internal/api/oidc/client_converter.go @@ -106,6 +106,9 @@ func (c *Client) IsScopeAllowed(scope string) bool { if strings.HasPrefix(scope, authreq_model.ProjectIDScope) { return true } + if strings.HasPrefix(scope, authreq_model.SelectIDPScope) { + return true + } if strings.HasPrefix(scope, ScopeUserMetaData) { return true } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index df3f41418..eb0d057da 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -133,6 +133,7 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *dom err = repo.checkLoginName(ctx, request, request.LoginHint) logging.LogWithFields("EVENT-aG311", "login name", request.LoginHint, "id", request.ID, "applicationID", request.ApplicationID, "traceID", tracing.TraceIDFromCtx(ctx)).OnError(err).Debug("login hint invalid") } + err = repo.AuthRequests.SaveAuthRequest(ctx, request) if err != nil { return nil, err @@ -642,7 +643,13 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth if err != nil { return nil, err } - if len(users) > 0 || domain.IsPrompt(request.Prompt, domain.PromptSelectAccount) { + if domain.IsPrompt(request.Prompt, domain.PromptSelectAccount) { + steps = append(steps, &domain.SelectUserStep{Users: users}) + } + if request.SelectedIDPConfigID != "" { + steps = append(steps, &domain.RedirectToExternalIDPStep{}) + } + if len(request.Prompt) == 0 && len(users) > 0 { steps = append(steps, &domain.SelectUserStep{Users: users}) } } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index 8f4ee22a2..741eea5f5 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -305,6 +305,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { []domain.NextStep{&domain.ExternalNotFoundOptionStep{}}, nil, }, + { + "user not set no active session selected idp, redirect to external idp step", + fields{ + userSessionViewProvider: &mockViewNoUserSession{}, + }, + args{&domain.AuthRequest{SelectedIDPConfigID: "id"}, false}, + []domain.NextStep{&domain.LoginStep{}, &domain.RedirectToExternalIDPStep{}}, + nil, + }, { "user not set, prompt select account and internal error, internal error", fields{ diff --git a/internal/auth_request/model/request.go b/internal/auth_request/model/request.go index 43f8c93ea..25bf0d31b 100644 --- a/internal/auth_request/model/request.go +++ b/internal/auth_request/model/request.go @@ -23,6 +23,7 @@ const ( OrgDomainPrimaryClaim = "urn:zitadel:iam:org:domain:primary" ProjectIDScope = "urn:zitadel:iam:org:project:id:" AudSuffix = ":aud" + SelectIDPScope = "urn:zitadel:iam:org:idp:id:" ) type AuthRequestOIDC struct { diff --git a/internal/command/iam_converter.go b/internal/command/iam_converter.go index acf5e6e81..eefbb8077 100644 --- a/internal/command/iam_converter.go +++ b/internal/command/iam_converter.go @@ -130,11 +130,12 @@ func writeModelToPrivacyPolicy(wm *PrivacyPolicyWriteModel) *domain.PrivacyPolic func writeModelToIDPConfig(wm *IDPConfigWriteModel) *domain.IDPConfig { return &domain.IDPConfig{ - ObjectRoot: writeModelToObjectRoot(wm.WriteModel), - IDPConfigID: wm.ConfigID, - Name: wm.Name, - State: wm.State, - StylingType: wm.StylingType, + ObjectRoot: writeModelToObjectRoot(wm.WriteModel), + IDPConfigID: wm.ConfigID, + Name: wm.Name, + State: wm.State, + StylingType: wm.StylingType, + AutoRegister: wm.AutoRegister, } } diff --git a/internal/command/iam_idp_config.go b/internal/command/iam_idp_config.go index 3d1cda2c0..86f630222 100644 --- a/internal/command/iam_idp_config.go +++ b/internal/command/iam_idp_config.go @@ -37,6 +37,7 @@ func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPCo config.Name, config.Type, config.StylingType, + config.AutoRegister, ), iam_repo.NewIDPOIDCConfigAddedEvent( ctx, @@ -73,11 +74,11 @@ func (c *Commands) ChangeDefaultIDPConfig(ctx context.Context, config *domain.ID return nil, err } if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified { - return nil, caos_errs.ThrowNotFound(nil, "IAM-4M9so", "Errors.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "IAM-m0e3r", "Errors.IDPConfig.NotExisting") } iamAgg := IAMAggregateFromWriteModel(&existingIDP.WriteModel) - changedEvent, hasChanged := existingIDP.NewChangedEvent(ctx, iamAgg, config.IDPConfigID, config.Name, config.StylingType) + changedEvent, hasChanged := existingIDP.NewChangedEvent(ctx, iamAgg, config.IDPConfigID, config.Name, config.StylingType, config.AutoRegister) if !hasChanged { return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") } @@ -98,7 +99,7 @@ func (c *Commands) DeactivateDefaultIDPConfig(ctx context.Context, idpID string) return nil, err } if existingIDP.State != domain.IDPConfigStateActive { - return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9so", "Errors.IAM.IDPConfig.NotActive") + return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-2n0fs", "Errors.IAM.IDPConfig.NotActive") } iamAgg := IAMAggregateFromWriteModel(&existingIDP.WriteModel) pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewIDPConfigDeactivatedEvent(ctx, iamAgg, idpID)) @@ -173,7 +174,7 @@ func (c *Commands) getIAMIDPConfigByID(ctx context.Context, idpID string) (*doma return nil, err } if !config.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "IAM-4M9so", "Errors.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "IAM-p0pFF", "Errors.IDPConfig.NotExisting") } return writeModelToIDPConfig(&config.IDPConfigWriteModel), nil } diff --git a/internal/command/iam_idp_config_model.go b/internal/command/iam_idp_config_model.go index ae4e72de1..2f8994afd 100644 --- a/internal/command/iam_idp_config_model.go +++ b/internal/command/iam_idp_config_model.go @@ -100,6 +100,7 @@ func (wm *IAMIDPConfigWriteModel) NewChangedEvent( configID, name string, stylingType domain.IDPConfigStylingType, + autoRegister bool, ) (*iam.IDPConfigChangedEvent, bool) { changes := make([]idpconfig.IDPConfigChanges, 0) @@ -111,6 +112,9 @@ func (wm *IAMIDPConfigWriteModel) NewChangedEvent( if stylingType.Valid() && wm.StylingType != stylingType { changes = append(changes, idpconfig.ChangeStyleType(stylingType)) } + if wm.AutoRegister != autoRegister { + changes = append(changes, idpconfig.ChangeAutoRegister(autoRegister)) + } if len(changes) == 0 { return nil, false } diff --git a/internal/command/iam_idp_config_test.go b/internal/command/iam_idp_config_test.go index a900b6efa..7aa781e6c 100644 --- a/internal/command/iam_idp_config_test.go +++ b/internal/command/iam_idp_config_test.go @@ -68,6 +68,7 @@ func TestCommandSide_AddDefaultIDPConfig(t *testing.T) { "name1", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeGoogle, + true, ), ), eventFromEventPusher( @@ -99,8 +100,9 @@ func TestCommandSide_AddDefaultIDPConfig(t *testing.T) { args: args{ ctx: context.Background(), config: &domain.IDPConfig{ - Name: "name1", - StylingType: domain.IDPConfigStylingTypeGoogle, + Name: "name1", + StylingType: domain.IDPConfigStylingTypeGoogle, + AutoRegister: true, OIDCConfig: &domain.OIDCIDPConfig{ ClientID: "clientid1", Issuer: "issuer", @@ -119,10 +121,11 @@ func TestCommandSide_AddDefaultIDPConfig(t *testing.T) { AggregateID: "IAM", ResourceOwner: "IAM", }, - IDPConfigID: "config1", - Name: "name1", - StylingType: domain.IDPConfigStylingTypeGoogle, - State: domain.IDPConfigStateActive, + IDPConfigID: "config1", + Name: "name1", + StylingType: domain.IDPConfigStylingTypeGoogle, + State: domain.IDPConfigStateActive, + AutoRegister: true, }, }, }, @@ -212,6 +215,7 @@ func TestCommandSide_ChangeDefaultIDPConfig(t *testing.T) { "name1", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeGoogle, + true, ), ), eventFromEventPusher( @@ -237,7 +241,7 @@ func TestCommandSide_ChangeDefaultIDPConfig(t *testing.T) { expectPush( []*repository.Event{ eventFromEventPusher( - newDefaultIDPConfigChangedEvent(context.Background(), "config1", "name1", "name2", domain.IDPConfigStylingTypeUnspecified), + newDefaultIDPConfigChangedEvent(context.Background(), "config1", "name1", "name2", domain.IDPConfigStylingTypeUnspecified, false), ), }, uniqueConstraintsFromEventConstraint(idpconfig.NewRemoveIDPConfigNameUniqueConstraint("name1", "IAM")), @@ -248,9 +252,10 @@ func TestCommandSide_ChangeDefaultIDPConfig(t *testing.T) { args: args{ ctx: context.Background(), config: &domain.IDPConfig{ - IDPConfigID: "config1", - Name: "name2", - StylingType: domain.IDPConfigStylingTypeUnspecified, + IDPConfigID: "config1", + Name: "name2", + StylingType: domain.IDPConfigStylingTypeUnspecified, + AutoRegister: false, }, }, res: res{ @@ -259,10 +264,11 @@ func TestCommandSide_ChangeDefaultIDPConfig(t *testing.T) { AggregateID: "IAM", ResourceOwner: "IAM", }, - IDPConfigID: "config1", - Name: "name2", - StylingType: domain.IDPConfigStylingTypeUnspecified, - State: domain.IDPConfigStateActive, + IDPConfigID: "config1", + Name: "name2", + StylingType: domain.IDPConfigStylingTypeUnspecified, + State: domain.IDPConfigStateActive, + AutoRegister: false, }, }, }, @@ -286,7 +292,7 @@ func TestCommandSide_ChangeDefaultIDPConfig(t *testing.T) { } } -func newDefaultIDPConfigChangedEvent(ctx context.Context, configID, oldName, newName string, stylingType domain.IDPConfigStylingType) *iam.IDPConfigChangedEvent { +func newDefaultIDPConfigChangedEvent(ctx context.Context, configID, oldName, newName string, stylingType domain.IDPConfigStylingType, autoRegister bool) *iam.IDPConfigChangedEvent { event, _ := iam.NewIDPConfigChangedEvent(ctx, &iam.NewAggregate().Aggregate, configID, @@ -294,6 +300,7 @@ func newDefaultIDPConfigChangedEvent(ctx context.Context, configID, oldName, new []idpconfig.IDPConfigChanges{ idpconfig.ChangeName(newName), idpconfig.ChangeStyleType(stylingType), + idpconfig.ChangeAutoRegister(autoRegister), }, ) return event diff --git a/internal/command/iam_idp_oidc_config_test.go b/internal/command/iam_idp_oidc_config_test.go index 425cfc791..9381935dd 100644 --- a/internal/command/iam_idp_oidc_config_test.go +++ b/internal/command/iam_idp_oidc_config_test.go @@ -84,6 +84,7 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) { "name1", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeGoogle, + true, ), ), eventFromEventPusher( @@ -138,6 +139,7 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) { "name1", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeGoogle, + true, ), ), eventFromEventPusher( @@ -193,6 +195,7 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) { "name1", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeGoogle, + true, ), ), eventFromEventPusher( diff --git a/internal/command/iam_policy_login_test.go b/internal/command/iam_policy_login_test.go index 77119d7b6..b36b7ae16 100644 --- a/internal/command/iam_policy_login_test.go +++ b/internal/command/iam_policy_login_test.go @@ -344,6 +344,7 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) { "name", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeUnspecified, + true, ), ), ), @@ -391,6 +392,7 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) { "name", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeUnspecified, + true, ), ), ), diff --git a/internal/command/idp_config_model.go b/internal/command/idp_config_model.go index 133e12047..c41ace1bb 100644 --- a/internal/command/idp_config_model.go +++ b/internal/command/idp_config_model.go @@ -11,9 +11,10 @@ type IDPConfigWriteModel struct { State domain.IDPConfigState - ConfigID string - Name string - StylingType domain.IDPConfigStylingType + ConfigID string + Name string + AutoRegister bool + StylingType domain.IDPConfigStylingType } func (rm *IDPConfigWriteModel) AppendEvents(events ...eventstore.EventReader) { @@ -42,6 +43,7 @@ func (rm *IDPConfigWriteModel) reduceConfigAddedEvent(e *idpconfig.IDPConfigAdde rm.ConfigID = e.ConfigID rm.Name = e.Name rm.StylingType = e.StylingType + rm.AutoRegister = e.AutoRegister rm.State = domain.IDPConfigStateActive } @@ -52,6 +54,9 @@ func (rm *IDPConfigWriteModel) reduceConfigChangedEvent(e *idpconfig.IDPConfigCh if e.StylingType != nil && e.StylingType.Valid() { rm.StylingType = *e.StylingType } + if e.AutoRegister != nil { + rm.AutoRegister = *e.AutoRegister + } } func (rm *IDPConfigWriteModel) reduceConfigStateChanged(configID string, state domain.IDPConfigState) { diff --git a/internal/command/org_idp_config.go b/internal/command/org_idp_config.go index 6c9043807..2a15ab907 100644 --- a/internal/command/org_idp_config.go +++ b/internal/command/org_idp_config.go @@ -40,6 +40,7 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, r config.Name, config.Type, config.StylingType, + config.AutoRegister, ), org_repo.NewIDPOIDCConfigAddedEvent( ctx, @@ -69,12 +70,12 @@ func (c *Commands) ChangeIDPConfig(ctx context.Context, config *domain.IDPConfig if resourceOwner == "" { return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Gh8ds", "Errors.ResourceOwnerMissing") } - existingIDP, err := c.orgIDPConfigWriteModelByID(ctx, config.IDPConfigID, config.AggregateID) + existingIDP, err := c.orgIDPConfigWriteModelByID(ctx, config.IDPConfigID, resourceOwner) if err != nil { return nil, err } if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified { - return nil, caos_errs.ThrowNotFound(nil, "Org-4M9so", "Errors.Org.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "Org-1J9fs", "Errors.Org.IDPConfig.NotExisting") } orgAgg := OrgAggregateFromWriteModel(&existingIDP.WriteModel) @@ -83,7 +84,8 @@ func (c *Commands) ChangeIDPConfig(ctx context.Context, config *domain.IDPConfig orgAgg, config.IDPConfigID, config.Name, - config.StylingType) + config.StylingType, + config.AutoRegister) if !hasChanged { return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-4M9vs", "Errors.Org.LabelPolicy.NotChanged") @@ -105,7 +107,7 @@ func (c *Commands) DeactivateIDPConfig(ctx context.Context, idpID, orgID string) return nil, err } if existingIDP.State != domain.IDPConfigStateActive { - return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-4M9so", "Errors.Org.IDPConfig.NotActive") + return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-BBmd0", "Errors.Org.IDPConfig.NotActive") } orgAgg := OrgAggregateFromWriteModel(&existingIDP.WriteModel) pushedEvents, err := c.eventstore.PushEvents(ctx, org_repo.NewIDPConfigDeactivatedEvent(ctx, orgAgg, idpID)) @@ -185,7 +187,7 @@ func (c *Commands) getOrgIDPConfigByID(ctx context.Context, idpID, orgID string) return nil, err } if !config.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "ORG-4M9so", "Errors.Org.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "ORG-2m90f", "Errors.Org.IDPConfig.NotExisting") } return writeModelToIDPConfig(&config.IDPConfigWriteModel), nil } diff --git a/internal/command/org_idp_config_model.go b/internal/command/org_idp_config_model.go index b3c466bfa..2c5599da1 100644 --- a/internal/command/org_idp_config_model.go +++ b/internal/command/org_idp_config_model.go @@ -100,6 +100,7 @@ func (wm *OrgIDPConfigWriteModel) NewChangedEvent( configID, name string, stylingType domain.IDPConfigStylingType, + autoRegister bool, ) (*org.IDPConfigChangedEvent, bool) { changes := make([]idpconfig.IDPConfigChanges, 0) @@ -111,6 +112,9 @@ func (wm *OrgIDPConfigWriteModel) NewChangedEvent( if stylingType.Valid() && wm.StylingType != stylingType { changes = append(changes, idpconfig.ChangeStyleType(stylingType)) } + if wm.AutoRegister != autoRegister { + changes = append(changes, idpconfig.ChangeAutoRegister(autoRegister)) + } if len(changes) == 0 { return nil, false } diff --git a/internal/command/org_idp_config_test.go b/internal/command/org_idp_config_test.go index 781849ea4..ffef7899d 100644 --- a/internal/command/org_idp_config_test.go +++ b/internal/command/org_idp_config_test.go @@ -50,8 +50,9 @@ func TestCommandSide_AddIDPConfig(t *testing.T) { args: args{ ctx: context.Background(), config: &domain.IDPConfig{ - Name: "name1", - StylingType: domain.IDPConfigStylingTypeGoogle, + Name: "name1", + StylingType: domain.IDPConfigStylingTypeGoogle, + AutoRegister: true, OIDCConfig: &domain.OIDCIDPConfig{ ClientID: "clientid1", Issuer: "issuer", @@ -96,6 +97,7 @@ func TestCommandSide_AddIDPConfig(t *testing.T) { "name1", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeGoogle, + true, ), ), eventFromEventPusher( @@ -128,8 +130,9 @@ func TestCommandSide_AddIDPConfig(t *testing.T) { ctx: context.Background(), resourceOwner: "org1", config: &domain.IDPConfig{ - Name: "name1", - StylingType: domain.IDPConfigStylingTypeGoogle, + Name: "name1", + StylingType: domain.IDPConfigStylingTypeGoogle, + AutoRegister: true, OIDCConfig: &domain.OIDCIDPConfig{ ClientID: "clientid1", Issuer: "issuer", @@ -148,10 +151,11 @@ func TestCommandSide_AddIDPConfig(t *testing.T) { AggregateID: "org1", ResourceOwner: "org1", }, - IDPConfigID: "config1", - Name: "name1", - StylingType: domain.IDPConfigStylingTypeGoogle, - State: domain.IDPConfigStateActive, + IDPConfigID: "config1", + Name: "name1", + StylingType: domain.IDPConfigStylingTypeGoogle, + State: domain.IDPConfigStateActive, + AutoRegister: true, }, }, }, @@ -260,6 +264,7 @@ func TestCommandSide_ChangeIDPConfig(t *testing.T) { "name1", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeGoogle, + true, ), ), eventFromEventPusher( @@ -297,9 +302,10 @@ func TestCommandSide_ChangeIDPConfig(t *testing.T) { ctx: context.Background(), resourceOwner: "org1", config: &domain.IDPConfig{ - IDPConfigID: "config1", - Name: "name2", - StylingType: domain.IDPConfigStylingTypeUnspecified, + IDPConfigID: "config1", + Name: "name2", + StylingType: domain.IDPConfigStylingTypeUnspecified, + AutoRegister: true, }, }, res: res{ @@ -308,10 +314,11 @@ func TestCommandSide_ChangeIDPConfig(t *testing.T) { AggregateID: "org1", ResourceOwner: "org1", }, - IDPConfigID: "config1", - Name: "name2", - StylingType: domain.IDPConfigStylingTypeUnspecified, - State: domain.IDPConfigStateActive, + IDPConfigID: "config1", + Name: "name2", + StylingType: domain.IDPConfigStylingTypeUnspecified, + State: domain.IDPConfigStateActive, + AutoRegister: true, }, }, }, diff --git a/internal/command/org_idp_oidc_config_test.go b/internal/command/org_idp_oidc_config_test.go index ec835de87..214a841f0 100644 --- a/internal/command/org_idp_oidc_config_test.go +++ b/internal/command/org_idp_oidc_config_test.go @@ -104,6 +104,7 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) { "name1", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeGoogle, + true, ), ), eventFromEventPusher( @@ -159,6 +160,7 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) { "name1", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeGoogle, + true, ), ), eventFromEventPusher( @@ -215,6 +217,7 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) { "name1", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeGoogle, + true, ), ), eventFromEventPusher( diff --git a/internal/command/org_policy_login_test.go b/internal/command/org_policy_login_test.go index ddd73b38a..ff4cbb471 100644 --- a/internal/command/org_policy_login_test.go +++ b/internal/command/org_policy_login_test.go @@ -659,6 +659,7 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) { "name", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeUnspecified, + true, ), ), ), @@ -710,6 +711,7 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) { "name", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeUnspecified, + true, ), ), ), diff --git a/internal/command/user_human_externalidp_test.go b/internal/command/user_human_externalidp_test.go index d08b30d22..a74e575be 100644 --- a/internal/command/user_human_externalidp_test.go +++ b/internal/command/user_human_externalidp_test.go @@ -164,6 +164,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) { "name", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeUnspecified, + true, ), ), ), @@ -213,6 +214,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) { "name", domain.IDPConfigTypeOIDC, domain.IDPConfigStylingTypeUnspecified, + true, ), ), ), diff --git a/internal/domain/idp_config.go b/internal/domain/idp_config.go index 063eb17fe..b1ce3178a 100644 --- a/internal/domain/idp_config.go +++ b/internal/domain/idp_config.go @@ -9,12 +9,13 @@ import ( type IDPConfig struct { es_models.ObjectRoot - IDPConfigID string - Type IDPConfigType - Name string - StylingType IDPConfigStylingType - State IDPConfigState - OIDCConfig *OIDCIDPConfig + IDPConfigID string + Type IDPConfigType + Name string + StylingType IDPConfigStylingType + State IDPConfigState + OIDCConfig *OIDCIDPConfig + AutoRegister bool } type IDPConfigView struct { @@ -27,6 +28,7 @@ type IDPConfigView struct { ChangeDate time.Time Sequence uint64 IDPProviderType IdentityProviderType + AutoRegister bool IsOIDC bool OIDCClientID string diff --git a/internal/domain/next_step.go b/internal/domain/next_step.go index 57eaa3304..01083b0d1 100644 --- a/internal/domain/next_step.go +++ b/internal/domain/next_step.go @@ -27,6 +27,7 @@ const ( NextStepPasswordlessRegistrationPrompt NextStepRegistration NextStepProjectRequired + NextStepRedirectToExternalIDP ) type LoginStep struct{} @@ -67,6 +68,12 @@ const ( UserSessionStateTerminated ) +type RedirectToExternalIDPStep struct{} + +func (s *RedirectToExternalIDPStep) Type() NextStepType { + return NextStepRedirectToExternalIDP +} + type InitUserStep struct { PasswordSet bool } diff --git a/internal/iam/model/idp_config_view.go b/internal/iam/model/idp_config_view.go index 37de4feed..9c7cbb25a 100644 --- a/internal/iam/model/idp_config_view.go +++ b/internal/iam/model/idp_config_view.go @@ -13,6 +13,7 @@ type IDPConfigView struct { IDPConfigID string Name string StylingType IDPStylingType + AutoRegister bool State IDPConfigState CreationDate time.Time ChangeDate time.Time diff --git a/internal/iam/repository/view/model/idp_config.go b/internal/iam/repository/view/model/idp_config.go index 5470dc634..78cfab6cb 100644 --- a/internal/iam/repository/view/model/idp_config.go +++ b/internal/iam/repository/view/model/idp_config.go @@ -33,6 +33,7 @@ type IDPConfigView struct { ChangeDate time.Time `json:"-" gorm:"column:change_date"` IDPState int32 `json:"-" gorm:"column:idp_state"` IDPProviderType int32 `json:"-" gorm:"column:idp_provider_type"` + AutoRegister bool `json:"autoRegister" gorm:"column:auto_register"` IsOIDC bool `json:"-" gorm:"column:is_oidc"` OIDCClientID string `json:"clientId" gorm:"column:oidc_client_id"` @@ -54,6 +55,7 @@ func IDPConfigViewToModel(idp *IDPConfigView) *model.IDPConfigView { State: model.IDPConfigState(idp.IDPState), Name: idp.Name, StylingType: model.IDPStylingType(idp.StylingType), + AutoRegister: idp.AutoRegister, Sequence: idp.Sequence, CreationDate: idp.CreationDate, ChangeDate: idp.ChangeDate, diff --git a/internal/query/idp_config_model.go b/internal/query/idp_config_model.go index 35ffecdd9..044069900 100644 --- a/internal/query/idp_config_model.go +++ b/internal/query/idp_config_model.go @@ -12,6 +12,7 @@ type IDPConfigReadModel struct { State domain.IDPConfigState ConfigID string Name string + AutoRegister bool StylingType domain.IDPConfigStylingType ProviderType domain.IdentityProviderType @@ -77,6 +78,7 @@ func (rm *IDPConfigReadModel) reduceConfigAddedEvent(e *idpconfig.IDPConfigAdded rm.Name = e.Name rm.StylingType = e.StylingType rm.State = domain.IDPConfigStateActive + rm.AutoRegister = e.AutoRegister } func (rm *IDPConfigReadModel) reduceConfigChangedEvent(e *idpconfig.IDPConfigChangedEvent) { @@ -86,6 +88,9 @@ func (rm *IDPConfigReadModel) reduceConfigChangedEvent(e *idpconfig.IDPConfigCha if e.StylingType != nil && e.StylingType.Valid() { rm.StylingType = *e.StylingType } + if e.AutoRegister != nil { + rm.AutoRegister = *e.AutoRegister + } } func (rm *IDPConfigReadModel) reduceConfigStateChanged(configID string, state domain.IDPConfigState) { diff --git a/internal/repository/iam/idp_config.go b/internal/repository/iam/idp_config.go index 1f2226ece..6152c13ad 100644 --- a/internal/repository/iam/idp_config.go +++ b/internal/repository/iam/idp_config.go @@ -28,6 +28,7 @@ func NewIDPConfigAddedEvent( name string, configType domain.IDPConfigType, stylingType domain.IDPConfigStylingType, + autoRegister bool, ) *IDPConfigAddedEvent { return &IDPConfigAddedEvent{ @@ -41,6 +42,7 @@ func NewIDPConfigAddedEvent( name, configType, stylingType, + autoRegister, ), } } diff --git a/internal/repository/idpconfig/idp_config.go b/internal/repository/idpconfig/idp_config.go index e022503e6..5ce71f4ba 100644 --- a/internal/repository/idpconfig/idp_config.go +++ b/internal/repository/idpconfig/idp_config.go @@ -29,10 +29,11 @@ func NewRemoveIDPConfigNameUniqueConstraint(idpConfigName, resourceOwner string) type IDPConfigAddedEvent struct { eventstore.BaseEvent `json:"-"` - ConfigID string `json:"idpConfigId"` - Name string `json:"name,omitempty"` - Typ domain.IDPConfigType `json:"idpType,omitempty"` - StylingType domain.IDPConfigStylingType `json:"stylingType,omitempty"` + ConfigID string `json:"idpConfigId"` + Name string `json:"name,omitempty"` + Typ domain.IDPConfigType `json:"idpType,omitempty"` + StylingType domain.IDPConfigStylingType `json:"stylingType,omitempty"` + AutoRegister bool `json:"autoRegister,omitempty"` } func NewIDPConfigAddedEvent( @@ -41,13 +42,15 @@ func NewIDPConfigAddedEvent( name string, configType domain.IDPConfigType, stylingType domain.IDPConfigStylingType, + autoRegister bool, ) *IDPConfigAddedEvent { return &IDPConfigAddedEvent{ - BaseEvent: *base, - ConfigID: configID, - Name: name, - StylingType: stylingType, - Typ: configType, + BaseEvent: *base, + ConfigID: configID, + Name: name, + StylingType: stylingType, + Typ: configType, + AutoRegister: autoRegister, } } @@ -75,10 +78,11 @@ func IDPConfigAddedEventMapper(event *repository.Event) (eventstore.EventReader, type IDPConfigChangedEvent struct { eventstore.BaseEvent `json:"-"` - ConfigID string `json:"idpConfigId"` - Name *string `json:"name,omitempty"` - StylingType *domain.IDPConfigStylingType `json:"stylingType,omitempty"` - oldName string `json:"-"` + ConfigID string `json:"idpConfigId"` + Name *string `json:"name,omitempty"` + StylingType *domain.IDPConfigStylingType `json:"stylingType,omitempty"` + AutoRegister *bool `json:"autoRegister,omitempty"` + oldName string `json:"-"` } func (e *IDPConfigChangedEvent) Data() interface{} { @@ -129,6 +133,12 @@ func ChangeStyleType(styleType domain.IDPConfigStylingType) func(*IDPConfigChang } } +func ChangeAutoRegister(autoRegister bool) func(*IDPConfigChangedEvent) { + return func(e *IDPConfigChangedEvent) { + e.AutoRegister = &autoRegister + } +} + func IDPConfigChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) { e := &IDPConfigChangedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), diff --git a/internal/repository/org/idp_config.go b/internal/repository/org/idp_config.go index 0e787872f..9b17c5eb1 100644 --- a/internal/repository/org/idp_config.go +++ b/internal/repository/org/idp_config.go @@ -28,6 +28,7 @@ func NewIDPConfigAddedEvent( name string, configType domain.IDPConfigType, stylingType domain.IDPConfigStylingType, + autoRegister bool, ) *IDPConfigAddedEvent { return &IDPConfigAddedEvent{ @@ -41,6 +42,7 @@ func NewIDPConfigAddedEvent( name, configType, stylingType, + autoRegister, ), } } diff --git a/internal/ui/login/handler/external_login_handler.go b/internal/ui/login/handler/external_login_handler.go index 472cf8dc6..2221d58a2 100644 --- a/internal/ui/login/handler/external_login_handler.go +++ b/internal/ui/login/handler/external_login_handler.go @@ -160,7 +160,16 @@ func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.R if errors.IsNotFound(err) { err = nil } - l.renderExternalNotFoundOption(w, r, authReq, err) + if !idpConfig.AutoRegister { + l.renderExternalNotFoundOption(w, r, authReq, err) + return + } + authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, userAgentID) + if err != nil { + l.renderExternalNotFoundOption(w, r, authReq, err) + return + } + l.handleAutoRegister(w, r, authReq) return } l.renderNextStep(w, r, authReq) diff --git a/internal/ui/login/handler/external_register_handler.go b/internal/ui/login/handler/external_register_handler.go index 6ff1c0211..bd49fc939 100644 --- a/internal/ui/login/handler/external_register_handler.go +++ b/internal/ui/login/handler/external_register_handler.go @@ -119,7 +119,27 @@ func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Reques return } user, externalIDP := l.mapTokenToLoginHumanAndExternalIDP(orgIamPolicy, tokens, idpConfig) - l.renderExternalRegisterOverview(w, r, authReq, orgIamPolicy, user, externalIDP, nil) + if !idpConfig.AutoRegister { + l.renderExternalRegisterOverview(w, r, authReq, orgIamPolicy, user, externalIDP, nil) + return + } + l.registerExternalUser(w, r, authReq, iam, user, externalIDP) +} + +func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, iam *iam_model.IAM, user *domain.Human, externalIDP *domain.ExternalIDP) { + resourceOwner := iam.GlobalOrgID + memberRoles := []string{domain.RoleOrgProjectCreator} + + if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner { + memberRoles = nil + resourceOwner = authReq.RequestedOrgID + } + _, err := l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, user, externalIDP, memberRoles) + if err != nil { + l.renderRegisterOption(w, r, authReq, err) + return + } + l.renderNextStep(w, r, authReq) } func (l *Login) renderExternalRegisterOverview(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgIAMPolicy *iam_model.OrgIAMPolicyView, human *domain.Human, idp *domain.ExternalIDP, err error) { diff --git a/internal/ui/login/handler/renderer.go b/internal/ui/login/handler/renderer.go index 43b3891d9..ff24ca187 100644 --- a/internal/ui/login/handler/renderer.go +++ b/internal/ui/login/handler/renderer.go @@ -223,7 +223,7 @@ func (l *Login) renderNextStep(w http.ResponseWriter, r *http.Request, authReq * userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) authReq, err := l.authRepo.AuthRequestByID(r.Context(), authReq.ID, userAgentID) if err != nil { - l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(err, "APP-sio0W", "could not get authreq")) + l.renderInternalError(w, r, authReq, err) return } if len(authReq.PossibleSteps) == 0 { @@ -257,6 +257,8 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq * l.renderRegisterOption(w, r, authReq, nil) case *domain.SelectUserStep: l.renderUserSelection(w, r, authReq, step) + case *domain.RedirectToExternalIDPStep: + l.handleIDP(w, r, authReq, authReq.SelectedIDPConfigID) case *domain.InitPasswordStep: l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) case *domain.PasswordStep: diff --git a/migrations/cockroach/V1.70__idp.sql b/migrations/cockroach/V1.70__idp.sql new file mode 100644 index 000000000..798c2696a --- /dev/null +++ b/migrations/cockroach/V1.70__idp.sql @@ -0,0 +1,3 @@ +ALTER TABLE management.idp_configs ADD COLUMN auto_register BOOLEAN; +ALTER TABLE adminapi.idp_configs ADD COLUMN auto_register BOOLEAN; +ALTER TABLE auth.idp_configs ADD COLUMN auto_register BOOLEAN; diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 6407ef007..c1b105865 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -2428,6 +2428,7 @@ message AddOIDCIDPRequest { description: "definition which field is mapped to the email of the user"; } ]; + bool auto_register = 9; } message AddOIDCIDPResponse { @@ -2458,6 +2459,7 @@ message UpdateIDPRequest { description: "some identity providers specify the styling of the button to their login"; } ]; + bool auto_register = 4; } message UpdateIDPResponse { diff --git a/proto/zitadel/idp.proto b/proto/zitadel/idp.proto index d6fb1a688..2d7b28c4d 100644 --- a/proto/zitadel/idp.proto +++ b/proto/zitadel/idp.proto @@ -38,6 +38,7 @@ message IDP { oneof config { OIDCConfig oidc_config = 7; } + bool auto_register = 8; } message IDPUserLink { diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index ffe0ab1e2..c9a90e01d 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -4884,6 +4884,7 @@ message AddOrgOIDCIDPRequest { description: "definition which field is mapped to the email of the user"; } ]; + bool auto_register = 9; } message AddOrgOIDCIDPResponse { @@ -4928,6 +4929,7 @@ message UpdateOrgIDPRequest { description: "some identity providers specify the styling of the button to their login"; } ]; + bool auto_register = 4; } message UpdateOrgIDPResponse {