diff --git a/console/src/app/modules/idp-table/idp-table.component.html b/console/src/app/modules/idp-table/idp-table.component.html index f579dba7ec..7e6badec34 100644 --- a/console/src/app/modules/idp-table/idp-table.component.html +++ b/console/src/app/modules/idp-table/idp-table.component.html @@ -2,18 +2,19 @@ [timestamp]="idpResult?.viewTimestamp" [selection]="selection"> - + add{{ 'ACTIONS.NEW' | translate }} @@ -82,4 +83,4 @@ - \ No newline at end of file + diff --git a/console/src/app/modules/policies/login-policy/login-policy.component.html b/console/src/app/modules/policies/login-policy/login-policy.component.html index bbea94cdda..3e0afe0cec 100644 --- a/console/src/app/modules/policies/login-policy/login-policy.component.html +++ b/console/src/app/modules/policies/login-policy/login-policy.component.html @@ -1,10 +1,10 @@ - + -->
@@ -12,19 +12,20 @@ {{'ORG.POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate}} + [(ngModel)]="loginData.allowUsernamePassword" [disabled]="serviceType==PolicyComponentServiceType.MGMT">
{{'ORG.POLICY.DATA.ALLOWREGISTER' | translate}} - +
{{'ORG.POLICY.DATA.ALLOWEXTERNALIDP' | translate}} -
@@ -34,18 +35,18 @@
- remove_circle + remove_circle {{idp.name}} {{ 'IDP.TYPE' | translate }}: {{ 'IDP.TYPES.'+idp.type | translate }} {{ 'IDP.ID' | translate }}: {{idp.idpConfigId}}
-
+
add
-
diff --git a/internal/auth/repository/auth_request.go b/internal/auth/repository/auth_request.go index 8779b7fb7c..f85414f391 100644 --- a/internal/auth/repository/auth_request.go +++ b/internal/auth/repository/auth_request.go @@ -23,4 +23,6 @@ type AuthRequestRepository interface { VerifyMfaOTP(ctx context.Context, agentID, authRequestID, code, userAgentID string, info *model.BrowserInfo) error LinkExternalUsers(ctx context.Context, authReqID, userAgentID string) error AutoRegisterExternalUser(ctx context.Context, user *user_model.User, externalIDP *user_model.ExternalIDP, member *org_model.OrgMember, authReqID, userAgentID, resourceOwner string) error + ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error + GetOrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error) } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 73e86fa798..c64cd4e687 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -74,6 +74,7 @@ type userEventProvider interface { type orgViewProvider interface { OrgByID(string) (*org_view_model.OrgView, error) + OrgByPrimaryDomain(string) (*org_view_model.OrgView, error) } func (repo *AuthRequestRepo) Health(ctx context.Context) error { @@ -231,6 +232,16 @@ func (repo *AuthRequestRepo) LinkExternalUsers(ctx context.Context, authReqID, u return repo.AuthRequests.UpdateAuthRequest(ctx, request) } +func (repo *AuthRequestRepo) ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error { + request, err := repo.getAuthRequest(ctx, authReqID, userAgentID) + if err != nil { + return err + } + request.LinkingUsers = nil + request.SelectedIDPConfigID = "" + return repo.AuthRequests.UpdateAuthRequest(ctx, request) +} + func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *user_model.User, externalIDP *user_model.ExternalIDP, orgMember *org_model.OrgMember, authReqID, userAgentID, resourceOwner string) error { request, err := repo.getAuthRequest(ctx, authReqID, userAgentID) if err != nil { @@ -317,7 +328,14 @@ func (repo *AuthRequestRepo) getLoginPolicyAndIDPProviders(ctx context.Context, func (repo *AuthRequestRepo) fillLoginPolicy(ctx context.Context, request *model.AuthRequest) error { orgID := request.UserOrgID if orgID == "" { - orgID = request.GetScopeOrgID() + primaryDomain := request.GetScopeOrgPrimaryDomain() + if primaryDomain != "" { + org, err := repo.GetOrgByPrimaryDomain(primaryDomain) + if err != nil { + return err + } + orgID = org.ID + } } if orgID == "" { orgID = repo.IAMID @@ -335,7 +353,16 @@ func (repo *AuthRequestRepo) fillLoginPolicy(ctx context.Context, request *model } func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *model.AuthRequest, loginName string) (err error) { - orgID := request.GetScopeOrgID() + primaryDomain := request.GetScopeOrgPrimaryDomain() + orgID := "" + if primaryDomain != "" { + org, err := repo.GetOrgByPrimaryDomain(primaryDomain) + if err != nil { + return err + } + orgID = org.ID + } + user := new(user_view_model.UserView) if orgID != "" { user, err = repo.View.UserByLoginNameAndResourceOwner(loginName, orgID) @@ -356,6 +383,14 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *model. return nil } +func (repo AuthRequestRepo) GetOrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error) { + org, err := repo.OrgViewProvider.OrgByPrimaryDomain(primaryDomain) + if err != nil { + return nil, err + } + return org_view_model.OrgToModel(org), nil +} + func (repo AuthRequestRepo) checkLoginPolicyWithResourceOwner(ctx context.Context, request *model.AuthRequest, user *user_view_model.UserView) error { loginPolicy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, user.ResourceOwner) if err != nil { @@ -386,10 +421,15 @@ func (repo *AuthRequestRepo) checkSelectedExternalIDP(request *model.AuthRequest } func (repo *AuthRequestRepo) checkExternalUserLogin(request *model.AuthRequest, idpConfigID, externalUserID string) (err error) { - orgID := request.GetScopeOrgID() + primaryDomain := request.GetScopeOrgPrimaryDomain() externalIDP := new(user_view_model.ExternalIDPView) - if orgID != "" { - externalIDP, err = repo.View.ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(externalUserID, idpConfigID, orgID) + org := new(org_model.OrgView) + if primaryDomain != "" { + org, err = repo.GetOrgByPrimaryDomain(primaryDomain) + if err != nil { + return err + } + externalIDP, err = repo.View.ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(externalUserID, idpConfigID, org.ID) } else { externalIDP, err = repo.View.ExternalIDPByExternalUserIDAndIDPConfigID(externalUserID, idpConfigID) } @@ -435,7 +475,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR return nil, err } - if request.SelectedIDPConfigID == "" { + if request.SelectedIDPConfigID == "" || (request.SelectedIDPConfigID != "" && request.LinkingUsers != nil && len(request.LinkingUsers) > 0) { if user.InitRequired { return append(steps, &model.InitUserStep{PasswordSet: user.PasswordSet}), nil } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index 848b615b42..a24dce8217 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -139,12 +139,22 @@ func (m *mockViewOrg) OrgByID(string) (*org_view_model.OrgView, error) { }, nil } +func (m *mockViewOrg) OrgByPrimaryDomain(string) (*org_view_model.OrgView, error) { + return &org_view_model.OrgView{ + State: int32(m.State), + }, nil +} + type mockViewErrOrg struct{} func (m *mockViewErrOrg) OrgByID(string) (*org_view_model.OrgView, error) { return nil, errors.ThrowInternal(nil, "id", "internal error") } +func (m *mockViewErrOrg) OrgByPrimaryDomain(string) (*org_view_model.OrgView, error) { + return nil, errors.ThrowInternal(nil, "id", "internal error") +} + func TestAuthRequestRepo_nextSteps(t *testing.T) { type fields struct { UserEvents *user_event.UserEventstore @@ -582,7 +592,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { nil, }, { - "linking users, link users step", + "linking users, password step", fields{ userSessionViewProvider: &mockViewUserSession{ MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute), @@ -596,6 +606,32 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, MfaSoftwareCheckLifeTime: 18 * time.Hour, }, + args{ + &model.AuthRequest{ + UserID: "UserID", + SelectedIDPConfigID: "IDPConfigID", + LinkingUsers: []*model.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}}, + }, false}, + []model.NextStep{&model.PasswordStep{}}, + nil, + }, + { + "linking users, linking step", + fields{ + userSessionViewProvider: &mockViewUserSession{ + PasswordVerification: time.Now().UTC().Add(-5 * time.Minute), + MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute), + }, + userViewProvider: &mockViewUser{ + PasswordSet: true, + IsEmailVerified: true, + MfaMaxSetUp: int32(model.MfaLevelSoftware), + }, + userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + MfaSoftwareCheckLifeTime: 18 * time.Hour, + PasswordCheckLifeTime: 10 * 24 * time.Hour, + }, args{ &model.AuthRequest{ UserID: "UserID", diff --git a/internal/auth/repository/eventsourcing/handler/org.go b/internal/auth/repository/eventsourcing/handler/org.go index 62ce072258..58bafb6ac0 100644 --- a/internal/auth/repository/eventsourcing/handler/org.go +++ b/internal/auth/repository/eventsourcing/handler/org.go @@ -46,6 +46,17 @@ func (o *Org) Reduce(event *es_models.Event) (err error) { return err } err = org.AppendEvent(event) + case model.OrgDomainPrimarySet: + domain := new(org_model.OrgDomainView) + err = domain.SetData(event) + if err != nil { + return err + } + org, err = o.view.OrgByID(event.AggregateID) + if err != nil { + return err + } + org.Domain = domain.Domain default: return o.view.ProcessedOrgSequence(event.Sequence) } diff --git a/internal/auth/repository/eventsourcing/view/org.go b/internal/auth/repository/eventsourcing/view/org.go index ca33f0cdfd..6383f62398 100644 --- a/internal/auth/repository/eventsourcing/view/org.go +++ b/internal/auth/repository/eventsourcing/view/org.go @@ -15,6 +15,10 @@ func (v *View) OrgByID(orgID string) (*org_model.OrgView, error) { return org_view.OrgByID(v.Db, orgTable, orgID) } +func (v *View) OrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error) { + return org_view.OrgByPrimaryDomain(v.Db, orgTable, primaryDomain) +} + func (v *View) SearchOrgs(req *model.OrgSearchRequest) ([]*org_model.OrgView, uint64, error) { return org_view.SearchOrgs(v.Db, orgTable, req) } diff --git a/internal/auth_request/model/auth_request.go b/internal/auth_request/model/auth_request.go index b1f56accf1..54df88007f 100644 --- a/internal/auth_request/model/auth_request.go +++ b/internal/auth_request/model/auth_request.go @@ -126,12 +126,12 @@ func (a *AuthRequest) SetUserInfo(userID, loginName, displayName, userOrgID stri a.UserOrgID = userOrgID } -func (a *AuthRequest) GetScopeOrgID() string { +func (a *AuthRequest) GetScopeOrgPrimaryDomain() string { switch request := a.Request.(type) { case *AuthRequestOIDC: for _, scope := range request.Scopes { - if strings.HasPrefix(scope, OrgIDScope) { - strings.TrimPrefix(scope, OrgIDScope) + if strings.HasPrefix(scope, OrgDomainPrimaryScope) { + return strings.TrimPrefix(scope, OrgDomainPrimaryScope) } } } diff --git a/internal/auth_request/model/request.go b/internal/auth_request/model/request.go index 5bf1f9a507..e0288ea2a6 100644 --- a/internal/auth_request/model/request.go +++ b/internal/auth_request/model/request.go @@ -19,7 +19,7 @@ const ( ) const ( - OrgIDScope = "urn:zitadel:organisation:id:" + OrgDomainPrimaryScope = "urn:zitadel:org:domain:primary:" ) type AuthRequestOIDC struct { diff --git a/internal/iam/repository/eventsourcing/model/oidc_idp_config.go b/internal/iam/repository/eventsourcing/model/oidc_idp_config.go index ecdda1b32c..efd6a84e1c 100644 --- a/internal/iam/repository/eventsourcing/model/oidc_idp_config.go +++ b/internal/iam/repository/eventsourcing/model/oidc_idp_config.go @@ -27,7 +27,7 @@ func (c *OIDCIDPConfig) Changes(changed *OIDCIDPConfig) map[string]interface{} { if c.ClientID != changed.ClientID { changes["clientId"] = changed.ClientID } - if c.ClientSecret != nil && c.ClientSecret != changed.ClientSecret { + if changed.ClientSecret != nil && c.ClientSecret != changed.ClientSecret { changes["clientSecret"] = changed.ClientSecret } if c.Issuer != changed.Issuer { diff --git a/internal/org/repository/view/org_view.go b/internal/org/repository/view/org_view.go index 0647091035..d1c2214de7 100644 --- a/internal/org/repository/view/org_view.go +++ b/internal/org/repository/view/org_view.go @@ -18,6 +18,16 @@ func OrgByID(db *gorm.DB, table, orgID string) (*model.OrgView, error) { return org, err } +func OrgByPrimaryDomain(db *gorm.DB, table, primaryDomain string) (*model.OrgView, error) { + org := new(model.OrgView) + query := repository.PrepareGetByKey(table, model.OrgSearchKey(org_model.OrgSearchKeyOrgDomain), primaryDomain) + err := query(db, org) + if caos_errs.IsNotFound(err) { + return nil, caos_errs.ThrowNotFound(nil, "VIEW-GEwea", "Errors.Org.NotFound") + } + return org, err +} + func SearchOrgs(db *gorm.DB, table string, req *org_model.OrgSearchRequest) ([]*model.OrgView, uint64, error) { orgs := make([]*model.OrgView, 0) query := repository.PrepareSearchQuery(table, model.OrgSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries}) diff --git a/internal/ui/login/handler/external_login_handler.go b/internal/ui/login/handler/external_login_handler.go index b00673ddc8..6b28bc7c2c 100644 --- a/internal/ui/login/handler/external_login_handler.go +++ b/internal/ui/login/handler/external_login_handler.go @@ -33,6 +33,7 @@ type externalIDPCallbackData struct { type externalNotFoundOptionFormData struct { Link bool `schema:"link"` AutoRegister bool `schema:"autoregister"` + ResetLinking bool `schema:"resetlinking"` } type externalNotFoundOptionData struct { @@ -139,35 +140,52 @@ func (l *Login) handleExternalNotFoundOptionCheck(w http.ResponseWriter, r *http data := new(externalNotFoundOptionFormData) authReq, err := l.getAuthRequestAndParseData(r, data) if err != nil { - l.renderError(w, r, authReq, err) + l.renderExternalNotFoundOption(w, r, authReq, err) return } if data.Link { l.renderLogin(w, r, authReq, nil) return + } else if data.ResetLinking { + userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) + err = l.authRepo.ResetLinkingUsers(r.Context(), authReq.ID, userAgentID) + if err != nil { + l.renderExternalNotFoundOption(w, r, authReq, err) + } + l.handleLogin(w, r) + return } l.handleAutoRegister(w, r, authReq) } func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) { - orgIamPolicy, err := l.getOrgIamPolicy(r, authReq.GetScopeOrgID()) - if err != nil { - l.renderExternalNotFoundOption(w, r, authReq, err) - return - } iam, err := l.authRepo.GetIAM(r.Context()) if err != nil { l.renderExternalNotFoundOption(w, r, authReq, err) return } + resourceOwner := iam.GlobalOrgID member := &org_model.OrgMember{ ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID}, Roles: []string{orgProjectCreatorRole}, } - if authReq.GetScopeOrgID() != iam.GlobalOrgID && authReq.GetScopeOrgID() != "" { - member = nil - resourceOwner = authReq.GetScopeOrgID() + if authReq.GetScopeOrgPrimaryDomain() != "" { + primaryDomain := authReq.GetScopeOrgPrimaryDomain() + org, err := l.authRepo.GetOrgByPrimaryDomain(primaryDomain) + if err != nil { + l.renderExternalNotFoundOption(w, r, authReq, err) + } + if org.ID != iam.GlobalOrgID { + member = nil + resourceOwner = org.ID + } + } + + orgIamPolicy, err := l.getOrgIamPolicy(r, resourceOwner) + if err != nil { + l.renderExternalNotFoundOption(w, r, authReq, err) + return } idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID) @@ -216,7 +234,6 @@ func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.ID } return externalUser } - func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *org_model.OrgIAMPolicy, linkingUser *model.ExternalUser, idpConfig *iam_model.IDPConfigView) (*usr_model.User, *usr_model.ExternalIDP) { username := linkingUser.PreferredUsername switch idpConfig.OIDCUsernameMapping { diff --git a/internal/ui/login/handler/external_register_handler.go b/internal/ui/login/handler/external_register_handler.go index e5cdc8bb04..7e2a07f916 100644 --- a/internal/ui/login/handler/external_register_handler.go +++ b/internal/ui/login/handler/external_register_handler.go @@ -71,11 +71,6 @@ func (l *Login) handleExternalRegisterCallback(w http.ResponseWriter, r *http.Re } func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) { - orgIamPolicy, err := l.getOrgIamPolicy(r, authReq.GetScopeOrgID()) - if err != nil { - l.renderRegisterOption(w, r, authReq, err) - return - } iam, err := l.authRepo.GetIAM(r.Context()) if err != nil { l.renderRegisterOption(w, r, authReq, err) @@ -86,11 +81,24 @@ func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Reques ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID}, Roles: []string{orgProjectCreatorRole}, } - if authReq.GetScopeOrgID() != iam.GlobalOrgID && authReq.GetScopeOrgID() != "" { - member = nil - resourceOwner = authReq.GetScopeOrgID() - } + if authReq.GetScopeOrgPrimaryDomain() != "" { + primaryDomain := authReq.GetScopeOrgPrimaryDomain() + org, err := l.authRepo.GetOrgByPrimaryDomain(primaryDomain) + if err != nil { + l.renderRegisterOption(w, r, authReq, err) + return + } + if org.ID != iam.GlobalOrgID { + member = nil + resourceOwner = org.ID + } + } + orgIamPolicy, err := l.getOrgIamPolicy(r, resourceOwner) + if err != nil { + l.renderRegisterOption(w, r, authReq, err) + return + } user, externalIDP := l.mapTokenToLoginUserAndExternalIDP(orgIamPolicy, tokens, idpConfig) _, err = l.authRepo.RegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, member, resourceOwner) if err != nil { diff --git a/internal/ui/login/handler/register_handler.go b/internal/ui/login/handler/register_handler.go index e8ffdf0508..4ec4eb1bf8 100644 --- a/internal/ui/login/handler/register_handler.go +++ b/internal/ui/login/handler/register_handler.go @@ -71,9 +71,17 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) { ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID}, Roles: []string{orgProjectCreatorRole}, } - if authRequest.GetScopeOrgID() != "" && authRequest.GetScopeOrgID() != iam.GlobalOrgID { - member = nil - resourceOwner = authRequest.GetScopeOrgID() + if authRequest.GetScopeOrgPrimaryDomain() != "" { + primaryDomain := authRequest.GetScopeOrgPrimaryDomain() + org, err := l.authRepo.GetOrgByPrimaryDomain(primaryDomain) + if err != nil { + l.renderRegisterOption(w, r, authRequest, err) + return + } + if org.ID != iam.GlobalOrgID { + member = nil + resourceOwner = org.ID + } } user, err := l.authRepo.Register(setContext(r.Context(), resourceOwner), data.toUserModel(), member, resourceOwner) if err != nil { diff --git a/internal/ui/login/handler/renderer.go b/internal/ui/login/handler/renderer.go index dbba159ac3..839844cc84 100644 --- a/internal/ui/login/handler/renderer.go +++ b/internal/ui/login/handler/renderer.go @@ -292,7 +292,14 @@ func (l *Login) getOrgID(authReq *model.AuthRequest) string { if authReq.Request == nil { return "" } - return authReq.GetScopeOrgID() + primaryDomain := authReq.GetScopeOrgPrimaryDomain() + if primaryDomain != "" { + org, _ := l.authRepo.GetOrgByPrimaryDomain(primaryDomain) + if org != nil { + return org.ID + } + } + return "" } func getRequestID(authReq *model.AuthRequest, r *http.Request) string { diff --git a/internal/ui/login/static/templates/error-message.html b/internal/ui/login/static/templates/error-message.html index 8bee342aa1..323d2d2624 100644 --- a/internal/ui/login/static/templates/error-message.html +++ b/internal/ui/login/static/templates/error-message.html @@ -1,9 +1,7 @@ {{ define "error-message" }} {{if .ErrMessage }} -
-
- {{ if .ErrType }}{{ .ErrType }} - {{end}}{{ .ErrMessage }} -
+
+ {{ if .ErrType }}{{ .ErrType }} - {{end}}{{ .ErrMessage }}
{{end}} {{ end }} \ No newline at end of file diff --git a/internal/ui/login/static/templates/external_not_found_option.html b/internal/ui/login/static/templates/external_not_found_option.html index a56525c689..29510b7c96 100644 --- a/internal/ui/login/static/templates/external_not_found_option.html +++ b/internal/ui/login/static/templates/external_not_found_option.html @@ -15,9 +15,7 @@
- - {{t "Actions.Back"}} - +
{{template "error-message" .}}