mirror of
https://github.com/zitadel/zitadel.git
synced 2025-06-07 23:18:33 +00:00
fix(actions): Linking external account doesn't trigger flow "External Authentication" on "Post Authentication" on first login (#9397)
# Which Problems Are Solved When logging in using exeternal idp to Zitadel using SAML with action setup to override existing Zitadel account attributes (first name/last name/display name ect) with that of external linked idp account as described here: https://zitadel.com/docs/guides/integrate/identity-providers/azure-ad-saml#add-action-to-map-user-attributes, does not happen until the next time the user logs in using the external idp # Additional Context - Closes https://github.com/zitadel/zitadel/issues/9133 --------- Co-authored-by: Iraq Jaber <IraqJaber@gmail.com> Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
parent
007c96d54a
commit
b0fa974419
@ -472,21 +472,25 @@ func (l *Login) handleExternalUserAuthenticated(
|
|||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// if a user was linked, we don't want to do any more renderings
|
||||||
|
var userLinked bool
|
||||||
// if action is done and no user linked then link or register
|
// if action is done and no user linked then link or register
|
||||||
if zerrors.IsNotFound(externalErr) {
|
if zerrors.IsNotFound(externalErr) {
|
||||||
l.externalUserNotExisting(w, r, authReq, provider, externalUser, externalUserChange)
|
userLinked = l.createOrLinkUser(w, r, authReq, provider, externalUser, externalUserChange)
|
||||||
return
|
if !userLinked {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if provider.IsAutoUpdate || externalUserChange {
|
if provider.IsAutoUpdate || externalUserChange {
|
||||||
err = l.updateExternalUser(r.Context(), authReq, externalUser)
|
err = l.updateExternalUser(r.Context(), authReq, externalUser)
|
||||||
if err != nil {
|
if err != nil && !userLinked {
|
||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(externalUser.Metadatas) > 0 {
|
if len(externalUser.Metadatas) > 0 {
|
||||||
_, err = l.command.BulkSetUserMetadata(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, externalUser.Metadatas...)
|
_, err = l.command.BulkSetUserMetadata(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, externalUser.Metadatas...)
|
||||||
if err != nil {
|
if err != nil && !userLinked {
|
||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -498,12 +502,12 @@ func (l *Login) handleExternalUserAuthenticated(
|
|||||||
// The decision, which information will be checked is based on the IdP template option.
|
// The decision, which information will be checked is based on the IdP template option.
|
||||||
// The function returns a boolean whether a user was found or not.
|
// The function returns a boolean whether a user was found or not.
|
||||||
// If single a user was found, it will be automatically linked.
|
// If single a user was found, it will be automatically linked.
|
||||||
func (l *Login) checkAutoLinking(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, provider *query.IDPTemplate, externalUser *domain.ExternalUser) bool {
|
func (l *Login) checkAutoLinking(r *http.Request, authReq *domain.AuthRequest, provider *query.IDPTemplate, externalUser *domain.ExternalUser) (bool, error) {
|
||||||
queries := make([]query.SearchQuery, 0, 2)
|
queries := make([]query.SearchQuery, 0, 2)
|
||||||
switch provider.AutoLinking {
|
switch provider.AutoLinking {
|
||||||
case domain.AutoLinkingOptionUnspecified:
|
case domain.AutoLinkingOptionUnspecified:
|
||||||
// is auto linking is disable, we shouldn't even get here, but in case we do we can directly return
|
// is auto linking is disable, we shouldn't even get here, but in case we do we can directly return
|
||||||
return false
|
return false, nil
|
||||||
case domain.AutoLinkingOptionUsername:
|
case domain.AutoLinkingOptionUsername:
|
||||||
// if we're checking for usernames there are to options:
|
// if we're checking for usernames there are to options:
|
||||||
//
|
//
|
||||||
@ -512,22 +516,24 @@ func (l *Login) checkAutoLinking(w http.ResponseWriter, r *http.Request, authReq
|
|||||||
if authReq.RequestedOrgID == "" {
|
if authReq.RequestedOrgID == "" {
|
||||||
user, err := l.query.GetNotifyUserByLoginName(r.Context(), false, externalUser.PreferredUsername)
|
user, err := l.query.GetNotifyUserByLoginName(r.Context(), false, externalUser.PreferredUsername)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
l.autoLinkUser(w, r, authReq, user)
|
if err = l.autoLinkUser(r, authReq, user); err != nil {
|
||||||
return true
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
// If a specific org has been requested, we'll check the provided username against usernames (of that org).
|
// If a specific org has been requested, we'll check the provided username against usernames (of that org).
|
||||||
usernameQuery, err := query.NewUserUsernameSearchQuery(externalUser.PreferredUsername, query.TextEqualsIgnoreCase)
|
usernameQuery, err := query.NewUserUsernameSearchQuery(externalUser.PreferredUsername, query.TextEqualsIgnoreCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
queries = append(queries, usernameQuery)
|
queries = append(queries, usernameQuery)
|
||||||
case domain.AutoLinkingOptionEmail:
|
case domain.AutoLinkingOptionEmail:
|
||||||
// Email will always be checked against verified email addresses.
|
// Email will always be checked against verified email addresses.
|
||||||
emailQuery, err := query.NewUserVerifiedEmailSearchQuery(string(externalUser.Email))
|
emailQuery, err := query.NewUserVerifiedEmailSearchQuery(string(externalUser.Email))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
queries = append(queries, emailQuery)
|
queries = append(queries, emailQuery)
|
||||||
}
|
}
|
||||||
@ -535,38 +541,39 @@ func (l *Login) checkAutoLinking(w http.ResponseWriter, r *http.Request, authReq
|
|||||||
if authReq.RequestedOrgID != "" {
|
if authReq.RequestedOrgID != "" {
|
||||||
resourceOwnerQuery, err := query.NewUserResourceOwnerSearchQuery(authReq.RequestedOrgID, query.TextEquals)
|
resourceOwnerQuery, err := query.NewUserResourceOwnerSearchQuery(authReq.RequestedOrgID, query.TextEquals)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
queries = append(queries, resourceOwnerQuery)
|
queries = append(queries, resourceOwnerQuery)
|
||||||
}
|
}
|
||||||
user, err := l.query.GetNotifyUser(r.Context(), false, queries...)
|
user, err := l.query.GetNotifyUser(r.Context(), false, queries...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
l.autoLinkUser(w, r, authReq, user)
|
if err = l.autoLinkUser(r, authReq, user); err != nil {
|
||||||
return true
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Login) autoLinkUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, user *query.NotifyUser) {
|
func (l *Login) autoLinkUser(r *http.Request, authReq *domain.AuthRequest, user *query.NotifyUser) error {
|
||||||
if err := l.authRepo.SelectUser(r.Context(), authReq.ID, user.ID, authReq.AgentID); err != nil {
|
if err := l.authRepo.SelectUser(r.Context(), authReq.ID, user.ID, authReq.AgentID); err != nil {
|
||||||
l.renderError(w, r, authReq, err)
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if err := l.authRepo.LinkExternalUsers(r.Context(), authReq.ID, authReq.AgentID, domain.BrowserInfoFromRequest(r)); err != nil {
|
if err := l.authRepo.LinkExternalUsers(r.Context(), authReq.ID, authReq.AgentID, domain.BrowserInfoFromRequest(r)); err != nil {
|
||||||
l.renderError(w, r, authReq, err)
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
l.renderNextStep(w, r, authReq)
|
authReq.UserID = user.ID
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// externalUserNotExisting is called if an externalAuthentication couldn't find a corresponding externalID
|
// createOrLinkUser is called if an externalAuthentication couldn't find a corresponding externalID
|
||||||
// possible solutions are:
|
// possible solutions are:
|
||||||
//
|
//
|
||||||
// * auto creation
|
// * auto creation
|
||||||
// * external not found overview:
|
// * external not found overview:
|
||||||
// - creation by user
|
// - creation by user
|
||||||
// - linking to existing user
|
// - linking to existing user
|
||||||
func (l *Login) externalUserNotExisting(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, provider *query.IDPTemplate, externalUser *domain.ExternalUser, changed bool) {
|
func (l *Login) createOrLinkUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, provider *query.IDPTemplate, externalUser *domain.ExternalUser, changed bool) (userLinked bool) {
|
||||||
resourceOwner := determineResourceOwner(r.Context(), authReq)
|
resourceOwner := determineResourceOwner(r.Context(), authReq)
|
||||||
orgIAMPolicy, err := l.getOrgDomainPolicy(r, resourceOwner)
|
orgIAMPolicy, err := l.getOrgDomainPolicy(r, resourceOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -577,8 +584,13 @@ func (l *Login) externalUserNotExisting(w http.ResponseWriter, r *http.Request,
|
|||||||
human, idpLink, _ := mapExternalUserToLoginUser(externalUser, orgIAMPolicy.UserLoginMustBeDomain)
|
human, idpLink, _ := mapExternalUserToLoginUser(externalUser, orgIAMPolicy.UserLoginMustBeDomain)
|
||||||
// let's check if auto-linking is enabled and if the user would be found by the corresponding option
|
// let's check if auto-linking is enabled and if the user would be found by the corresponding option
|
||||||
if provider.AutoLinking != domain.AutoLinkingOptionUnspecified {
|
if provider.AutoLinking != domain.AutoLinkingOptionUnspecified {
|
||||||
if l.checkAutoLinking(w, r, authReq, provider, externalUser) {
|
userLinked, err = l.checkAutoLinking(r, authReq, provider, externalUser)
|
||||||
return
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if userLinked {
|
||||||
|
return userLinked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -602,6 +614,7 @@ func (l *Login) externalUserNotExisting(w http.ResponseWriter, r *http.Request,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
l.autoCreateExternalUser(w, r, authReq)
|
l.autoCreateExternalUser(w, r, authReq)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// autoCreateExternalUser takes the externalUser and creates it automatically (without user interaction)
|
// autoCreateExternalUser takes the externalUser and creates it automatically (without user interaction)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user