- remove_circle
+ remove_circle
{{idp.name}}
{{ 'IDP.TYPE' | translate }}: {{ 'IDP.TYPES.'+idp.type | translate }}
{{ 'IDP.ID' | translate }}: {{idp.idpConfigId}}
-
- {{ 'ACTIONS.SAVE' | translate }}
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 "ExternalNotFoundOption.Link"}}
{{t "ExternalNotFoundOption.AutoRegister"}}
-
- {{t "Actions.Back"}}
-
+
{{t "Actions.Back"}}
{{template "error-message" .}}