diff --git a/internal/api/grpc/admin/idp.go b/internal/api/grpc/admin/idp.go
index 24c3c0e00..515199972 100644
--- a/internal/api/grpc/admin/idp.go
+++ b/internal/api/grpc/admin/idp.go
@@ -152,7 +152,11 @@ func (s *Server) UpdateIDPJWTConfig(ctx context.Context, req *admin_pb.UpdateIDP
}
func (s *Server) GetProviderByID(ctx context.Context, req *admin_pb.GetProviderByIDRequest) (*admin_pb.GetProviderByIDResponse, error) {
- idp, err := s.query.IDPTemplateByIDAndResourceOwner(ctx, true, req.Id, authz.GetInstance(ctx).InstanceID(), false)
+ instanceIDQuery, err := query.NewIDPTemplateResourceOwnerSearchQuery(authz.GetInstance(ctx).InstanceID())
+ if err != nil {
+ return nil, err
+ }
+ idp, err := s.query.IDPTemplateByID(ctx, true, req.Id, false, instanceIDQuery)
if err != nil {
return nil, err
}
diff --git a/internal/api/grpc/idp/converter.go b/internal/api/grpc/idp/converter.go
index b478f1e5d..75a72c9b2 100644
--- a/internal/api/grpc/idp/converter.go
+++ b/internal/api/grpc/idp/converter.go
@@ -84,13 +84,11 @@ func IDPUserLinkToPb(link *query.IDPUserLink) *idp_pb.IDPUserLink {
}
}
-func IDPTypeToPb(idpType domain.IDPConfigType) idp_pb.IDPType {
+func IDPTypeToPb(idpType domain.IDPType) idp_pb.IDPType {
switch idpType {
- case domain.IDPConfigTypeOIDC:
+ case domain.IDPTypeOIDC:
return idp_pb.IDPType_IDP_TYPE_OIDC
- case domain.IDPConfigTypeSAML:
- return idp_pb.IDPType_IDP_TYPE_UNSPECIFIED
- case domain.IDPConfigTypeJWT:
+ case domain.IDPTypeJWT:
return idp_pb.IDPType_IDP_TYPE_JWT
default:
return idp_pb.IDPType_IDP_TYPE_UNSPECIFIED
diff --git a/internal/api/grpc/management/idp.go b/internal/api/grpc/management/idp.go
index df76c0309..571b06c6b 100644
--- a/internal/api/grpc/management/idp.go
+++ b/internal/api/grpc/management/idp.go
@@ -144,7 +144,11 @@ func (s *Server) UpdateOrgIDPJWTConfig(ctx context.Context, req *mgmt_pb.UpdateO
}
func (s *Server) GetProviderByID(ctx context.Context, req *mgmt_pb.GetProviderByIDRequest) (*mgmt_pb.GetProviderByIDResponse, error) {
- idp, err := s.query.IDPTemplateByIDAndResourceOwner(ctx, true, req.Id, authz.GetCtxData(ctx).OrgID, false)
+ orgIDQuery, err := query.NewIDPTemplateResourceOwnerSearchQuery(authz.GetCtxData(ctx).OrgID)
+ if err != nil {
+ return nil, err
+ }
+ idp, err := s.query.IDPTemplateByID(ctx, true, req.Id, false, orgIDQuery)
if err != nil {
return nil, err
}
diff --git a/internal/api/ui/login/custom_action.go b/internal/api/ui/login/custom_action.go
index 316520fbb..43ac22c01 100644
--- a/internal/api/ui/login/custom_action.go
+++ b/internal/api/ui/login/custom_action.go
@@ -13,7 +13,6 @@ import (
"github.com/zitadel/zitadel/internal/actions/object"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
- iam_model "github.com/zitadel/zitadel/internal/iam/model"
)
func (l *Login) runPostExternalAuthenticationActions(
@@ -21,18 +20,13 @@ func (l *Login) runPostExternalAuthenticationActions(
tokens *oidc.Tokens,
authRequest *domain.AuthRequest,
httpRequest *http.Request,
- config *iam_model.IDPConfigView,
authenticationError error,
) (*domain.ExternalUser, error) {
ctx := httpRequest.Context()
resourceOwner := authRequest.RequestedOrgID
if resourceOwner == "" {
- resourceOwner = config.AggregateID
- }
- instance := authz.GetInstance(ctx)
- if resourceOwner == instance.InstanceID() {
- resourceOwner = instance.DefaultOrganisationID()
+ resourceOwner = authz.GetInstance(ctx).DefaultOrganisationID()
}
triggerActions, err := l.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeExternalAuthentication, domain.TriggerTypePostAuthentication, resourceOwner, false)
if err != nil {
diff --git a/internal/api/ui/login/external_login_handler.go b/internal/api/ui/login/external_login_handler.go
deleted file mode 100644
index 35d755705..000000000
--- a/internal/api/ui/login/external_login_handler.go
+++ /dev/null
@@ -1,538 +0,0 @@
-package login
-
-import (
- "context"
- "encoding/base64"
- "net/http"
- "net/url"
- "strings"
- "time"
-
- "github.com/zitadel/logging"
- "github.com/zitadel/oidc/v2/pkg/client/rp"
- "github.com/zitadel/oidc/v2/pkg/oidc"
- "golang.org/x/oauth2"
- "golang.org/x/text/language"
-
- "github.com/zitadel/zitadel/internal/api/authz"
- http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
- "github.com/zitadel/zitadel/internal/crypto"
- "github.com/zitadel/zitadel/internal/domain"
- "github.com/zitadel/zitadel/internal/errors"
- iam_model "github.com/zitadel/zitadel/internal/iam/model"
- "github.com/zitadel/zitadel/internal/query"
-)
-
-const (
- queryIDPConfigID = "idpConfigID"
- tmplExternalNotFoundOption = "externalnotfoundoption"
-)
-
-type externalIDPData struct {
- IDPConfigID string `schema:"idpConfigID"`
-}
-
-type externalIDPCallbackData struct {
- State string `schema:"state"`
- Code string `schema:"code"`
-}
-
-type externalNotFoundOptionFormData struct {
- externalRegisterFormData
- Link bool `schema:"linkbutton"`
- AutoRegister bool `schema:"autoregisterbutton"`
- ResetLinking bool `schema:"resetlinking"`
- TermsConfirm bool `schema:"terms-confirm"`
-}
-
-type externalNotFoundOptionData struct {
- baseData
- externalNotFoundOptionFormData
- ExternalIDPID string
- ExternalIDPUserID string
- ExternalIDPUserDisplayName string
- ShowUsername bool
- ShowUsernameSuffix bool
- OrgRegister bool
- ExternalEmail string
- ExternalEmailVerified bool
- ExternalPhone string
- ExternalPhoneVerified bool
-}
-
-func (l *Login) handleExternalLoginStep(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, selectedIDPConfigID string) {
- for _, idp := range authReq.AllowedExternalIDPs {
- if idp.IDPConfigID == selectedIDPConfigID {
- l.handleIDP(w, r, authReq, selectedIDPConfigID)
- return
- }
- }
- l.renderLogin(w, r, authReq, errors.ThrowInvalidArgument(nil, "VIEW-Fsj7f", "Errors.User.ExternalIDP.NotAllowed"))
-}
-
-func (l *Login) handleExternalLogin(w http.ResponseWriter, r *http.Request) {
- data := new(externalIDPData)
- authReq, err := l.getAuthRequestAndParseData(r, data)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- if authReq == nil {
- l.defaultRedirect(w, r)
- return
- }
- l.handleIDP(w, r, authReq, data.IDPConfigID)
-}
-
-func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, selectedIDPConfigID string) {
- idpConfig, err := l.getIDPConfigByID(r, selectedIDPConfigID)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
- err = l.authRepo.SelectExternalIDP(r.Context(), authReq.ID, idpConfig.IDPConfigID, userAgentID)
- if err != nil {
- l.renderLogin(w, r, authReq, err)
- return
- }
- if !idpConfig.IsOIDC {
- l.handleJWTAuthorize(w, r, authReq, idpConfig)
- return
- }
- l.handleOIDCAuthorize(w, r, authReq, idpConfig, EndpointExternalLoginCallback)
-}
-
-func (l *Login) handleOIDCAuthorize(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) {
- provider, err := l.getRPConfig(r.Context(), idpConfig, callbackEndpoint)
- if err != nil {
- l.renderLogin(w, r, authReq, err)
- return
- }
- http.Redirect(w, r, rp.AuthURL(authReq.ID, provider, rp.WithPrompt(oidc.PromptSelectAccount)), http.StatusFound)
-}
-
-func (l *Login) handleJWTAuthorize(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView) {
- redirect, err := url.Parse(idpConfig.JWTEndpoint)
- if err != nil {
- l.renderLogin(w, r, authReq, err)
- return
- }
- q := redirect.Query()
- q.Set(QueryAuthRequestID, authReq.ID)
- userAgentID, ok := http_mw.UserAgentIDFromCtx(r.Context())
- if !ok {
- l.renderLogin(w, r, authReq, errors.ThrowPreconditionFailed(nil, "LOGIN-dsgg3", "Errors.AuthRequest.UserAgentNotFound"))
- return
- }
- nonce, err := l.idpConfigAlg.Encrypt([]byte(userAgentID))
- if err != nil {
- l.renderLogin(w, r, authReq, err)
- return
- }
- q.Set(queryUserAgentID, base64.RawURLEncoding.EncodeToString(nonce))
- redirect.RawQuery = q.Encode()
- http.Redirect(w, r, redirect.String(), http.StatusFound)
-}
-
-func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Request) {
- data := new(externalIDPCallbackData)
- err := l.getParseData(r, data)
- if err != nil {
- l.renderError(w, r, nil, err)
- return
- }
- userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
- authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- if idpConfig.IsOIDC {
- provider, err := l.getRPConfig(r.Context(), idpConfig, EndpointExternalLoginCallback)
- if err != nil {
- emtpyTokens := &oidc.Tokens{Token: &oauth2.Token{}}
- if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, emtpyTokens, authReq, r, idpConfig, err); actionErr != nil {
- logging.WithError(err).Error("both external user authentication and action post authentication failed")
- }
-
- l.renderLogin(w, r, authReq, err)
- return
- }
- tokens, err := rp.CodeExchange(r.Context(), data.Code, provider)
- if err != nil {
- emtpyTokens := &oidc.Tokens{Token: &oauth2.Token{}}
- if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, emtpyTokens, authReq, r, idpConfig, err); actionErr != nil {
- logging.WithError(err).Error("both external user authentication and action post authentication failed")
- }
-
- l.renderLogin(w, r, authReq, err)
- return
- }
- l.handleExternalUserAuthenticated(w, r, authReq, idpConfig, userAgentID, tokens)
- return
- }
-
- err = errors.ThrowPreconditionFailed(nil, "RP-asff2", "Errors.ExternalIDP.IDPTypeNotImplemented")
- emtpyTokens := &oidc.Tokens{Token: &oauth2.Token{}}
- if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, emtpyTokens, authReq, r, idpConfig, err); actionErr != nil {
- logging.WithError(err).Error("both external user authentication and action post authentication failed")
- }
-
- l.renderError(w, r, authReq, err)
-}
-
-func (l *Login) getRPConfig(ctx context.Context, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) (rp.RelyingParty, error) {
- oidcClientSecret, err := crypto.DecryptString(idpConfig.OIDCClientSecret, l.idpConfigAlg)
- if err != nil {
- return nil, err
- }
- if idpConfig.OIDCIssuer != "" {
- return rp.NewRelyingPartyOIDC(idpConfig.OIDCIssuer, idpConfig.OIDCClientID, oidcClientSecret, l.baseURL(ctx)+callbackEndpoint, idpConfig.OIDCScopes, rp.WithVerifierOpts(rp.WithIssuedAtOffset(3*time.Second)))
- }
- if idpConfig.OAuthAuthorizationEndpoint == "" || idpConfig.OAuthTokenEndpoint == "" {
- return nil, errors.ThrowPreconditionFailed(nil, "RP-4n0fs", "Errors.IdentityProvider.InvalidConfig")
- }
- oauth2Config := &oauth2.Config{
- ClientID: idpConfig.OIDCClientID,
- ClientSecret: oidcClientSecret,
- Endpoint: oauth2.Endpoint{
- AuthURL: idpConfig.OAuthAuthorizationEndpoint,
- TokenURL: idpConfig.OAuthTokenEndpoint,
- },
- RedirectURL: l.baseURL(ctx) + callbackEndpoint,
- Scopes: idpConfig.OIDCScopes,
- }
- return rp.NewRelyingPartyOAuth(oauth2Config, rp.WithVerifierOpts(rp.WithIssuedAtOffset(3*time.Second)))
-}
-
-func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) {
- externalUser := l.mapTokenToLoginUser(tokens, idpConfig)
- externalUser, err := l.runPostExternalAuthenticationActions(externalUser, tokens, authReq, r, idpConfig, nil)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
-
- err = l.authRepo.CheckExternalUserLogin(setContext(r.Context(), ""), authReq.ID, userAgentID, externalUser, domain.BrowserInfoFromRequest(r))
- if err != nil {
- if errors.IsNotFound(err) {
- err = nil
- }
- resourceOwner := authz.GetInstance(r.Context()).DefaultOrganisationID()
-
- if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner {
- resourceOwner = authReq.RequestedOrgID
- }
-
- orgIAMPolicy, err := l.getOrgDomainPolicy(r, resourceOwner)
- if err != nil {
- l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, err)
- return
- }
-
- human, idpLinking, _ := l.mapExternalUserToLoginUser(orgIAMPolicy, externalUser, idpConfig)
- if !idpConfig.AutoRegister {
- l.renderExternalNotFoundOption(w, r, authReq, orgIAMPolicy, human, idpLinking, err)
- return
- }
- authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, userAgentID)
- if err != nil {
- l.renderExternalNotFoundOption(w, r, authReq, orgIAMPolicy, human, idpLinking, err)
- return
- }
- l.handleAutoRegister(w, r, authReq, false)
- return
- }
- if len(externalUser.Metadatas) > 0 {
- authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, userAgentID)
- if err != nil {
- return
- }
- _, err = l.command.BulkSetUserMetadata(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, externalUser.Metadatas...)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- }
- l.renderNextStep(w, r, authReq)
-}
-
-func (l *Login) renderExternalNotFoundOption(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgIAMPolicy *query.DomainPolicy, human *domain.Human, externalIDP *domain.UserIDPLink, err error) {
- var errID, errMessage string
- if err != nil {
- errID, errMessage = l.getErrorMessage(r, err)
- }
- if orgIAMPolicy == nil {
- resourceOwner := authz.GetInstance(r.Context()).DefaultOrganisationID()
-
- if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner {
- resourceOwner = authReq.RequestedOrgID
- }
-
- orgIAMPolicy, err = l.getOrgDomainPolicy(r, resourceOwner)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
-
- }
-
- if human == nil || externalIDP == nil {
- idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- linkingUser := authReq.LinkingUsers[len(authReq.LinkingUsers)-1]
- human, externalIDP, _ = l.mapExternalUserToLoginUser(orgIAMPolicy, linkingUser, idpConfig)
- }
-
- var resourceOwner string
- if authReq != nil {
- resourceOwner = authReq.RequestedOrgID
- }
- if resourceOwner == "" {
- resourceOwner = authz.GetInstance(r.Context()).DefaultOrganisationID()
- }
- labelPolicy, err := l.getLabelPolicy(r, resourceOwner)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
-
- translator := l.getTranslator(r.Context(), authReq)
- data := externalNotFoundOptionData{
- baseData: l.getBaseData(r, authReq, "ExternalNotFound.Title", "ExternalNotFound.Description", errID, errMessage),
- externalNotFoundOptionFormData: externalNotFoundOptionFormData{
- externalRegisterFormData: externalRegisterFormData{
- Email: human.EmailAddress,
- Username: human.Username,
- Firstname: human.FirstName,
- Lastname: human.LastName,
- Nickname: human.NickName,
- Language: human.PreferredLanguage.String(),
- },
- },
- ExternalIDPID: externalIDP.IDPConfigID,
- ExternalIDPUserID: externalIDP.ExternalUserID,
- ExternalIDPUserDisplayName: externalIDP.DisplayName,
- ExternalEmail: human.EmailAddress,
- ExternalEmailVerified: human.IsEmailVerified,
- ShowUsername: orgIAMPolicy.UserLoginMustBeDomain,
- ShowUsernameSuffix: !labelPolicy.HideLoginNameSuffix,
- OrgRegister: orgIAMPolicy.UserLoginMustBeDomain,
- }
- if human.Phone != nil {
- data.Phone = human.PhoneNumber
- data.ExternalPhone = human.PhoneNumber
- data.ExternalPhoneVerified = human.IsPhoneVerified
- }
- funcs := map[string]interface{}{
- "selectedLanguage": func(l string) bool {
- return data.Language == l
- },
- }
- l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplExternalNotFoundOption], data, funcs)
-}
-
-func (l *Login) handleExternalNotFoundOptionCheck(w http.ResponseWriter, r *http.Request) {
- data := new(externalNotFoundOptionFormData)
- authReq, err := l.getAuthRequestAndParseData(r, data)
- if err != nil {
- l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, 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, nil, nil, nil, err)
- }
- l.handleLogin(w, r)
- return
- }
- l.handleAutoRegister(w, r, authReq, true)
-}
-
-func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userNotFound bool) {
- resourceOwner := authz.GetInstance(r.Context()).DefaultOrganisationID()
-
- if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner {
- resourceOwner = authReq.RequestedOrgID
- }
-
- orgIamPolicy, err := l.getOrgDomainPolicy(r, resourceOwner)
- if err != nil {
- l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, err)
- return
- }
-
- idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
- if err != nil {
- l.renderExternalNotFoundOption(w, r, authReq, orgIamPolicy, nil, nil, err)
- return
- }
-
- userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
- if len(authReq.LinkingUsers) == 0 {
- l.renderError(w, r, authReq, errors.ThrowPreconditionFailed(nil, "LOGIN-asfg3", "Errors.ExternalIDP.NoExternalUserData"))
- return
- }
-
- linkingUser := authReq.LinkingUsers[len(authReq.LinkingUsers)-1]
- if userNotFound {
- data := new(externalNotFoundOptionFormData)
- err := l.getParseData(r, data)
- if err != nil {
- l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, err)
- return
- }
- linkingUser = l.mapExternalNotFoundOptionFormDataToLoginUser(data)
- }
-
- user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, linkingUser, idpConfig)
-
- user, metadata, err = l.runPreCreationActions(authReq, r, user, metadata, resourceOwner, domain.FlowTypeExternalAuthentication)
- if err != nil {
- l.renderExternalNotFoundOption(w, r, authReq, orgIamPolicy, nil, nil, err)
- return
- }
- err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, nil, authReq.ID, userAgentID, resourceOwner, metadata, domain.BrowserInfoFromRequest(r))
- if err != nil {
- l.renderExternalNotFoundOption(w, r, authReq, orgIamPolicy, user, externalIDP, err)
- return
- }
- authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- userGrants, err := l.runPostCreationActions(authReq.UserID, authReq, r, resourceOwner, domain.FlowTypeExternalAuthentication)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- err = l.appendUserGrants(r.Context(), userGrants, resourceOwner)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- l.renderNextStep(w, r, authReq)
-}
-
-func (l *Login) mapExternalNotFoundOptionFormDataToLoginUser(formData *externalNotFoundOptionFormData) *domain.ExternalUser {
- isEmailVerified := formData.ExternalEmailVerified && formData.Email == formData.ExternalEmail
- isPhoneVerified := formData.ExternalPhoneVerified && formData.Phone == formData.ExternalPhone
- return &domain.ExternalUser{
- IDPConfigID: formData.ExternalIDPConfigID,
- ExternalUserID: formData.ExternalIDPExtUserID,
- PreferredUsername: formData.Username,
- DisplayName: formData.Email,
- FirstName: formData.Firstname,
- LastName: formData.Lastname,
- NickName: formData.Nickname,
- Email: formData.Email,
- IsEmailVerified: isEmailVerified,
- Phone: formData.Phone,
- IsPhoneVerified: isPhoneVerified,
- PreferredLanguage: language.Make(formData.Language),
- }
-}
-
-func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) *domain.ExternalUser {
- displayName := tokens.IDTokenClaims.GetPreferredUsername()
- if displayName == "" && tokens.IDTokenClaims.GetEmail() != "" {
- displayName = tokens.IDTokenClaims.GetEmail()
- }
- switch idpConfig.OIDCIDPDisplayNameMapping {
- case iam_model.OIDCMappingFieldEmail:
- if tokens.IDTokenClaims.IsEmailVerified() && tokens.IDTokenClaims.GetEmail() != "" {
- displayName = tokens.IDTokenClaims.GetEmail()
- }
- }
-
- externalUser := &domain.ExternalUser{
- IDPConfigID: idpConfig.IDPConfigID,
- ExternalUserID: tokens.IDTokenClaims.GetSubject(),
- PreferredUsername: tokens.IDTokenClaims.GetPreferredUsername(),
- DisplayName: displayName,
- FirstName: tokens.IDTokenClaims.GetGivenName(),
- LastName: tokens.IDTokenClaims.GetFamilyName(),
- NickName: tokens.IDTokenClaims.GetNickname(),
- Email: tokens.IDTokenClaims.GetEmail(),
- IsEmailVerified: tokens.IDTokenClaims.IsEmailVerified(),
- PreferredLanguage: tokens.IDTokenClaims.GetLocale(),
- }
-
- if tokens.IDTokenClaims.GetPhoneNumber() != "" {
- externalUser.Phone = tokens.IDTokenClaims.GetPhoneNumber()
- externalUser.IsPhoneVerified = tokens.IDTokenClaims.IsPhoneNumberVerified()
- }
- return externalUser
-}
-func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *query.DomainPolicy, linkingUser *domain.ExternalUser, idpConfig *iam_model.IDPConfigView) (*domain.Human, *domain.UserIDPLink, []*domain.Metadata) {
- username := linkingUser.PreferredUsername
- switch idpConfig.OIDCUsernameMapping {
- case iam_model.OIDCMappingFieldEmail:
- if linkingUser.IsEmailVerified && linkingUser.Email != "" && username == "" {
- username = linkingUser.Email
- }
- }
- if username == "" {
- username = linkingUser.Email
- }
-
- if orgIamPolicy.UserLoginMustBeDomain {
- index := strings.LastIndex(username, "@")
- if index > 1 {
- username = username[:index]
- }
- }
-
- human := &domain.Human{
- Username: username,
- Profile: &domain.Profile{
- FirstName: linkingUser.FirstName,
- LastName: linkingUser.LastName,
- PreferredLanguage: linkingUser.PreferredLanguage,
- NickName: linkingUser.NickName,
- },
- Email: &domain.Email{
- EmailAddress: linkingUser.Email,
- IsEmailVerified: linkingUser.IsEmailVerified,
- },
- }
- if linkingUser.Phone != "" {
- human.Phone = &domain.Phone{
- PhoneNumber: linkingUser.Phone,
- IsPhoneVerified: linkingUser.IsPhoneVerified,
- }
- }
-
- displayName := linkingUser.PreferredUsername
- switch idpConfig.OIDCIDPDisplayNameMapping {
- case iam_model.OIDCMappingFieldEmail:
- if linkingUser.IsEmailVerified && linkingUser.Email != "" && displayName == "" {
- displayName = linkingUser.Email
- }
- }
- if displayName == "" {
- displayName = linkingUser.Email
- }
-
- externalIDP := &domain.UserIDPLink{
- IDPConfigID: idpConfig.IDPConfigID,
- ExternalUserID: linkingUser.ExternalUserID,
- DisplayName: displayName,
- }
- return human, externalIDP, linkingUser.Metadatas
-}
diff --git a/internal/api/ui/login/external_provider_handler.go b/internal/api/ui/login/external_provider_handler.go
new file mode 100644
index 000000000..9e6d7d378
--- /dev/null
+++ b/internal/api/ui/login/external_provider_handler.go
@@ -0,0 +1,707 @@
+package login
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/zitadel/logging"
+ "github.com/zitadel/oidc/v2/pkg/client/rp"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "golang.org/x/oauth2"
+ "golang.org/x/text/language"
+
+ "github.com/zitadel/zitadel/internal/api/authz"
+ http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
+ "github.com/zitadel/zitadel/internal/crypto"
+ "github.com/zitadel/zitadel/internal/domain"
+ "github.com/zitadel/zitadel/internal/errors"
+ "github.com/zitadel/zitadel/internal/eventstore/v1/models"
+ "github.com/zitadel/zitadel/internal/idp"
+ "github.com/zitadel/zitadel/internal/idp/providers/google"
+ "github.com/zitadel/zitadel/internal/idp/providers/jwt"
+ openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
+ "github.com/zitadel/zitadel/internal/query"
+)
+
+const (
+ queryIDPConfigID = "idpConfigID"
+ tmplExternalNotFoundOption = "externalnotfoundoption"
+)
+
+type externalIDPData struct {
+ IDPConfigID string `schema:"idpConfigID"`
+}
+
+type externalIDPCallbackData struct {
+ State string `schema:"state"`
+ Code string `schema:"code"`
+}
+
+type externalNotFoundOptionFormData struct {
+ externalRegisterFormData
+ Link bool `schema:"linkbutton"`
+ AutoRegister bool `schema:"autoregisterbutton"`
+ ResetLinking bool `schema:"resetlinking"`
+ TermsConfirm bool `schema:"terms-confirm"`
+}
+
+type externalNotFoundOptionData struct {
+ baseData
+ externalNotFoundOptionFormData
+ IsLinkingAllowed bool
+ IsCreationAllowed bool
+ ExternalIDPID string
+ ExternalIDPUserID string
+ ExternalIDPUserDisplayName string
+ ShowUsername bool
+ ShowUsernameSuffix bool
+ OrgRegister bool
+ ExternalEmail string
+ ExternalEmailVerified bool
+ ExternalPhone string
+ ExternalPhoneVerified bool
+}
+
+type externalRegisterFormData struct {
+ ExternalIDPConfigID string `schema:"external-idp-config-id"`
+ ExternalIDPExtUserID string `schema:"external-idp-ext-user-id"`
+ ExternalIDPDisplayName string `schema:"external-idp-display-name"`
+ ExternalEmail string `schema:"external-email"`
+ ExternalEmailVerified bool `schema:"external-email-verified"`
+ Email string `schema:"email"`
+ Username string `schema:"username"`
+ Firstname string `schema:"firstname"`
+ Lastname string `schema:"lastname"`
+ Nickname string `schema:"nickname"`
+ ExternalPhone string `schema:"external-phone"`
+ ExternalPhoneVerified bool `schema:"external-phone-verified"`
+ Phone string `schema:"phone"`
+ Language string `schema:"language"`
+ TermsConfirm bool `schema:"terms-confirm"`
+}
+
+// handleExternalLoginStep is called as nextStep
+func (l *Login) handleExternalLoginStep(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, selectedIDPID string) {
+ for _, idp := range authReq.AllowedExternalIDPs {
+ if idp.IDPConfigID == selectedIDPID {
+ l.handleIDP(w, r, authReq, selectedIDPID)
+ return
+ }
+ }
+ l.renderLogin(w, r, authReq, errors.ThrowInvalidArgument(nil, "VIEW-Fsj7f", "Errors.User.ExternalIDP.NotAllowed"))
+}
+
+// handleExternalLogin is called when a user selects the idp on the login page
+func (l *Login) handleExternalLogin(w http.ResponseWriter, r *http.Request) {
+ data := new(externalIDPData)
+ authReq, err := l.getAuthRequestAndParseData(r, data)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ if authReq == nil {
+ l.defaultRedirect(w, r)
+ return
+ }
+ l.handleIDP(w, r, authReq, data.IDPConfigID)
+}
+
+// handleExternalRegister is called when a user selects the idp on the register options page
+func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) {
+ data := new(externalIDPData)
+ authReq, err := l.getAuthRequestAndParseData(r, data)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ l.handleIDP(w, r, authReq, data.IDPConfigID)
+}
+
+// handleIDP start the authentication of the selected IDP
+// it will redirect to the IDPs auth page
+func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, id string) {
+ identityProvider, err := l.getIDPByID(r, id)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
+ err = l.authRepo.SelectExternalIDP(r.Context(), authReq.ID, identityProvider.ID, userAgentID)
+ if err != nil {
+ l.renderLogin(w, r, authReq, err)
+ return
+ }
+ var provider idp.Provider
+ switch identityProvider.Type {
+ case domain.IDPTypeOIDC:
+ provider, err = l.oidcProvider(r.Context(), identityProvider)
+ case domain.IDPTypeJWT:
+ provider, err = l.jwtProvider(r.Context(), identityProvider)
+ case domain.IDPTypeGoogle:
+ provider, err = l.googleProvider(r.Context(), identityProvider)
+ case domain.IDPTypeOAuth,
+ domain.IDPTypeLDAP,
+ domain.IDPTypeAzureAD,
+ domain.IDPTypeGitHub,
+ domain.IDPTypeGitHubEE,
+ domain.IDPTypeGitLab,
+ domain.IDPTypeGitLabSelfHosted,
+ domain.IDPTypeUnspecified:
+ fallthrough
+ default:
+ l.renderLogin(w, r, authReq, errors.ThrowInvalidArgument(nil, "LOGIN-AShek", "Errors.ExternalIDP.IDPTypeNotImplemented"))
+ return
+ }
+ if err != nil {
+ l.renderLogin(w, r, authReq, err)
+ return
+ }
+ session, err := provider.BeginAuth(r.Context(), authReq.ID, authReq.AgentID)
+ if err != nil {
+ l.renderLogin(w, r, authReq, err)
+ return
+ }
+ http.Redirect(w, r, session.GetAuthURL(), http.StatusFound)
+}
+
+// handleExternalLoginCallback handles the callback from a IDP
+// and tries to extract the user with the provided data
+func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Request) {
+ data := new(externalIDPCallbackData)
+ err := l.getParseData(r, data)
+ if err != nil {
+ l.renderLogin(w, r, nil, err)
+ return
+ }
+ userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
+ authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID)
+ if err != nil {
+ l.externalAuthFailed(w, r, authReq, nil, err)
+ return
+ }
+ identityProvider, err := l.getIDPByID(r, authReq.SelectedIDPConfigID)
+ if err != nil {
+ l.externalAuthFailed(w, r, authReq, nil, err)
+ return
+ }
+ var provider idp.Provider
+ var session idp.Session
+ switch identityProvider.Type {
+ case domain.IDPTypeOIDC:
+ provider, err = l.oidcProvider(r.Context(), identityProvider)
+ if err != nil {
+ l.externalAuthFailed(w, r, authReq, nil, err)
+ return
+ }
+ session = &openid.Session{Provider: provider.(*openid.Provider), Code: data.Code}
+ case domain.IDPTypeGoogle:
+ provider, err = l.googleProvider(r.Context(), identityProvider)
+ if err != nil {
+ l.externalAuthFailed(w, r, authReq, nil, err)
+ return
+ }
+ session = &openid.Session{Provider: provider.(*google.Provider).Provider, Code: data.Code}
+ case domain.IDPTypeJWT,
+ domain.IDPTypeOAuth,
+ domain.IDPTypeLDAP,
+ domain.IDPTypeAzureAD,
+ domain.IDPTypeGitHub,
+ domain.IDPTypeGitHubEE,
+ domain.IDPTypeGitLab,
+ domain.IDPTypeGitLabSelfHosted,
+ domain.IDPTypeUnspecified:
+ fallthrough
+ default:
+ l.renderLogin(w, r, authReq, errors.ThrowInvalidArgument(nil, "LOGIN-SFefg", "Errors.ExternalIDP.IDPTypeNotImplemented"))
+ return
+ }
+
+ user, err := session.FetchUser(r.Context())
+ if err != nil {
+ l.externalAuthFailed(w, r, authReq, tokens(session), err)
+ return
+ }
+ l.handleExternalUserAuthenticated(w, r, authReq, identityProvider, session, user, l.renderNextStep)
+}
+
+// handleExternalUserAuthenticated maps the IDP user, checks for a corresponding externalID
+func (l *Login) handleExternalUserAuthenticated(
+ w http.ResponseWriter,
+ r *http.Request,
+ authReq *domain.AuthRequest,
+ provider *query.IDPTemplate,
+ session idp.Session,
+ user idp.User,
+ callback func(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest),
+) {
+ externalUser := mapIDPUserToExternalUser(user, provider.ID)
+ externalUser, err := l.runPostExternalAuthenticationActions(externalUser, tokens(session), authReq, r, nil)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ err = l.authRepo.CheckExternalUserLogin(setContext(r.Context(), ""), authReq.ID, authReq.AgentID, externalUser, domain.BrowserInfoFromRequest(r))
+ if err != nil {
+ if !errors.IsNotFound(err) {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ l.externalUserNotExisting(w, r, authReq, provider, externalUser)
+ return
+ }
+ if provider.IsAutoUpdate || len(externalUser.Metadatas) > 0 {
+ // read current auth request state (incl. authorized user)
+ authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ }
+ if provider.IsAutoUpdate {
+ err = l.updateExternalUser(r.Context(), authReq, externalUser)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ }
+ if len(externalUser.Metadatas) > 0 {
+ _, err = l.command.BulkSetUserMetadata(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, externalUser.Metadatas...)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ }
+ callback(w, r, authReq)
+}
+
+// externalUserNotExisting is called if an externalAuthentication couldn't find a corresponding externalID
+// possible solutions are:
+//
+// * auto creation
+// * external not found overview:
+// - creation by user
+// - linking to existing user
+func (l *Login) externalUserNotExisting(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, provider *query.IDPTemplate, externalUser *domain.ExternalUser) {
+ resourceOwner := authz.GetInstance(r.Context()).DefaultOrganisationID()
+
+ if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner {
+ resourceOwner = authReq.RequestedOrgID
+ }
+
+ orgIAMPolicy, err := l.getOrgDomainPolicy(r, resourceOwner)
+ if err != nil {
+ l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, err)
+ return
+ }
+
+ human, idpLink, _ := mapExternalUserToLoginUser(externalUser, orgIAMPolicy.UserLoginMustBeDomain)
+ // if auto creation or creation itself is disabled, send the user to the notFoundOption
+ if !provider.IsCreationAllowed || !provider.IsAutoCreation {
+ l.renderExternalNotFoundOption(w, r, authReq, orgIAMPolicy, human, idpLink, err)
+ return
+ }
+
+ // reload auth request, to ensure current state (checked external login)
+ authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
+ if err != nil {
+ l.renderExternalNotFoundOption(w, r, authReq, orgIAMPolicy, human, idpLink, err)
+ return
+ }
+ l.autoCreateExternalUser(w, r, authReq)
+}
+
+// autoCreateExternalUser takes the externalUser and creates it automatically (without user interaction)
+func (l *Login) autoCreateExternalUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) {
+ if len(authReq.LinkingUsers) == 0 {
+ l.renderError(w, r, authReq, errors.ThrowPreconditionFailed(nil, "LOGIN-asfg3", "Errors.ExternalIDP.NoExternalUserData"))
+ return
+ }
+
+ // TODO (LS): how do we get multiple and why do we use the last of them (taken as is)?
+ linkingUser := authReq.LinkingUsers[len(authReq.LinkingUsers)-1]
+
+ l.registerExternalUser(w, r, authReq, linkingUser)
+}
+
+// renderExternalNotFoundOption renders a page, where the user is able to edit the IDP data,
+// create a new externalUser of link to existing on (based on the IDP template)
+func (l *Login) renderExternalNotFoundOption(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgIAMPolicy *query.DomainPolicy, human *domain.Human, idpLink *domain.UserIDPLink, err error) {
+ var errID, errMessage string
+ if err != nil {
+ errID, errMessage = l.getErrorMessage(r, err)
+ }
+ if orgIAMPolicy == nil {
+ resourceOwner := authz.GetInstance(r.Context()).DefaultOrganisationID()
+
+ if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner {
+ resourceOwner = authReq.RequestedOrgID
+ }
+
+ orgIAMPolicy, err = l.getOrgDomainPolicy(r, resourceOwner)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+
+ }
+
+ if human == nil || idpLink == nil {
+
+ // TODO (LS): how do we get multiple and why do we use the last of them (taken as is)?
+ linkingUser := authReq.LinkingUsers[len(authReq.LinkingUsers)-1]
+ human, idpLink, _ = mapExternalUserToLoginUser(linkingUser, orgIAMPolicy.UserLoginMustBeDomain)
+ }
+
+ var resourceOwner string
+ if authReq != nil {
+ resourceOwner = authReq.RequestedOrgID
+ }
+ if resourceOwner == "" {
+ resourceOwner = authz.GetInstance(r.Context()).DefaultOrganisationID()
+ }
+ labelPolicy, err := l.getLabelPolicy(r, resourceOwner)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+
+ idpTemplate, err := l.getIDPByID(r, idpLink.IDPConfigID)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+
+ translator := l.getTranslator(r.Context(), authReq)
+ data := externalNotFoundOptionData{
+ baseData: l.getBaseData(r, authReq, "ExternalNotFound.Title", "ExternalNotFound.Description", errID, errMessage),
+ externalNotFoundOptionFormData: externalNotFoundOptionFormData{
+ externalRegisterFormData: externalRegisterFormData{
+ Email: human.EmailAddress,
+ Username: human.Username,
+ Firstname: human.FirstName,
+ Lastname: human.LastName,
+ Nickname: human.NickName,
+ Language: human.PreferredLanguage.String(),
+ },
+ },
+ IsLinkingAllowed: idpTemplate.IsLinkingAllowed,
+ IsCreationAllowed: idpTemplate.IsCreationAllowed,
+ ExternalIDPID: idpLink.IDPConfigID,
+ ExternalIDPUserID: idpLink.ExternalUserID,
+ ExternalIDPUserDisplayName: idpLink.DisplayName,
+ ExternalEmail: human.EmailAddress,
+ ExternalEmailVerified: human.IsEmailVerified,
+ ShowUsername: orgIAMPolicy.UserLoginMustBeDomain,
+ ShowUsernameSuffix: !labelPolicy.HideLoginNameSuffix,
+ OrgRegister: orgIAMPolicy.UserLoginMustBeDomain,
+ }
+ if human.Phone != nil {
+ data.Phone = human.PhoneNumber
+ data.ExternalPhone = human.PhoneNumber
+ data.ExternalPhoneVerified = human.IsPhoneVerified
+ }
+ funcs := map[string]interface{}{
+ "selectedLanguage": func(l string) bool {
+ return data.Language == l
+ },
+ }
+ l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplExternalNotFoundOption], data, funcs)
+}
+
+// handleExternalNotFoundOptionCheck takes the data from the submitted externalNotFound page
+// and either links or creates an externalUser
+func (l *Login) handleExternalNotFoundOptionCheck(w http.ResponseWriter, r *http.Request) {
+ data := new(externalNotFoundOptionFormData)
+ authReq, err := l.getAuthRequestAndParseData(r, data)
+ if err != nil {
+ l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, err)
+ return
+ }
+
+ idpTemplate, err := l.getIDPByID(r, authReq.SelectedIDPConfigID)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ // if the user click on the cancel button / back icon
+ 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, nil, nil, nil, err)
+ }
+ l.handleLogin(w, r)
+ return
+ }
+ // if the user selects the linking button
+ if data.Link {
+ if !idpTemplate.IsLinkingAllowed {
+ l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, errors.ThrowPreconditionFailed(nil, "LOGIN-AS3ff", "Errors.ExternalIDP.LinkingNotAllowed"))
+ return
+ }
+ l.renderLogin(w, r, authReq, nil)
+ return
+ }
+ // if the user selects the creation button
+ if !idpTemplate.IsCreationAllowed {
+ l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, errors.ThrowPreconditionFailed(nil, "LOGIN-dsfd3", "Errors.ExternalIDP.CreationNotAllowed"))
+ return
+ }
+ linkingUser := mapExternalNotFoundOptionFormDataToLoginUser(data)
+ l.registerExternalUser(w, r, authReq, linkingUser)
+}
+
+// registerExternalUser creates an externalUser with the provided data
+// incl. execution of pre and post creation actions
+//
+// it is called from either the [autoCreateExternalUser] or [handleExternalNotFoundOptionCheck]
+func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, externalUser *domain.ExternalUser) {
+ resourceOwner := authz.GetInstance(r.Context()).DefaultOrganisationID()
+
+ if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner {
+ resourceOwner = authReq.RequestedOrgID
+ }
+
+ orgIamPolicy, err := l.getOrgDomainPolicy(r, resourceOwner)
+ if err != nil {
+ l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, err)
+ return
+ }
+ user, externalIDP, metadata := mapExternalUserToLoginUser(externalUser, orgIamPolicy.UserLoginMustBeDomain)
+
+ user, metadata, err = l.runPreCreationActions(authReq, r, user, metadata, resourceOwner, domain.FlowTypeExternalAuthentication)
+ if err != nil {
+ l.renderExternalNotFoundOption(w, r, authReq, orgIamPolicy, nil, nil, err)
+ return
+ }
+ err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, nil, authReq.ID, authReq.AgentID, resourceOwner, metadata, domain.BrowserInfoFromRequest(r))
+ if err != nil {
+ l.renderExternalNotFoundOption(w, r, authReq, orgIamPolicy, user, externalIDP, err)
+ return
+ }
+ // read auth request again to get current state including userID
+ authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ userGrants, err := l.runPostCreationActions(authReq.UserID, authReq, r, resourceOwner, domain.FlowTypeExternalAuthentication)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ err = l.appendUserGrants(r.Context(), userGrants, resourceOwner)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ l.renderNextStep(w, r, authReq)
+}
+
+// updateExternalUser will update the existing user (email, phone, profile) with data provided by the IDP
+func (l *Login) updateExternalUser(ctx context.Context, authReq *domain.AuthRequest, externalUser *domain.ExternalUser) error {
+ user, err := l.query.GetUserByID(ctx, true, authReq.UserID, false)
+ if err != nil {
+ return err
+ }
+ if user.Human == nil {
+ return errors.ThrowPreconditionFailed(nil, "LOGIN-WLTce", "Errors.User.NotHuman")
+ }
+ if externalUser.Email != "" && externalUser.Email != user.Human.Email && externalUser.IsEmailVerified != user.Human.IsEmailVerified {
+ emailCodeGenerator, err := l.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, l.userCodeAlg)
+ logging.WithFields("authReq", authReq.ID, "user", authReq.UserID).OnError(err).Error("unable to update email")
+ if err == nil {
+ _, err = l.command.ChangeHumanEmail(setContext(ctx, authReq.UserOrgID),
+ &domain.Email{
+ ObjectRoot: models.ObjectRoot{AggregateID: authReq.UserID},
+ EmailAddress: externalUser.Email,
+ IsEmailVerified: externalUser.IsEmailVerified,
+ },
+ emailCodeGenerator)
+ logging.WithFields("authReq", authReq.ID, "user", authReq.UserID).OnError(err).Error("unable to update email")
+ }
+ }
+ if externalUser.Phone != "" && externalUser.Phone != user.Human.Phone && externalUser.IsPhoneVerified != user.Human.IsPhoneVerified {
+ phoneCodeGenerator, err := l.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, l.userCodeAlg)
+ logging.WithFields("authReq", authReq.ID, "user", authReq.UserID).OnError(err).Error("unable to update phone")
+ if err == nil {
+ _, err = l.command.ChangeHumanPhone(setContext(ctx, authReq.UserOrgID),
+ &domain.Phone{
+ ObjectRoot: models.ObjectRoot{AggregateID: authReq.UserID},
+ PhoneNumber: externalUser.Phone,
+ IsPhoneVerified: externalUser.IsPhoneVerified,
+ },
+ authReq.UserOrgID,
+ phoneCodeGenerator)
+ logging.WithFields("authReq", authReq.ID, "user", authReq.UserID).OnError(err).Error("unable to update phone")
+ }
+ }
+ if externalUser.FirstName != user.Human.FirstName ||
+ externalUser.LastName != user.Human.LastName ||
+ externalUser.NickName != user.Human.NickName ||
+ externalUser.DisplayName != user.Human.DisplayName ||
+ externalUser.PreferredLanguage != user.Human.PreferredLanguage {
+ _, err = l.command.ChangeHumanProfile(setContext(ctx, authReq.UserOrgID), &domain.Profile{
+ ObjectRoot: models.ObjectRoot{AggregateID: authReq.UserID},
+ FirstName: externalUser.FirstName,
+ LastName: externalUser.LastName,
+ NickName: externalUser.NickName,
+ DisplayName: externalUser.DisplayName,
+ PreferredLanguage: externalUser.PreferredLanguage,
+ Gender: user.Human.Gender,
+ })
+ logging.WithFields("authReq", authReq.ID, "user", authReq.UserID).OnError(err).Error("unable to update profile")
+ }
+ return nil
+}
+
+func (l *Login) googleProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*google.Provider, error) {
+ errorHandler := func(w http.ResponseWriter, r *http.Request, errorType string, errorDesc string, state string) {
+ logging.Errorf("token exchanged failed: %s - %s (state: %s)", errorType, errorType, state)
+ rp.DefaultErrorHandler(w, r, errorType, errorDesc, state)
+ }
+ openid.WithRelyingPartyOption(rp.WithErrorHandler(errorHandler))
+ secret, err := crypto.DecryptString(identityProvider.GoogleIDPTemplate.ClientSecret, l.idpConfigAlg)
+ if err != nil {
+ return nil, err
+ }
+ return google.New(
+ identityProvider.GoogleIDPTemplate.ClientID,
+ secret,
+ l.baseURL(ctx)+EndpointExternalLoginCallback,
+ identityProvider.GoogleIDPTemplate.Scopes,
+ )
+}
+
+func (l *Login) oidcProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*openid.Provider, error) {
+ secret, err := crypto.DecryptString(identityProvider.OIDCIDPTemplate.ClientSecret, l.idpConfigAlg)
+ if err != nil {
+ return nil, err
+ }
+ return openid.New(identityProvider.Name,
+ identityProvider.OIDCIDPTemplate.Issuer,
+ identityProvider.OIDCIDPTemplate.ClientID,
+ secret,
+ l.baseURL(ctx)+EndpointExternalLoginCallback,
+ identityProvider.OIDCIDPTemplate.Scopes,
+ openid.DefaultMapper,
+ )
+}
+
+func (l *Login) jwtProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*jwt.Provider, error) {
+ return jwt.New(
+ identityProvider.Name,
+ identityProvider.JWTIDPTemplate.Issuer,
+ identityProvider.JWTIDPTemplate.Endpoint,
+ identityProvider.JWTIDPTemplate.KeysEndpoint,
+ identityProvider.JWTIDPTemplate.HeaderName,
+ l.idpConfigAlg,
+ )
+}
+
+func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserGrant, resourceOwner string) error {
+ if len(userGrants) == 0 {
+ return nil
+ }
+ for _, grant := range userGrants {
+ _, err := l.command.AddUserGrant(setContext(ctx, resourceOwner), grant, resourceOwner)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (l *Login) externalAuthFailed(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, tokens *oidc.Tokens, err error) {
+ if tokens == nil {
+ tokens = &oidc.Tokens{Token: &oauth2.Token{}}
+ }
+ if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, tokens, authReq, r, err); actionErr != nil {
+ logging.WithError(err).Error("both external user authentication and action post authentication failed")
+ }
+ l.renderLogin(w, r, authReq, err)
+}
+
+// tokens extracts the oidc.Tokens for backwards compatibility of PostExternalAuthenticationActions
+func tokens(session idp.Session) *oidc.Tokens {
+ switch s := session.(type) {
+ case *openid.Session:
+ return s.Tokens
+ case *jwt.Session:
+ return s.Tokens
+ }
+ return nil
+}
+
+func mapIDPUserToExternalUser(user idp.User, id string) *domain.ExternalUser {
+ return &domain.ExternalUser{
+ IDPConfigID: id,
+ ExternalUserID: user.GetID(),
+ PreferredUsername: user.GetPreferredUsername(),
+ DisplayName: user.GetDisplayName(),
+ FirstName: user.GetFirstName(),
+ LastName: user.GetLastName(),
+ NickName: user.GetNickname(),
+ Email: user.GetEmail(),
+ IsEmailVerified: user.IsEmailVerified(),
+ PreferredLanguage: user.GetPreferredLanguage(),
+ Phone: user.GetPhone(),
+ IsPhoneVerified: user.IsPhoneVerified(),
+ }
+}
+
+func mapExternalUserToLoginUser(externalUser *domain.ExternalUser, mustBeDomain bool) (*domain.Human, *domain.UserIDPLink, []*domain.Metadata) {
+ username := externalUser.PreferredUsername
+ if mustBeDomain {
+ index := strings.LastIndex(username, "@")
+ if index > 1 {
+ username = username[:index]
+ }
+ }
+ human := &domain.Human{
+ Username: username,
+ Profile: &domain.Profile{
+ FirstName: externalUser.FirstName,
+ LastName: externalUser.LastName,
+ PreferredLanguage: externalUser.PreferredLanguage,
+ NickName: externalUser.NickName,
+ DisplayName: externalUser.DisplayName,
+ },
+ Email: &domain.Email{
+ EmailAddress: externalUser.Email,
+ IsEmailVerified: externalUser.IsEmailVerified,
+ },
+ }
+ if externalUser.Phone != "" {
+ human.Phone = &domain.Phone{
+ PhoneNumber: externalUser.Phone,
+ IsPhoneVerified: externalUser.IsPhoneVerified,
+ }
+ }
+ externalIDP := &domain.UserIDPLink{
+ IDPConfigID: externalUser.IDPConfigID,
+ ExternalUserID: externalUser.ExternalUserID,
+ DisplayName: externalUser.DisplayName,
+ }
+ return human, externalIDP, externalUser.Metadatas
+}
+
+func mapExternalNotFoundOptionFormDataToLoginUser(formData *externalNotFoundOptionFormData) *domain.ExternalUser {
+ isEmailVerified := formData.ExternalEmailVerified && formData.Email == formData.ExternalEmail
+ isPhoneVerified := formData.ExternalPhoneVerified && formData.Phone == formData.ExternalPhone
+ return &domain.ExternalUser{
+ IDPConfigID: formData.ExternalIDPConfigID,
+ ExternalUserID: formData.ExternalIDPExtUserID,
+ PreferredUsername: formData.Username,
+ DisplayName: formData.Email,
+ FirstName: formData.Firstname,
+ LastName: formData.Lastname,
+ NickName: formData.Nickname,
+ Email: formData.Email,
+ IsEmailVerified: isEmailVerified,
+ Phone: formData.Phone,
+ IsPhoneVerified: isPhoneVerified,
+ PreferredLanguage: language.Make(formData.Language),
+ }
+}
diff --git a/internal/api/ui/login/external_register_handler.go b/internal/api/ui/login/external_register_handler.go
deleted file mode 100644
index 713d41b60..000000000
--- a/internal/api/ui/login/external_register_handler.go
+++ /dev/null
@@ -1,304 +0,0 @@
-package login
-
-import (
- "net/http"
-
- "github.com/zitadel/oidc/v2/pkg/client/rp"
- "github.com/zitadel/oidc/v2/pkg/oidc"
- "golang.org/x/text/language"
-
- "github.com/zitadel/zitadel/internal/api/authz"
- http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
- "github.com/zitadel/zitadel/internal/domain"
- iam_model "github.com/zitadel/zitadel/internal/iam/model"
- "github.com/zitadel/zitadel/internal/query"
-)
-
-const (
- tmplExternalRegisterOverview = "externalregisteroverview"
-)
-
-type externalRegisterFormData struct {
- ExternalIDPConfigID string `schema:"external-idp-config-id"`
- ExternalIDPExtUserID string `schema:"external-idp-ext-user-id"`
- ExternalIDPDisplayName string `schema:"external-idp-display-name"`
- ExternalEmail string `schema:"external-email"`
- ExternalEmailVerified bool `schema:"external-email-verified"`
- Email string `schema:"email"`
- Username string `schema:"username"`
- Firstname string `schema:"firstname"`
- Lastname string `schema:"lastname"`
- Nickname string `schema:"nickname"`
- ExternalPhone string `schema:"external-phone"`
- ExternalPhoneVerified bool `schema:"external-phone-verified"`
- Phone string `schema:"phone"`
- Language string `schema:"language"`
- TermsConfirm bool `schema:"terms-confirm"`
-}
-
-type externalRegisterData struct {
- baseData
- externalRegisterFormData
- ExternalIDPID string
- ExternalIDPUserID string
- ExternalIDPUserDisplayName string
- ShowUsername bool
- ShowUsernameSuffix bool
- OrgRegister bool
- ExternalEmail string
- ExternalEmailVerified bool
- ExternalPhone string
- ExternalPhoneVerified bool
-}
-
-func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) {
- data := new(externalIDPData)
- authReq, err := l.getAuthRequestAndParseData(r, data)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- l.handleExternalRegisterByConfigID(w, r, authReq, data.IDPConfigID)
-}
-
-func (l *Login) handleExternalRegisterByConfigID(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, configID string) {
- if authReq == nil {
- l.defaultRedirect(w, r)
- return
- }
- idpConfig, err := l.getIDPConfigByID(r, configID)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
- err = l.authRepo.SelectExternalIDP(r.Context(), authReq.ID, idpConfig.IDPConfigID, userAgentID)
- if err != nil {
- l.renderLogin(w, r, authReq, err)
- return
- }
- if !idpConfig.IsOIDC {
- l.handleJWTAuthorize(w, r, authReq, idpConfig)
- return
- }
- l.handleOIDCAuthorize(w, r, authReq, idpConfig, EndpointExternalRegisterCallback)
-}
-
-func (l *Login) handleExternalRegisterCallback(w http.ResponseWriter, r *http.Request) {
- data := new(externalIDPCallbackData)
- err := l.getParseData(r, data)
- if err != nil {
- l.renderError(w, r, nil, err)
- return
- }
- userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
- authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- provider, err := l.getRPConfig(r.Context(), idpConfig, EndpointExternalRegisterCallback)
- if err != nil {
- l.renderRegisterOption(w, r, authReq, err)
- return
- }
- tokens, err := rp.CodeExchange(r.Context(), data.Code, provider)
- if err != nil {
- l.renderRegisterOption(w, r, authReq, err)
- return
- }
- l.handleExternalUserRegister(w, r, authReq, idpConfig, userAgentID, tokens)
-}
-
-func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) {
- resourceOwner := authz.GetInstance(r.Context()).DefaultOrganisationID()
- if authReq.RequestedOrgID != "" {
- resourceOwner = authReq.RequestedOrgID
- }
- externalUser, externalIDP := l.mapTokenToLoginHumanAndExternalIDP(tokens, idpConfig)
- externalUser, err := l.runPostExternalAuthenticationActions(externalUser, tokens, authReq, r, idpConfig, nil)
- if err != nil {
- l.renderRegisterOption(w, r, authReq, err)
- return
- }
- if idpConfig.AutoRegister {
- l.registerExternalUser(w, r, authReq, externalUser)
- return
- }
- orgIamPolicy, err := l.getOrgDomainPolicy(r, resourceOwner)
- if err != nil {
- l.renderRegisterOption(w, r, authReq, err)
- return
- }
- labelPolicy, err := l.getLabelPolicy(r, resourceOwner)
- if err != nil {
- l.renderRegisterOption(w, r, authReq, err)
- return
- }
- l.renderExternalRegisterOverview(w, r, authReq, orgIamPolicy, externalUser, externalIDP, labelPolicy.HideLoginNameSuffix, nil)
-}
-
-func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, externalUser *domain.ExternalUser) {
- resourceOwner := authz.GetInstance(r.Context()).DefaultOrganisationID()
-
- if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner {
- resourceOwner = authReq.RequestedOrgID
- }
- orgIamPolicy, err := l.getOrgDomainPolicy(r, resourceOwner)
- if err != nil {
- l.renderRegisterOption(w, r, authReq, err)
- return
- }
-
- idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
- if err != nil {
- l.renderRegisterOption(w, r, authReq, err)
- return
- }
- user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, externalUser, idpConfig)
- user, metadata, err = l.runPreCreationActions(authReq, r, user, metadata, resourceOwner, domain.FlowTypeExternalAuthentication)
- if err != nil {
- l.renderRegisterOption(w, r, authReq, err)
- return
- }
- err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, nil, authReq.ID, authReq.AgentID, resourceOwner, metadata, domain.BrowserInfoFromRequest(r))
- if err != nil {
- l.renderRegisterOption(w, r, authReq, err)
- return
- }
- // read auth request again to get current state including userID
- authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- userGrants, err := l.runPostCreationActions(authReq.UserID, authReq, r, resourceOwner, domain.FlowTypeExternalAuthentication)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- err = l.appendUserGrants(r.Context(), userGrants, resourceOwner)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- l.renderNextStep(w, r, authReq)
-}
-
-func (l *Login) renderExternalRegisterOverview(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgIAMPolicy *query.DomainPolicy, externalUser *domain.ExternalUser, idp *domain.UserIDPLink, hideLoginNameSuffix bool, err error) {
- var errID, errMessage string
- if err != nil {
- errID, errMessage = l.getErrorMessage(r, err)
- }
-
- translator := l.getTranslator(r.Context(), authReq)
- data := externalRegisterData{
- baseData: l.getBaseData(r, authReq, "ExternalRegistrationUserOverview.Title", "ExternalRegistrationUserOverview.Description", errID, errMessage),
- externalRegisterFormData: externalRegisterFormData{
- Email: externalUser.Email,
- Username: externalUser.PreferredUsername,
- Firstname: externalUser.FirstName,
- Lastname: externalUser.LastName,
- Nickname: externalUser.NickName,
- Language: externalUser.PreferredLanguage.String(),
- },
- ExternalIDPID: idp.IDPConfigID,
- ExternalIDPUserID: idp.ExternalUserID,
- ExternalIDPUserDisplayName: idp.DisplayName,
- ExternalEmail: externalUser.Email,
- ExternalEmailVerified: externalUser.IsEmailVerified,
- ShowUsername: orgIAMPolicy.UserLoginMustBeDomain,
- OrgRegister: orgIAMPolicy.UserLoginMustBeDomain,
- ShowUsernameSuffix: !hideLoginNameSuffix,
- }
- data.Phone = externalUser.Phone
- data.ExternalPhone = externalUser.Phone
- data.ExternalPhoneVerified = externalUser.IsPhoneVerified
-
- funcs := map[string]interface{}{
- "selectedLanguage": func(l string) bool {
- return data.Language == l
- },
- }
- l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplExternalRegisterOverview], data, funcs)
-}
-
-func (l *Login) handleExternalRegisterCheck(w http.ResponseWriter, r *http.Request) {
- data := new(externalRegisterFormData)
- authReq, err := l.getAuthRequestAndParseData(r, data)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
-
- resourceOwner := authz.GetInstance(r.Context()).DefaultOrganisationID()
-
- if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner {
- resourceOwner = authReq.RequestedOrgID
- }
-
- user := l.mapExternalRegisterDataToUser(data)
- l.registerExternalUser(w, r, authReq, user)
-}
-
-func (l *Login) mapTokenToLoginHumanAndExternalIDP(tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) (*domain.ExternalUser, *domain.UserIDPLink) {
- displayName := tokens.IDTokenClaims.GetPreferredUsername()
- switch idpConfig.OIDCIDPDisplayNameMapping {
- case iam_model.OIDCMappingFieldEmail:
- if tokens.IDTokenClaims.IsEmailVerified() && tokens.IDTokenClaims.GetEmail() != "" {
- displayName = tokens.IDTokenClaims.GetEmail()
- }
- }
- if displayName == "" {
- displayName = tokens.IDTokenClaims.GetEmail()
- }
-
- externalUser := &domain.ExternalUser{
- IDPConfigID: idpConfig.IDPConfigID,
- ExternalUserID: tokens.IDTokenClaims.GetSubject(),
- PreferredUsername: tokens.IDTokenClaims.GetPreferredUsername(),
- DisplayName: displayName,
- FirstName: tokens.IDTokenClaims.GetGivenName(),
- LastName: tokens.IDTokenClaims.GetFamilyName(),
- NickName: tokens.IDTokenClaims.GetNickname(),
- Email: tokens.IDTokenClaims.GetEmail(),
- IsEmailVerified: tokens.IDTokenClaims.IsEmailVerified(),
- PreferredLanguage: tokens.IDTokenClaims.GetLocale(),
- }
-
- if tokens.IDTokenClaims.GetPhoneNumber() != "" {
- externalUser.Phone = tokens.IDTokenClaims.GetPhoneNumber()
- externalUser.IsPhoneVerified = tokens.IDTokenClaims.IsPhoneNumberVerified()
- }
-
- externalIDP := &domain.UserIDPLink{
- IDPConfigID: idpConfig.IDPConfigID,
- ExternalUserID: tokens.IDTokenClaims.GetSubject(),
- DisplayName: displayName,
- }
- return externalUser, externalIDP
-}
-
-func (l *Login) mapExternalRegisterDataToUser(data *externalRegisterFormData) *domain.ExternalUser {
- isEmailVerified := data.ExternalEmailVerified && data.Email == data.ExternalEmail
- isPhoneVerified := data.ExternalPhoneVerified && data.Phone == data.ExternalPhone
- return &domain.ExternalUser{
- IDPConfigID: data.ExternalIDPConfigID,
- ExternalUserID: data.ExternalIDPExtUserID,
- PreferredUsername: data.Username,
- DisplayName: data.Email,
- FirstName: data.Firstname,
- LastName: data.Lastname,
- NickName: data.Nickname,
- PreferredLanguage: language.Make(data.Language),
- Email: data.Email,
- IsEmailVerified: isEmailVerified,
- Phone: data.Phone,
- IsPhoneVerified: isPhoneVerified,
- }
-}
diff --git a/internal/api/ui/login/jwt_handler.go b/internal/api/ui/login/jwt_handler.go
index ac100dd6a..94c5a39e0 100644
--- a/internal/api/ui/login/jwt_handler.go
+++ b/internal/api/ui/login/jwt_handler.go
@@ -6,17 +6,16 @@ import (
"net/http"
"net/url"
"strings"
- "time"
"github.com/zitadel/logging"
- "github.com/zitadel/oidc/v2/pkg/client/rp"
"github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/oauth2"
http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
- iam_model "github.com/zitadel/zitadel/internal/iam/model"
+ "github.com/zitadel/zitadel/internal/idp/providers/jwt"
+ "github.com/zitadel/zitadel/internal/query"
)
type jwtRequest struct {
@@ -50,12 +49,12 @@ func (l *Login) handleJWTRequest(w http.ResponseWriter, r *http.Request) {
l.renderError(w, r, authReq, err)
return
}
- idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
+ idpConfig, err := l.getIDPByID(r, authReq.SelectedIDPConfigID)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
- if idpConfig.IsOIDC {
+ if idpConfig.Type != domain.IDPTypeJWT {
if err != nil {
l.renderError(w, r, nil, err)
return
@@ -64,50 +63,39 @@ func (l *Login) handleJWTRequest(w http.ResponseWriter, r *http.Request) {
l.handleJWTExtraction(w, r, authReq, idpConfig)
}
-func (l *Login) handleJWTExtraction(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView) {
- token, err := getToken(r, idpConfig.JWTHeaderName)
+func (l *Login) handleJWTExtraction(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, identityProvider *query.IDPTemplate) {
+ token, err := getToken(r, identityProvider.JWTIDPTemplate.HeaderName)
if err != nil {
- emtpyTokens := &oidc.Tokens{Token: &oauth2.Token{}}
- if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, emtpyTokens, authReq, r, idpConfig, err); actionErr != nil {
+ emptyTokens := &oidc.Tokens{Token: &oauth2.Token{}}
+ if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, emptyTokens, authReq, r, err); actionErr != nil {
logging.WithError(err).Error("both external user authentication and action post authentication failed")
}
l.renderError(w, r, authReq, err)
return
}
- tokenClaims, err := validateToken(r.Context(), token, idpConfig)
- tokens := &oidc.Tokens{IDToken: token, IDTokenClaims: tokenClaims, Token: &oauth2.Token{}}
+ provider, err := l.jwtProvider(r.Context(), identityProvider)
if err != nil {
- if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, tokens, authReq, r, idpConfig, err); actionErr != nil {
+ emptyTokens := &oidc.Tokens{Token: &oauth2.Token{}}
+ if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, emptyTokens, authReq, r, err); actionErr != nil {
logging.WithError(err).Error("both external user authentication and action post authentication failed")
}
l.renderError(w, r, authReq, err)
return
}
- externalUser := l.mapTokenToLoginUser(tokens, idpConfig)
- externalUser, err = l.runPostExternalAuthenticationActions(externalUser, tokens, authReq, r, idpConfig, nil)
+ session := &jwt.Session{Provider: provider, Tokens: &oidc.Tokens{IDToken: token, Token: &oauth2.Token{}}}
+ user, err := session.FetchUser(r.Context())
if err != nil {
+ if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, tokens(session), authReq, r, err); actionErr != nil {
+ logging.WithError(err).Error("both external user authentication and action post authentication failed")
+ }
l.renderError(w, r, authReq, err)
return
}
- metadata := externalUser.Metadatas
- err = l.authRepo.CheckExternalUserLogin(setContext(r.Context(), ""), authReq.ID, authReq.AgentID, externalUser, domain.BrowserInfoFromRequest(r))
- if err != nil {
- l.jwtExtractionUserNotFound(w, r, authReq, idpConfig, tokens, err)
- return
- }
- if len(metadata) > 0 {
- authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- _, err = l.command.BulkSetUserMetadata(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, metadata...)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- }
+ l.handleExternalUserAuthenticated(w, r, authReq, identityProvider, session, user, l.jwtCallback)
+}
+
+func (l *Login) jwtCallback(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) {
redirect, err := l.redirectToJWTCallback(r.Context(), authReq)
if err != nil {
l.renderError(w, r, nil, err)
@@ -116,73 +104,6 @@ func (l *Login) handleJWTExtraction(w http.ResponseWriter, r *http.Request, auth
http.Redirect(w, r, redirect, http.StatusFound)
}
-func (l *Login) jwtExtractionUserNotFound(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, tokens *oidc.Tokens, err error) {
- if errors.IsNotFound(err) {
- err = nil
- }
- if !idpConfig.AutoRegister {
- l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, err)
- return
- }
- authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- resourceOwner := l.getOrgID(r, authReq)
- orgIamPolicy, err := l.getOrgDomainPolicy(r, resourceOwner)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
-
- user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig)
- user, metadata, err = l.runPreCreationActions(authReq, r, user, metadata, resourceOwner, domain.FlowTypeExternalAuthentication)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, nil, authReq.ID, authReq.AgentID, resourceOwner, metadata, domain.BrowserInfoFromRequest(r))
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- userGrants, err := l.runPostCreationActions(authReq.UserID, authReq, r, resourceOwner, domain.FlowTypeExternalAuthentication)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- err = l.appendUserGrants(r.Context(), userGrants, resourceOwner)
- if err != nil {
- l.renderError(w, r, authReq, err)
- return
- }
- redirect, err := l.redirectToJWTCallback(r.Context(), authReq)
- if err != nil {
- l.renderError(w, r, nil, err)
- return
- }
- http.Redirect(w, r, redirect, http.StatusFound)
-}
-
-func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserGrant, resourceOwner string) error {
- if len(userGrants) == 0 {
- return nil
- }
- for _, grant := range userGrants {
- _, err := l.command.AddUserGrant(setContext(ctx, resourceOwner), grant, resourceOwner)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
func (l *Login) redirectToJWTCallback(ctx context.Context, authReq *domain.AuthRequest) (string, error) {
redirect, err := url.Parse(l.baseURL(ctx) + EndpointJWTCallback)
if err != nil {
@@ -221,52 +142,18 @@ func (l *Login) handleJWTCallback(w http.ResponseWriter, r *http.Request) {
l.renderError(w, r, authReq, err)
return
}
- idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
+ idpConfig, err := l.getIDPByID(r, authReq.SelectedIDPConfigID)
if err != nil {
l.renderError(w, r, authReq, err)
return
}
- if idpConfig.IsOIDC {
+ if idpConfig.Type != domain.IDPTypeJWT {
l.renderLogin(w, r, authReq, err)
return
}
l.renderNextStep(w, r, authReq)
}
-func validateToken(ctx context.Context, token string, config *iam_model.IDPConfigView) (oidc.IDTokenClaims, error) {
- logging.Log("LOGIN-ADf42").Debug("begin token validation")
- offset := 3 * time.Second
- maxAge := time.Hour
- claims := oidc.EmptyIDTokenClaims()
- payload, err := oidc.ParseToken(token, claims)
- if err != nil {
- return nil, err
- }
-
- if err = oidc.CheckIssuer(claims, config.JWTIssuer); err != nil {
- return nil, err
- }
-
- logging.Log("LOGIN-Dfg22").Debug("begin signature validation")
- keySet := rp.NewRemoteKeySet(http.DefaultClient, config.JWTKeysEndpoint)
- if err = oidc.CheckSignature(ctx, token, payload, claims, nil, keySet); err != nil {
- return nil, err
- }
-
- if !claims.GetExpiration().IsZero() {
- if err = oidc.CheckExpiration(claims, offset); err != nil {
- return nil, err
- }
- }
-
- if !claims.GetIssuedAt().IsZero() {
- if err = oidc.CheckIssuedAt(claims, maxAge, offset); err != nil {
- return nil, err
- }
- }
- return claims, nil
-}
-
func getToken(r *http.Request, headerName string) (string, error) {
if headerName == "" {
headerName = http_util.Authorization
diff --git a/internal/api/ui/login/policy_handler.go b/internal/api/ui/login/policy_handler.go
index 8b8bcd698..e5e633606 100644
--- a/internal/api/ui/login/policy_handler.go
+++ b/internal/api/ui/login/policy_handler.go
@@ -3,7 +3,6 @@ package login
import (
"net/http"
- iam_model "github.com/zitadel/zitadel/internal/iam/model"
"github.com/zitadel/zitadel/internal/query"
)
@@ -18,8 +17,8 @@ func (l *Login) getOrgDomainPolicy(r *http.Request, orgID string) (*query.Domain
return l.query.DomainPolicyByOrg(r.Context(), false, orgID, false)
}
-func (l *Login) getIDPConfigByID(r *http.Request, idpConfigID string) (*iam_model.IDPConfigView, error) {
- return l.authRepo.GetIDPConfigByID(r.Context(), idpConfigID)
+func (l *Login) getIDPByID(r *http.Request, id string) (*query.IDPTemplate, error) {
+ return l.query.IDPTemplateByID(r.Context(), false, id, false)
}
func (l *Login) getLoginPolicy(r *http.Request, orgID string) (*query.LoginPolicy, error) {
diff --git a/internal/api/ui/login/register_option_handler.go b/internal/api/ui/login/register_option_handler.go
index c00ee18a2..5488dc491 100644
--- a/internal/api/ui/login/register_option_handler.go
+++ b/internal/api/ui/login/register_option_handler.go
@@ -42,7 +42,7 @@ func (l *Login) renderRegisterOption(w http.ResponseWriter, r *http.Request, aut
if err == nil {
// if only external allowed with a single idp then use that
if !allowed && externalAllowed && len(authReq.AllowedExternalIDPs) == 1 {
- l.handleExternalRegisterByConfigID(w, r, authReq, authReq.AllowedExternalIDPs[0].IDPConfigID)
+ l.handleIDP(w, r, authReq, authReq.AllowedExternalIDPs[0].IDPConfigID)
return
}
// if only direct registration is allowed, show the form
diff --git a/internal/api/ui/login/renderer.go b/internal/api/ui/login/renderer.go
index 95ea08637..e0d42aabd 100644
--- a/internal/api/ui/login/renderer.go
+++ b/internal/api/ui/login/renderer.go
@@ -69,7 +69,6 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
tmplChangePasswordDone: "change_password_done.html",
tmplRegisterOption: "register_option.html",
tmplRegister: "register.html",
- tmplExternalRegisterOverview: "external_register_overview.html",
tmplLogoutDone: "logout_done.html",
tmplRegisterOrg: "register_org.html",
tmplChangeUsername: "change_username.html",
@@ -193,9 +192,6 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
"orgRegistrationUrl": func() string {
return path.Join(r.pathPrefix, EndpointRegisterOrg)
},
- "externalRegistrationUrl": func() string {
- return path.Join(r.pathPrefix, EndpointExternalRegister)
- },
"changeUsernameUrl": func() string {
return path.Join(r.pathPrefix, EndpointChangeUsername)
},
@@ -220,8 +216,8 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
"hasRegistration": func() bool {
return true
},
- "idpProviderClass": func(stylingType domain.IDPConfigStylingType) string {
- return stylingType.GetCSSClass()
+ "idpProviderClass": func(idpType domain.IDPType) string {
+ return idpType.GetCSSClass()
},
}
var err error
diff --git a/internal/api/ui/login/router.go b/internal/api/ui/login/router.go
index f4c335d5d..e0f776f24 100644
--- a/internal/api/ui/login/router.go
+++ b/internal/api/ui/login/router.go
@@ -95,8 +95,7 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
router.HandleFunc(EndpointRegister, login.handleRegister).Methods(http.MethodGet)
router.HandleFunc(EndpointRegister, login.handleRegisterCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointExternalRegister, login.handleExternalRegister).Methods(http.MethodGet)
- router.HandleFunc(EndpointExternalRegister, login.handleExternalRegisterCheck).Methods(http.MethodPost)
- router.HandleFunc(EndpointExternalRegisterCallback, login.handleExternalRegisterCallback).Methods(http.MethodGet)
+ router.HandleFunc(EndpointExternalRegisterCallback, login.handleExternalLoginCallback).Methods(http.MethodGet)
router.HandleFunc(EndpointLogoutDone, login.handleLogoutDone).Methods(http.MethodGet)
router.HandleFunc(EndpointDynamicResources, login.handleDynamicResources).Methods(http.MethodGet)
router.PathPrefix(EndpointResources).Handler(login.handleResources(staticDir)).Methods(http.MethodGet)
diff --git a/internal/api/ui/login/static/i18n/de.yaml b/internal/api/ui/login/static/i18n/de.yaml
index d5ff684fe..301bf33b6 100644
--- a/internal/api/ui/login/static/i18n/de.yaml
+++ b/internal/api/ui/login/static/i18n/de.yaml
@@ -370,6 +370,8 @@ Errors:
ExternalUserIDEmpty: Externe User ID ist leer
UserDisplayNameEmpty: Benutzer Anzeige Name ist leer
NoExternalUserData: Keine externe User Daten erhalten
+ CreationNotAllowed: Erstellen eines neuen User ist auf diesem Provider nicht erlaubt
+ LinkingNotAllowed: Linken eines Users ist auf diesem Provider nicht erlaubt
GrantRequired: Der Login an diese Applikation ist nicht möglich. Der Benutzer benötigt mindestens eine Berechtigung an der Applikation. Bitte melde dich bei deinem Administrator.
ProjectRequired: Der Login an diese Applikation ist nicht möglich. Die Organisation des Benutzer benötigt Berechtigung auf das Projekt. Bitte melde dich bei deinem Administrator.
IdentityProvider:
diff --git a/internal/api/ui/login/static/i18n/en.yaml b/internal/api/ui/login/static/i18n/en.yaml
index b939aefb2..d36b0bf5d 100644
--- a/internal/api/ui/login/static/i18n/en.yaml
+++ b/internal/api/ui/login/static/i18n/en.yaml
@@ -370,6 +370,8 @@ Errors:
ExternalUserIDEmpty: External User ID is empty
UserDisplayNameEmpty: User Display Name is empty
NoExternalUserData: No external User Data received
+ CreationNotAllowed: Creation of a new user is not allowed on this Provider
+ LinkingNotAllowed: Linking of a user is not allowed on this Provider
GrantRequired: Login not possible. The user is required to have at least one grant on the application. Please contact your administrator.
ProjectRequired: Login not possible. The organisation of the user must be granted to the project. Please contact your administrator.
IdentityProvider:
diff --git a/internal/api/ui/login/static/i18n/fr.yaml b/internal/api/ui/login/static/i18n/fr.yaml
index 226fe4862..afad746eb 100644
--- a/internal/api/ui/login/static/i18n/fr.yaml
+++ b/internal/api/ui/login/static/i18n/fr.yaml
@@ -370,6 +370,8 @@ Errors:
ExternalUserIDEmpty: L'ID de l'utilisateur externe est vide
UserDisplayNameEmpty: Le nom d'affichage de l'utilisateur est vide
NoExternalUserData: Aucune donnée d'utilisateur externe reçue
+ CreationNotAllowed : La création d'un nouvel utilisateur n'est pas autorisée sur ce fournisseur.
+ LinkingNotAllowed : La création d'un lien vers un utilisateur n'est pas autorisée pour ce fournisseur.
GrantRequired: Connexion impossible. L'utilisateur doit avoir au moins une subvention sur l'application. Veuillez contacter votre administrateur.
ProjectRequired: Connexion impossible. L'organisation de l'utilisateur doit être accordée au projet. Veuillez contacter votre administrateur.
IdentityProvider:
diff --git a/internal/api/ui/login/static/i18n/it.yaml b/internal/api/ui/login/static/i18n/it.yaml
index 9aa67f051..5eacf81b0 100644
--- a/internal/api/ui/login/static/i18n/it.yaml
+++ b/internal/api/ui/login/static/i18n/it.yaml
@@ -370,6 +370,8 @@ Errors:
ExternalUserIDEmpty: L'ID utente esterno è vuoto
UserDisplayNameEmpty: Il nome visualizzato dell'utente è vuoto
NoExternalUserData: Nessun dato utente esterno ricevuto
+ CreationNotAllowed: La creazione di un nuovo utente non è consentita su questo provider.
+ LinkingNotAllowed: Il collegamento di un utente non è consentito su questo provider.
GrantRequired: Accesso non possibile. L'utente deve avere almeno una sovvenzione sull'applicazione. Contatta il tuo amministratore.
ProjectRequired: Accesso non possibile. L'organizzazione dell'utente deve essere concessa al progetto. Contatta il tuo amministratore.
IdentityProvider:
diff --git a/internal/api/ui/login/static/i18n/pl.yaml b/internal/api/ui/login/static/i18n/pl.yaml
index a9e7c5ecd..2650463d6 100644
--- a/internal/api/ui/login/static/i18n/pl.yaml
+++ b/internal/api/ui/login/static/i18n/pl.yaml
@@ -370,6 +370,8 @@ Errors:
ExternalUserIDEmpty: Identyfikator użytkownika zewnętrznego jest pusty
UserDisplayNameEmpty: Nazwa wyświetlana użytkownika jest pusta
NoExternalUserData: Nie otrzymano danych użytkownika zewnętrznego
+ CreationNotAllowed: Tworzenie nowego użytkownika nie jest dozwolone w tym Providencie
+ LinkingNotAllowed: Linkowanie użytkownika nie jest dozwolone na tym Providencie
GrantRequired: Logowanie nie jest możliwe. Użytkownik musi posiadać przynajmniej jedno uprawnienie w aplikacji. Skontaktuj się z administratorem.
ProjectRequired: Logowanie nie jest możliwe. Organizacja użytkownika musi zostać udzielona projektowi. Skontaktuj się z administratorem.
IdentityProvider:
diff --git a/internal/api/ui/login/static/i18n/zh.yaml b/internal/api/ui/login/static/i18n/zh.yaml
index 078b09840..da380c5ab 100644
--- a/internal/api/ui/login/static/i18n/zh.yaml
+++ b/internal/api/ui/login/static/i18n/zh.yaml
@@ -370,6 +370,8 @@ Errors:
ExternalUserIDEmpty: 外部用户 ID 为空
UserDisplayNameEmpty: 用户显示名称为空
NoExternalUserData: 未收到外部用户数据
+ CreationNotAllowed: 不允许在该供应商上创建新用户
+ LinkingNotAllowed: 在此提供者上不允许链接一个用户
GrantRequired: 无法登录,用户需要在应用程序上拥有至少一项授权,请联系您的管理员。
ProjectRequired: 无法登录,用户的组织必须授予项目,请联系您的管理员。
IdentityProvider:
diff --git a/internal/api/ui/login/static/templates/external_not_found_option.html b/internal/api/ui/login/static/templates/external_not_found_option.html
index fe30fca7a..9069261e8 100644
--- a/internal/api/ui/login/static/templates/external_not_found_option.html
+++ b/internal/api/ui/login/static/templates/external_not_found_option.html
@@ -118,13 +118,17 @@
+ {{ if .IsLinkingAllowed }}
-
+ {{ end }}
+
+ {{ if .IsCreationAllowed }}
+ {{ end }}
diff --git a/internal/api/ui/login/static/templates/external_register_overview.html b/internal/api/ui/login/static/templates/external_register_overview.html
deleted file mode 100644
index 07875a413..000000000
--- a/internal/api/ui/login/static/templates/external_register_overview.html
+++ /dev/null
@@ -1,128 +0,0 @@
-{{template "main-top" .}}
-
-
-
{{t "ExternalRegistrationUserOverview.Title"}}
-
{{t "ExternalRegistrationUserOverview.Description"}}
-
-
-
-
-
-
-
-
-
-{{template "main-bottom" .}}
diff --git a/internal/api/ui/login/static/templates/login.html b/internal/api/ui/login/static/templates/login.html
index b9ca4dcb4..c811e001c 100644
--- a/internal/api/ui/login/static/templates/login.html
+++ b/internal/api/ui/login/static/templates/login.html
@@ -47,7 +47,7 @@
{{ $reqid := .AuthReqID}}
{{range $provider := .IDPProviders}}
+ class="lgn-idp {{idpProviderClass $provider.IDPType}}">
{{$provider.Name}}
diff --git a/internal/api/ui/login/static/templates/register_option.html b/internal/api/ui/login/static/templates/register_option.html
index df7d6b4cc..8ff122d9e 100644
--- a/internal/api/ui/login/static/templates/register_option.html
+++ b/internal/api/ui/login/static/templates/register_option.html
@@ -27,7 +27,7 @@
{{ $reqid := .AuthReqID}}
{{range $provider := .IDPProviders}}
+ class="lgn-idp {{idpProviderClass $provider.IDPType}}">
{{$provider.Name}}
diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go
index 62271fd5d..edbd01e98 100644
--- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go
+++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go
@@ -17,8 +17,6 @@ import (
"github.com/zitadel/zitadel/internal/eventstore"
v1 "github.com/zitadel/zitadel/internal/eventstore/v1"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
- iam_model "github.com/zitadel/zitadel/internal/iam/model"
- iam_view_model "github.com/zitadel/zitadel/internal/iam/repository/view/model"
"github.com/zitadel/zitadel/internal/id"
project_view_model "github.com/zitadel/zitadel/internal/project/repository/view/model"
"github.com/zitadel/zitadel/internal/query"
@@ -81,7 +79,7 @@ type lockoutPolicyViewProvider interface {
}
type idpProviderViewProvider interface {
- IDPProvidersByAggregateIDAndState(string, string, iam_model.IDPConfigState) ([]*iam_view_model.IDPProviderView, error)
+ IDPLoginPolicyLinks(context.Context, string, *query.IDPLoginPolicyLinksSearchQuery, bool) (*query.IDPLoginPolicyLinks, error)
}
type idpUserLinksProvider interface {
@@ -554,13 +552,11 @@ func (repo *AuthRequestRepo) getLoginPolicyAndIDPProviders(ctx context.Context,
if !policy.AllowExternalIDPs {
return policy, nil, nil
}
- idpProviders, err := getLoginPolicyIDPProviders(repo.IDPProviderViewProvider, authz.GetInstance(ctx).InstanceID(), orgID, policy.IsDefault)
+ idpProviders, err := getLoginPolicyIDPProviders(ctx, repo.IDPProviderViewProvider, authz.GetInstance(ctx).InstanceID(), orgID, policy.IsDefault)
if err != nil {
return nil, nil, err
}
-
- providers := iam_model.IdpProviderViewsToDomain(idpProviders)
- return policy, providers, nil
+ return policy, idpProviders, nil
}
func (repo *AuthRequestRepo) fillPolicies(ctx context.Context, request *domain.AuthRequest) error {
@@ -850,16 +846,32 @@ func (repo *AuthRequestRepo) checkSelectedExternalIDP(request *domain.AuthReques
}
func (repo *AuthRequestRepo) checkExternalUserLogin(ctx context.Context, request *domain.AuthRequest, idpConfigID, externalUserID string) (err error) {
- var externalIDP *user_view_model.ExternalIDPView
- if request.RequestedOrgID != "" {
- externalIDP, err = repo.View.ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(externalUserID, idpConfigID, request.RequestedOrgID, request.InstanceID)
- } else {
- externalIDP, err = repo.View.ExternalIDPByExternalUserIDAndIDPConfigID(externalUserID, idpConfigID, request.InstanceID)
- }
+ idQuery, err := query.NewIDPUserLinkIDPIDSearchQuery(idpConfigID)
if err != nil {
return err
}
- user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, externalIDP.UserID, false)
+ externalIDQuery, err := query.NewIDPUserLinksExternalIDSearchQuery(externalUserID)
+ if err != nil {
+ return err
+ }
+ queries := []query.SearchQuery{
+ idQuery, externalIDQuery,
+ }
+ if request.RequestedOrgID != "" {
+ orgIDQuery, err := query.NewIDPUserLinksResourceOwnerSearchQuery(idpConfigID)
+ if err != nil {
+ return err
+ }
+ queries = append(queries, orgIDQuery)
+ }
+ links, err := repo.Query.IDPUserLinks(ctx, &query.IDPUserLinksSearchQuery{Queries: queries}, false)
+ if err != nil {
+ return err
+ }
+ if len(links.Links) != 1 {
+ return errors.ThrowNotFound(nil, "AUTH-Sf8sd", "Errors.ExternalIDP.NotFound")
+ }
+ user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, links.Links[0].UserID, false)
if err != nil {
return err
}
@@ -1233,19 +1245,25 @@ func setOrgID(ctx context.Context, orgViewProvider orgViewProvider, request *dom
return nil
}
-func getLoginPolicyIDPProviders(provider idpProviderViewProvider, iamID, orgID string, defaultPolicy bool) ([]*iam_model.IDPProviderView, error) {
- if defaultPolicy {
- idpProviders, err := provider.IDPProvidersByAggregateIDAndState(iamID, iamID, iam_model.IDPConfigStateActive)
- if err != nil {
- return nil, err
- }
- return iam_view_model.IDPProviderViewsToModel(idpProviders), nil
+func getLoginPolicyIDPProviders(ctx context.Context, provider idpProviderViewProvider, iamID, orgID string, defaultPolicy bool) ([]*domain.IDPProvider, error) {
+ resourceOwner := iamID
+ if !defaultPolicy {
+ resourceOwner = orgID
}
- idpProviders, err := provider.IDPProvidersByAggregateIDAndState(orgID, iamID, iam_model.IDPConfigStateActive)
+ links, err := provider.IDPLoginPolicyLinks(ctx, resourceOwner, &query.IDPLoginPolicyLinksSearchQuery{}, false)
if err != nil {
return nil, err
}
- return iam_view_model.IDPProviderViewsToModel(idpProviders), nil
+ providers := make([]*domain.IDPProvider, len(links.Links))
+ for i, link := range links.Links {
+ providers[i] = &domain.IDPProvider{
+ Type: link.OwnerType,
+ IDPConfigID: link.IDPID,
+ Name: link.IDPName,
+ IDPType: link.IDPType,
+ }
+ }
+ return providers, nil
}
func checkVerificationTimeMaxAge(verificationTime time.Time, lifetime time.Duration, request *domain.AuthRequest) bool {
diff --git a/internal/auth/repository/eventsourcing/eventstore/org.go b/internal/auth/repository/eventsourcing/eventstore/org.go
index d8d19e9ef..fa15bfc2c 100644
--- a/internal/auth/repository/eventsourcing/eventstore/org.go
+++ b/internal/auth/repository/eventsourcing/eventstore/org.go
@@ -22,14 +22,6 @@ type OrgRepository struct {
Query *query.Queries
}
-func (repo *OrgRepository) GetIDPConfigByID(ctx context.Context, idpConfigID string) (*iam_model.IDPConfigView, error) {
- idpConfig, err := repo.View.IDPConfigByID(idpConfigID, authz.GetInstance(ctx).InstanceID())
- if err != nil {
- return nil, err
- }
- return iam_view_model.IDPConfigViewToModel(idpConfig), nil
-}
-
func (repo *OrgRepository) GetMyPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error) {
policy, err := repo.Query.PasswordComplexityPolicyByOrg(ctx, true, authz.GetCtxData(ctx).OrgID, false)
if err != nil {
diff --git a/internal/auth/repository/eventsourcing/handler/handler.go b/internal/auth/repository/eventsourcing/handler/handler.go
index 6eb3a836c..161500de3 100644
--- a/internal/auth/repository/eventsourcing/handler/handler.go
+++ b/internal/auth/repository/eventsourcing/handler/handler.go
@@ -41,14 +41,6 @@ func Register(ctx context.Context, configs Configs, bulkLimit, errorCount uint64
handler{view, bulkLimit, configs.cycleDuration("UserSession"), errorCount, es}, queries),
newToken(ctx,
handler{view, bulkLimit, configs.cycleDuration("Token"), errorCount, es}),
- newIDPConfig(ctx,
- handler{view, bulkLimit, configs.cycleDuration("IDPConfig"), errorCount, es}),
- newIDPProvider(ctx,
- handler{view, bulkLimit, configs.cycleDuration("IDPProvider"), errorCount, es},
- systemDefaults, queries),
- newExternalIDP(ctx,
- handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount, es},
- systemDefaults, queries),
newRefreshToken(ctx, handler{view, bulkLimit, configs.cycleDuration("RefreshToken"), errorCount, es}),
newOrgProjectMapping(ctx, handler{view, bulkLimit, configs.cycleDuration("OrgProjectMapping"), errorCount, es}),
}
diff --git a/internal/auth/repository/eventsourcing/handler/idp_config.go b/internal/auth/repository/eventsourcing/handler/idp_config.go
deleted file mode 100644
index 342daa5e9..000000000
--- a/internal/auth/repository/eventsourcing/handler/idp_config.go
+++ /dev/null
@@ -1,142 +0,0 @@
-package handler
-
-import (
- "context"
-
- "github.com/zitadel/logging"
-
- "github.com/zitadel/zitadel/internal/eventstore"
- v1 "github.com/zitadel/zitadel/internal/eventstore/v1"
- "github.com/zitadel/zitadel/internal/eventstore/v1/models"
- "github.com/zitadel/zitadel/internal/eventstore/v1/query"
- "github.com/zitadel/zitadel/internal/eventstore/v1/spooler"
- iam_model "github.com/zitadel/zitadel/internal/iam/model"
- iam_view_model "github.com/zitadel/zitadel/internal/iam/repository/view/model"
- "github.com/zitadel/zitadel/internal/repository/instance"
- "github.com/zitadel/zitadel/internal/repository/org"
-)
-
-const (
- idpConfigTable = "auth.idp_configs2"
-)
-
-type IDPConfig struct {
- handler
- subscription *v1.Subscription
-}
-
-func newIDPConfig(ctx context.Context, h handler) *IDPConfig {
- idpConfig := &IDPConfig{
- handler: h,
- }
-
- idpConfig.subscribe(ctx)
-
- return idpConfig
-}
-
-func (i *IDPConfig) subscribe(ctx context.Context) {
- i.subscription = i.es.Subscribe(i.AggregateTypes()...)
- go func() {
- for event := range i.subscription.Events {
- query.ReduceEvent(ctx, i, event)
- }
- }()
-}
-
-func (i *IDPConfig) ViewModel() string {
- return idpConfigTable
-}
-
-func (i *IDPConfig) Subscription() *v1.Subscription {
- return i.subscription
-}
-
-func (_ *IDPConfig) AggregateTypes() []models.AggregateType {
- return []models.AggregateType{org.AggregateType, instance.AggregateType}
-}
-
-func (i *IDPConfig) CurrentSequence(instanceID string) (uint64, error) {
- sequence, err := i.view.GetLatestIDPConfigSequence(instanceID)
- if err != nil {
- return 0, err
- }
- return sequence.CurrentSequence, nil
-}
-
-func (i *IDPConfig) EventQuery(instanceIDs []string) (*models.SearchQuery, error) {
- sequences, err := i.view.GetLatestIDPConfigSequences(instanceIDs)
- if err != nil {
- return nil, err
- }
- return newSearchQuery(sequences, i.AggregateTypes(), instanceIDs), nil
-}
-
-func (i *IDPConfig) Reduce(event *models.Event) (err error) {
- switch event.AggregateType {
- case org.AggregateType:
- err = i.processIdpConfig(iam_model.IDPProviderTypeOrg, event)
- case instance.AggregateType:
- err = i.processIdpConfig(iam_model.IDPProviderTypeSystem, event)
- }
- return err
-}
-
-func (i *IDPConfig) processIdpConfig(providerType iam_model.IDPProviderType, event *models.Event) (err error) {
- idp := new(iam_view_model.IDPConfigView)
- switch eventstore.EventType(event.Type) {
- case org.IDPConfigAddedEventType,
- instance.IDPConfigAddedEventType:
- err = idp.AppendEvent(providerType, event)
- case org.IDPConfigChangedEventType, instance.IDPConfigChangedEventType,
- org.IDPOIDCConfigAddedEventType, instance.IDPOIDCConfigAddedEventType,
- org.IDPOIDCConfigChangedEventType, instance.IDPOIDCConfigChangedEventType,
- org.IDPJWTConfigAddedEventType, instance.IDPJWTConfigAddedEventType,
- org.IDPJWTConfigChangedEventType, instance.IDPJWTConfigChangedEventType:
- err = idp.SetData(event)
- if err != nil {
- return err
- }
- idp, err = i.view.IDPConfigByID(idp.IDPConfigID, event.InstanceID)
- if err != nil {
- return err
- }
- err = idp.AppendEvent(providerType, event)
- case org.IDPConfigDeactivatedEventType, instance.IDPConfigDeactivatedEventType,
- org.IDPConfigReactivatedEventType, instance.IDPConfigReactivatedEventType:
- err = idp.SetData(event)
- if err != nil {
- return err
- }
- idp, err = i.view.IDPConfigByID(idp.IDPConfigID, event.InstanceID)
- if err != nil {
- return err
- }
- err = idp.AppendEvent(providerType, event)
- case org.IDPConfigRemovedEventType, instance.IDPConfigRemovedEventType:
- err = idp.SetData(event)
- if err != nil {
- return err
- }
- return i.view.DeleteIDPConfig(idp.IDPConfigID, event)
- case instance.InstanceRemovedEventType:
- return i.view.DeleteInstanceIDPs(event)
- case org.OrgRemovedEventType:
- return i.view.UpdateOrgOwnerRemovedIDPs(event)
- default:
- return i.view.ProcessedIDPConfigSequence(event)
- }
- if err != nil {
- return err
- }
- return i.view.PutIDPConfig(idp, event)
-}
-
-func (i *IDPConfig) OnError(event *models.Event, err error) error {
- logging.WithFields("id", event.AggregateID).WithError(err).Warn("something went wrong in idp config handler")
- return spooler.HandleError(event, err, i.view.GetLatestIDPConfigFailedEvent, i.view.ProcessedIDPConfigFailedEvent, i.view.ProcessedIDPConfigSequence, i.errorCountUntilSkip)
-}
-
-func (i *IDPConfig) OnSuccess(instanceIDs []string) error {
- return spooler.HandleSuccess(i.view.UpdateIDPConfigSpoolerRunTimestamp, instanceIDs)
-}
diff --git a/internal/auth/repository/eventsourcing/handler/idp_providers.go b/internal/auth/repository/eventsourcing/handler/idp_providers.go
deleted file mode 100644
index 72b3d0651..000000000
--- a/internal/auth/repository/eventsourcing/handler/idp_providers.go
+++ /dev/null
@@ -1,204 +0,0 @@
-package handler
-
-import (
- "context"
-
- "github.com/zitadel/logging"
-
- "github.com/zitadel/zitadel/internal/config/systemdefaults"
- "github.com/zitadel/zitadel/internal/domain"
- "github.com/zitadel/zitadel/internal/eventstore"
- v1 "github.com/zitadel/zitadel/internal/eventstore/v1"
- "github.com/zitadel/zitadel/internal/eventstore/v1/models"
- es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
- "github.com/zitadel/zitadel/internal/eventstore/v1/query"
- "github.com/zitadel/zitadel/internal/eventstore/v1/spooler"
- iam_model "github.com/zitadel/zitadel/internal/iam/model"
- iam_view_model "github.com/zitadel/zitadel/internal/iam/repository/view/model"
- query2 "github.com/zitadel/zitadel/internal/query"
- "github.com/zitadel/zitadel/internal/repository/instance"
- "github.com/zitadel/zitadel/internal/repository/org"
-)
-
-const (
- idpProviderTable = "auth.idp_providers2"
-)
-
-type IDPProvider struct {
- handler
- systemDefaults systemdefaults.SystemDefaults
- subscription *v1.Subscription
- queries *query2.Queries
-}
-
-func newIDPProvider(
- ctx context.Context,
- h handler,
- defaults systemdefaults.SystemDefaults,
- queries *query2.Queries,
-) *IDPProvider {
- idpProvider := &IDPProvider{
- handler: h,
- systemDefaults: defaults,
- queries: queries,
- }
-
- idpProvider.subscribe(ctx)
-
- return idpProvider
-}
-
-func (i *IDPProvider) subscribe(ctx context.Context) {
- i.subscription = i.es.Subscribe(i.AggregateTypes()...)
- go func() {
- for event := range i.subscription.Events {
- query.ReduceEvent(ctx, i, event)
- }
- }()
-}
-
-func (i *IDPProvider) ViewModel() string {
- return idpProviderTable
-}
-
-func (i *IDPProvider) Subscription() *v1.Subscription {
- return i.subscription
-}
-
-func (_ *IDPProvider) AggregateTypes() []models.AggregateType {
- return []es_models.AggregateType{instance.AggregateType, org.AggregateType}
-}
-
-func (i *IDPProvider) CurrentSequence(instanceID string) (uint64, error) {
- sequence, err := i.view.GetLatestIDPProviderSequence(instanceID)
- if err != nil {
- return 0, err
- }
- return sequence.CurrentSequence, nil
-}
-
-func (i *IDPProvider) EventQuery(instanceIDs []string) (*es_models.SearchQuery, error) {
- sequences, err := i.view.GetLatestIDPProviderSequences(instanceIDs)
- if err != nil {
- return nil, err
- }
-
- return newSearchQuery(sequences, i.AggregateTypes(), instanceIDs), nil
-}
-
-func (i *IDPProvider) Reduce(event *models.Event) (err error) {
- switch event.AggregateType {
- case instance.AggregateType, org.AggregateType:
- err = i.processIdpProvider(event)
- }
- return err
-}
-
-func (i *IDPProvider) processIdpProvider(event *models.Event) (err error) {
- provider := new(iam_view_model.IDPProviderView)
- switch eventstore.EventType(event.Type) {
- case instance.LoginPolicyIDPProviderAddedEventType, org.LoginPolicyIDPProviderAddedEventType:
- err = provider.AppendEvent(event)
- if err != nil {
- return err
- }
- err = i.fillData(provider)
- case instance.LoginPolicyIDPProviderRemovedEventType, instance.LoginPolicyIDPProviderCascadeRemovedEventType,
- org.LoginPolicyIDPProviderRemovedEventType, org.LoginPolicyIDPProviderCascadeRemovedEventType:
- err = provider.SetData(event)
- if err != nil {
- return err
- }
- return i.view.DeleteIDPProvider(event.AggregateID, provider.IDPConfigID, event.InstanceID, event)
- case instance.IDPConfigChangedEventType, org.IDPConfigChangedEventType:
- esConfig := new(iam_view_model.IDPConfigView)
- providerType := iam_model.IDPProviderTypeSystem
- if event.AggregateID != event.InstanceID {
- providerType = iam_model.IDPProviderTypeOrg
- }
- err = esConfig.AppendEvent(providerType, event)
- if err != nil {
- return err
- }
- providers, err := i.view.IDPProvidersByIDPConfigID(esConfig.IDPConfigID, event.InstanceID)
- if err != nil {
- return err
- }
- config := new(query2.IDP)
- if event.AggregateID == event.InstanceID {
- config, err = i.getDefaultIDPConfig(event.InstanceID, esConfig.IDPConfigID)
- } else {
- config, err = i.getOrgIDPConfig(event.InstanceID, event.AggregateID, esConfig.IDPConfigID)
- }
- if err != nil {
- return err
- }
- for _, provider := range providers {
- i.fillConfigData(provider, config)
- }
- return i.view.PutIDPProviders(event, providers...)
- case org.LoginPolicyRemovedEventType:
- return i.view.DeleteIDPProvidersByAggregateID(event.AggregateID, event.InstanceID, event)
- case instance.InstanceRemovedEventType:
- return i.view.DeleteInstanceIDPProviders(event)
- case org.OrgRemovedEventType:
- return i.view.UpdateOrgOwnerRemovedIDPProviders(event)
- default:
- return i.view.ProcessedIDPProviderSequence(event)
- }
- if err != nil {
- return err
- }
- return i.view.PutIDPProvider(provider, event)
-}
-
-func (i *IDPProvider) fillData(provider *iam_view_model.IDPProviderView) (err error) {
- var config *query2.IDP
- if provider.IDPProviderType == int32(iam_model.IDPProviderTypeSystem) {
- config, err = i.getDefaultIDPConfig(provider.InstanceID, provider.IDPConfigID)
- } else {
- config, err = i.getOrgIDPConfig(provider.InstanceID, provider.AggregateID, provider.IDPConfigID)
- }
- if err != nil {
- return err
- }
- i.fillConfigData(provider, config)
- return nil
-}
-
-func (i *IDPProvider) fillConfigData(provider *iam_view_model.IDPProviderView, config *query2.IDP) {
- provider.Name = config.Name
- provider.StylingType = int32(config.StylingType)
- if config.OIDCIDP != nil {
- provider.IDPConfigType = int32(domain.IDPConfigTypeOIDC)
- } else if config.JWTIDP != nil {
- provider.IDPConfigType = int32(domain.IDPConfigTypeJWT)
- }
- switch config.State {
- case domain.IDPConfigStateActive:
- provider.IDPState = int32(iam_model.IDPConfigStateActive)
- case domain.IDPConfigStateInactive:
- provider.IDPState = int32(iam_model.IDPConfigStateActive)
- case domain.IDPConfigStateRemoved:
- provider.IDPState = int32(iam_model.IDPConfigStateRemoved)
- default:
- provider.IDPState = int32(iam_model.IDPConfigStateActive)
- }
-}
-
-func (i *IDPProvider) OnError(event *es_models.Event, err error) error {
- logging.WithFields("id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler")
- return spooler.HandleError(event, err, i.view.GetLatestIDPProviderFailedEvent, i.view.ProcessedIDPProviderFailedEvent, i.view.ProcessedIDPProviderSequence, i.errorCountUntilSkip)
-}
-
-func (i *IDPProvider) OnSuccess(instanceIDs []string) error {
- return spooler.HandleSuccess(i.view.UpdateIDPProviderSpoolerRunTimestamp, instanceIDs)
-}
-
-func (i *IDPProvider) getOrgIDPConfig(instanceID, aggregateID, idpConfigID string) (*query2.IDP, error) {
- return i.queries.IDPByIDAndResourceOwner(withInstanceID(context.Background(), instanceID), false, idpConfigID, aggregateID, false)
-}
-
-func (i *IDPProvider) getDefaultIDPConfig(instanceID, idpConfigID string) (*query2.IDP, error) {
- return i.queries.IDPByIDAndResourceOwner(withInstanceID(context.Background(), instanceID), false, idpConfigID, instanceID, false)
-}
diff --git a/internal/auth/repository/eventsourcing/handler/user_external_idps.go b/internal/auth/repository/eventsourcing/handler/user_external_idps.go
deleted file mode 100644
index 4733cca4a..000000000
--- a/internal/auth/repository/eventsourcing/handler/user_external_idps.go
+++ /dev/null
@@ -1,194 +0,0 @@
-package handler
-
-import (
- "context"
-
- "github.com/zitadel/logging"
-
- "github.com/zitadel/zitadel/internal/config/systemdefaults"
- caos_errs "github.com/zitadel/zitadel/internal/errors"
- "github.com/zitadel/zitadel/internal/eventstore"
- v1 "github.com/zitadel/zitadel/internal/eventstore/v1"
- es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
- "github.com/zitadel/zitadel/internal/eventstore/v1/query"
- "github.com/zitadel/zitadel/internal/eventstore/v1/spooler"
- iam_model "github.com/zitadel/zitadel/internal/iam/model"
- iam_view_model "github.com/zitadel/zitadel/internal/iam/repository/view/model"
- query2 "github.com/zitadel/zitadel/internal/query"
- "github.com/zitadel/zitadel/internal/repository/instance"
- "github.com/zitadel/zitadel/internal/repository/org"
- "github.com/zitadel/zitadel/internal/repository/user"
- usr_view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
-)
-
-const (
- externalIDPTable = "auth.user_external_idps2"
-)
-
-type ExternalIDP struct {
- handler
- systemDefaults systemdefaults.SystemDefaults
- subscription *v1.Subscription
- queries *query2.Queries
-}
-
-func newExternalIDP(
- ctx context.Context,
- handler handler,
- defaults systemdefaults.SystemDefaults,
- queries *query2.Queries,
-) *ExternalIDP {
- h := &ExternalIDP{
- handler: handler,
- systemDefaults: defaults,
- queries: queries,
- }
-
- h.subscribe(ctx)
-
- return h
-}
-
-func (i *ExternalIDP) subscribe(ctx context.Context) {
- i.subscription = i.es.Subscribe(i.AggregateTypes()...)
- go func() {
- for event := range i.subscription.Events {
- query.ReduceEvent(ctx, i, event)
- }
- }()
-}
-
-func (i *ExternalIDP) ViewModel() string {
- return externalIDPTable
-}
-
-func (i *ExternalIDP) Subscription() *v1.Subscription {
- return i.subscription
-}
-
-func (_ *ExternalIDP) AggregateTypes() []es_models.AggregateType {
- return []es_models.AggregateType{user.AggregateType, instance.AggregateType, org.AggregateType}
-}
-
-func (i *ExternalIDP) CurrentSequence(instanceID string) (uint64, error) {
- sequence, err := i.view.GetLatestExternalIDPSequence(instanceID)
- if err != nil {
- return 0, err
- }
- return sequence.CurrentSequence, nil
-}
-
-func (i *ExternalIDP) EventQuery(instanceIDs []string) (*es_models.SearchQuery, error) {
- sequences, err := i.view.GetLatestExternalIDPSequences(instanceIDs)
- if err != nil {
- return nil, err
- }
- return newSearchQuery(sequences, i.AggregateTypes(), instanceIDs), nil
-}
-
-func (i *ExternalIDP) Reduce(event *es_models.Event) (err error) {
- switch event.AggregateType {
- case user.AggregateType:
- err = i.processUser(event)
- case instance.AggregateType, org.AggregateType:
- err = i.processIdpConfig(event)
- }
- return err
-}
-
-func (i *ExternalIDP) processUser(event *es_models.Event) (err error) {
- externalIDP := new(usr_view_model.ExternalIDPView)
- switch eventstore.EventType(event.Type) {
- case user.UserIDPLinkAddedType:
- err = externalIDP.AppendEvent(event)
- if err != nil {
- return err
- }
- err = i.fillData(externalIDP)
- case user.UserIDPLinkRemovedType, user.UserIDPLinkCascadeRemovedType:
- err = externalIDP.SetData(event)
- if err != nil {
- return err
- }
- return i.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.InstanceID, event)
- case user.UserRemovedType:
- return i.view.DeleteExternalIDPsByUserID(event.AggregateID, event.InstanceID, event)
- default:
- return i.view.ProcessedExternalIDPSequence(event)
- }
- if err != nil {
- return err
- }
- return i.view.PutExternalIDP(externalIDP, event)
-}
-
-func (i *ExternalIDP) processIdpConfig(event *es_models.Event) (err error) {
- switch eventstore.EventType(event.Type) {
- case instance.IDPConfigChangedEventType, org.IDPConfigChangedEventType:
- configView := new(iam_view_model.IDPConfigView)
- var config *query2.IDP
- if eventstore.EventType(event.Type) == instance.IDPConfigChangedEventType {
- err = configView.AppendEvent(iam_model.IDPProviderTypeSystem, event)
- } else {
- err = configView.AppendEvent(iam_model.IDPProviderTypeOrg, event)
- }
- if err != nil {
- return err
- }
- exterinalIDPs, err := i.view.ExternalIDPsByIDPConfigID(configView.IDPConfigID, event.InstanceID)
- if err != nil {
- return err
- }
- if event.AggregateType == instance.AggregateType {
- config, err = i.getDefaultIDPConfig(event.InstanceID, configView.IDPConfigID)
- } else {
- config, err = i.getOrgIDPConfig(event.InstanceID, event.AggregateID, configView.IDPConfigID)
- }
- if err != nil {
- return err
- }
- for _, provider := range exterinalIDPs {
- i.fillConfigData(provider, config)
- }
- return i.view.PutExternalIDPs(event, exterinalIDPs...)
- case instance.InstanceRemovedEventType:
- return i.view.DeleteInstanceExternalIDPs(event)
- case org.OrgRemovedEventType:
- return i.view.UpdateOrgOwnerRemovedExternalIDPs(event)
- default:
- return i.view.ProcessedExternalIDPSequence(event)
- }
-}
-
-func (i *ExternalIDP) fillData(externalIDP *usr_view_model.ExternalIDPView) error {
- config, err := i.getOrgIDPConfig(externalIDP.InstanceID, externalIDP.ResourceOwner, externalIDP.IDPConfigID)
- if caos_errs.IsNotFound(err) {
- config, err = i.getDefaultIDPConfig(externalIDP.InstanceID, externalIDP.IDPConfigID)
- }
- if err != nil {
- return err
- }
- i.fillConfigData(externalIDP, config)
- return nil
-}
-
-func (i *ExternalIDP) fillConfigData(externalIDP *usr_view_model.ExternalIDPView, config *query2.IDP) {
- externalIDP.IDPName = config.Name
-}
-
-func (i *ExternalIDP) OnError(event *es_models.Event, err error) error {
- logging.WithFields("id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler")
- return spooler.HandleError(event, err, i.view.GetLatestExternalIDPFailedEvent, i.view.ProcessedExternalIDPFailedEvent, i.view.ProcessedExternalIDPSequence, i.errorCountUntilSkip)
-}
-
-func (i *ExternalIDP) OnSuccess(instanceIDs []string) error {
- return spooler.HandleSuccess(i.view.UpdateExternalIDPSpoolerRunTimestamp, instanceIDs)
-}
-
-func (i *ExternalIDP) getOrgIDPConfig(instanceID, aggregateID, idpConfigID string) (*query2.IDP, error) {
- return i.queries.IDPByIDAndResourceOwner(withInstanceID(context.Background(), instanceID), false, idpConfigID, aggregateID, false)
-}
-
-func (i *ExternalIDP) getDefaultIDPConfig(instanceID, idpConfigID string) (*query2.IDP, error) {
- return i.queries.IDPByIDAndResourceOwner(withInstanceID(context.Background(), instanceID), false, idpConfigID, instanceID, false)
-}
diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go
index bc19fdab6..34df290c7 100644
--- a/internal/auth/repository/eventsourcing/repository.go
+++ b/internal/auth/repository/eventsourcing/repository.go
@@ -80,7 +80,7 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, c
UserViewProvider: view,
UserCommandProvider: command,
UserEventProvider: &userRepo,
- IDPProviderViewProvider: view,
+ IDPProviderViewProvider: queries,
IDPUserLinksProvider: queries,
LockoutPolicyViewProvider: queries,
LoginPolicyViewProvider: queries,
diff --git a/internal/auth/repository/eventsourcing/view/external_idps.go b/internal/auth/repository/eventsourcing/view/external_idps.go
deleted file mode 100644
index 3379b70ae..000000000
--- a/internal/auth/repository/eventsourcing/view/external_idps.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package view
-
-import (
- "github.com/zitadel/zitadel/internal/errors"
- "github.com/zitadel/zitadel/internal/eventstore/v1/models"
- "github.com/zitadel/zitadel/internal/user/repository/view"
- "github.com/zitadel/zitadel/internal/user/repository/view/model"
- global_view "github.com/zitadel/zitadel/internal/view/repository"
-)
-
-const (
- externalIDPTable = "auth.user_external_idps2"
-)
-
-func (v *View) ExternalIDPByExternalUserIDAndIDPConfigID(externalUserID, idpConfigID, instanceID string) (*model.ExternalIDPView, error) {
- return view.ExternalIDPByExternalUserIDAndIDPConfigID(v.Db, externalIDPTable, externalUserID, idpConfigID, instanceID)
-}
-
-func (v *View) ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(externalUserID, idpConfigID, resourceOwner, instanceID string) (*model.ExternalIDPView, error) {
- return view.ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(v.Db, externalIDPTable, externalUserID, idpConfigID, resourceOwner, instanceID)
-}
-
-func (v *View) ExternalIDPsByIDPConfigID(idpConfigID, instanceID string) ([]*model.ExternalIDPView, error) {
- return view.ExternalIDPsByIDPConfigID(v.Db, externalIDPTable, idpConfigID, instanceID)
-}
-
-func (v *View) PutExternalIDP(externalIDP *model.ExternalIDPView, event *models.Event) error {
- err := view.PutExternalIDP(v.Db, externalIDPTable, externalIDP)
- if err != nil {
- return err
- }
- return v.ProcessedExternalIDPSequence(event)
-}
-
-func (v *View) PutExternalIDPs(event *models.Event, externalIDPs ...*model.ExternalIDPView) error {
- err := view.PutExternalIDPs(v.Db, externalIDPTable, externalIDPs...)
- if err != nil {
- return err
- }
- return v.ProcessedExternalIDPSequence(event)
-}
-
-func (v *View) DeleteExternalIDP(externalUserID, idpConfigID, instanceID string, event *models.Event) error {
- err := view.DeleteExternalIDP(v.Db, externalIDPTable, externalUserID, idpConfigID, instanceID)
- if err != nil && !errors.IsNotFound(err) {
- return err
- }
- return v.ProcessedExternalIDPSequence(event)
-}
-
-func (v *View) DeleteExternalIDPsByUserID(userID, instanceID string, event *models.Event) error {
- err := view.DeleteExternalIDPsByUserID(v.Db, externalIDPTable, userID, instanceID)
- if err != nil {
- return err
- }
- return v.ProcessedExternalIDPSequence(event)
-}
-
-func (v *View) DeleteInstanceExternalIDPs(event *models.Event) error {
- err := view.DeleteInstanceExternalIDPs(v.Db, externalIDPTable, event.InstanceID)
- if err != nil && !errors.IsNotFound(err) {
- return err
- }
- return v.ProcessedExternalIDPSequence(event)
-}
-
-func (v *View) UpdateOrgOwnerRemovedExternalIDPs(event *models.Event) error {
- err := view.UpdateOrgOwnerRemovedExternalIDPs(v.Db, externalIDPTable, event.InstanceID, event.ResourceOwner)
- if err != nil && !errors.IsNotFound(err) {
- return err
- }
- return v.ProcessedExternalIDPSequence(event)
-}
-
-func (v *View) GetLatestExternalIDPSequence(instanceID string) (*global_view.CurrentSequence, error) {
- return v.latestSequence(externalIDPTable, instanceID)
-}
-
-func (v *View) GetLatestExternalIDPSequences(instanceIDs []string) ([]*global_view.CurrentSequence, error) {
- return v.latestSequences(externalIDPTable, instanceIDs)
-}
-
-func (v *View) ProcessedExternalIDPSequence(event *models.Event) error {
- return v.saveCurrentSequence(externalIDPTable, event)
-}
-
-func (v *View) UpdateExternalIDPSpoolerRunTimestamp(instanceIDs []string) error {
- return v.updateSpoolerRunSequence(externalIDPTable, instanceIDs)
-}
-
-func (v *View) GetLatestExternalIDPFailedEvent(sequence uint64, instanceID string) (*global_view.FailedEvent, error) {
- return v.latestFailedEvent(externalIDPTable, instanceID, sequence)
-}
-
-func (v *View) ProcessedExternalIDPFailedEvent(failedEvent *global_view.FailedEvent) error {
- return v.saveFailedEvent(failedEvent)
-}
diff --git a/internal/auth/repository/eventsourcing/view/idp_configs.go b/internal/auth/repository/eventsourcing/view/idp_configs.go
deleted file mode 100644
index 439b63711..000000000
--- a/internal/auth/repository/eventsourcing/view/idp_configs.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package view
-
-import (
- "github.com/zitadel/zitadel/internal/errors"
- "github.com/zitadel/zitadel/internal/eventstore/v1/models"
- iam_model "github.com/zitadel/zitadel/internal/iam/model"
- "github.com/zitadel/zitadel/internal/iam/repository/view"
- iam_es_model "github.com/zitadel/zitadel/internal/iam/repository/view/model"
- global_view "github.com/zitadel/zitadel/internal/view/repository"
-)
-
-const (
- idpConfigTable = "auth.idp_configs2"
-)
-
-func (v *View) IDPConfigByID(idpID, instanceID string) (*iam_es_model.IDPConfigView, error) {
- return view.IDPByID(v.Db, idpConfigTable, idpID, instanceID)
-}
-
-func (v *View) GetIDPConfigsByAggregateID(aggregateID, instanceID string) ([]*iam_es_model.IDPConfigView, error) {
- return view.GetIDPConfigsByAggregateID(v.Db, idpConfigTable, aggregateID, instanceID)
-}
-
-func (v *View) SearchIDPConfigs(request *iam_model.IDPConfigSearchRequest) ([]*iam_es_model.IDPConfigView, uint64, error) {
- return view.SearchIDPs(v.Db, idpConfigTable, request)
-}
-
-func (v *View) PutIDPConfig(idp *iam_es_model.IDPConfigView, event *models.Event) error {
- err := view.PutIDP(v.Db, idpConfigTable, idp)
- if err != nil {
- return err
- }
- return v.ProcessedIDPConfigSequence(event)
-}
-
-func (v *View) DeleteIDPConfig(idpID string, event *models.Event) error {
- err := view.DeleteIDP(v.Db, idpConfigTable, idpID, event.InstanceID)
- if err != nil && !errors.IsNotFound(err) {
- return err
- }
- return v.ProcessedIDPConfigSequence(event)
-}
-
-func (v *View) DeleteInstanceIDPs(event *models.Event) error {
- err := view.DeleteInstanceIDPs(v.Db, idpConfigTable, event.InstanceID)
- if err != nil && !errors.IsNotFound(err) {
- return err
- }
- return v.ProcessedIDPConfigSequence(event)
-}
-
-func (v *View) UpdateOrgOwnerRemovedIDPs(event *models.Event) error {
- err := view.UpdateOrgOwnerRemovedIDPs(v.Db, idpConfigTable, event.InstanceID, event.AggregateID)
- if err != nil && !errors.IsNotFound(err) {
- return err
- }
- return v.ProcessedIDPConfigSequence(event)
-}
-
-func (v *View) GetLatestIDPConfigSequence(instanceID string) (*global_view.CurrentSequence, error) {
- return v.latestSequence(idpConfigTable, instanceID)
-}
-
-func (v *View) GetLatestIDPConfigSequences(instanceIDs []string) ([]*global_view.CurrentSequence, error) {
- return v.latestSequences(idpConfigTable, instanceIDs)
-}
-
-func (v *View) ProcessedIDPConfigSequence(event *models.Event) error {
- return v.saveCurrentSequence(idpConfigTable, event)
-}
-
-func (v *View) UpdateIDPConfigSpoolerRunTimestamp(instanceIDs []string) error {
- return v.updateSpoolerRunSequence(idpConfigTable, instanceIDs)
-}
-
-func (v *View) GetLatestIDPConfigFailedEvent(sequence uint64, instanceID string) (*global_view.FailedEvent, error) {
- return v.latestFailedEvent(idpConfigTable, instanceID, sequence)
-}
-
-func (v *View) ProcessedIDPConfigFailedEvent(failedEvent *global_view.FailedEvent) error {
- return v.saveFailedEvent(failedEvent)
-}
diff --git a/internal/auth/repository/eventsourcing/view/idp_providers.go b/internal/auth/repository/eventsourcing/view/idp_providers.go
deleted file mode 100644
index ead8f7c0a..000000000
--- a/internal/auth/repository/eventsourcing/view/idp_providers.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package view
-
-import (
- "github.com/zitadel/zitadel/internal/errors"
- "github.com/zitadel/zitadel/internal/eventstore/v1/models"
- iam_model "github.com/zitadel/zitadel/internal/iam/model"
- "github.com/zitadel/zitadel/internal/iam/repository/view"
- "github.com/zitadel/zitadel/internal/iam/repository/view/model"
- global_view "github.com/zitadel/zitadel/internal/view/repository"
-)
-
-const (
- idpProviderTable = "auth.idp_providers2"
-)
-
-func (v *View) IDPProviderByAggregateAndIDPConfigID(aggregateID, idpConfigID, instanceID string) (*model.IDPProviderView, error) {
- return view.GetIDPProviderByAggregateIDAndConfigID(v.Db, idpProviderTable, aggregateID, idpConfigID, instanceID)
-}
-
-func (v *View) IDPProvidersByIDPConfigID(idpConfigID, instanceID string) ([]*model.IDPProviderView, error) {
- return view.IDPProvidersByIdpConfigID(v.Db, idpProviderTable, idpConfigID, instanceID)
-}
-
-func (v *View) IDPProvidersByAggregateIDAndState(aggregateID, instanceID string, idpConfigState iam_model.IDPConfigState) ([]*model.IDPProviderView, error) {
- return view.IDPProvidersByAggregateIDAndState(v.Db, idpProviderTable, aggregateID, instanceID, idpConfigState)
-}
-
-func (v *View) SearchIDPProviders(request *iam_model.IDPProviderSearchRequest) ([]*model.IDPProviderView, uint64, error) {
- return view.SearchIDPProviders(v.Db, idpProviderTable, request)
-}
-
-func (v *View) PutIDPProvider(provider *model.IDPProviderView, event *models.Event) error {
- err := view.PutIDPProvider(v.Db, idpProviderTable, provider)
- if err != nil {
- return err
- }
- return v.ProcessedIDPProviderSequence(event)
-}
-
-func (v *View) PutIDPProviders(event *models.Event, providers ...*model.IDPProviderView) error {
- err := view.PutIDPProviders(v.Db, idpProviderTable, providers...)
- if err != nil {
- return err
- }
- return v.ProcessedIDPProviderSequence(event)
-}
-
-func (v *View) DeleteIDPProvider(aggregateID, idpConfigID, instanceID string, event *models.Event) error {
- err := view.DeleteIDPProvider(v.Db, idpProviderTable, aggregateID, idpConfigID, instanceID)
- if err != nil && !errors.IsNotFound(err) {
- return err
- }
- return v.ProcessedIDPProviderSequence(event)
-}
-
-func (v *View) DeleteIDPProvidersByAggregateID(aggregateID, instanceID string, event *models.Event) error {
- err := view.DeleteIDPProvidersByAggregateID(v.Db, idpProviderTable, aggregateID, instanceID)
- if err != nil && !errors.IsNotFound(err) {
- return err
- }
- return v.ProcessedIDPProviderSequence(event)
-}
-
-func (v *View) DeleteInstanceIDPProviders(event *models.Event) error {
- err := view.DeleteInstanceIDPProviders(v.Db, idpProviderTable, event.InstanceID)
- if err != nil && !errors.IsNotFound(err) {
- return err
- }
- return v.ProcessedIDPProviderSequence(event)
-}
-
-func (v *View) UpdateOrgOwnerRemovedIDPProviders(event *models.Event) error {
- err := view.UpdateOrgOwnerRemovedIDPProviders(v.Db, idpProviderTable, event.InstanceID, event.AggregateID)
- if err != nil && !errors.IsNotFound(err) {
- return err
- }
- return v.ProcessedIDPProviderSequence(event)
-}
-
-func (v *View) GetLatestIDPProviderSequence(instanceID string) (*global_view.CurrentSequence, error) {
- return v.latestSequence(idpProviderTable, instanceID)
-}
-
-func (v *View) GetLatestIDPProviderSequences(instanceIDs []string) ([]*global_view.CurrentSequence, error) {
- return v.latestSequences(idpProviderTable, instanceIDs)
-}
-
-func (v *View) ProcessedIDPProviderSequence(event *models.Event) error {
- return v.saveCurrentSequence(idpProviderTable, event)
-}
-
-func (v *View) UpdateIDPProviderSpoolerRunTimestamp(instanceIDs []string) error {
- return v.updateSpoolerRunSequence(idpProviderTable, instanceIDs)
-}
-
-func (v *View) GetLatestIDPProviderFailedEvent(sequence uint64, instanceID string) (*global_view.FailedEvent, error) {
- return v.latestFailedEvent(idpProviderTable, instanceID, sequence)
-}
-
-func (v *View) ProcessedIDPProviderFailedEvent(failedEvent *global_view.FailedEvent) error {
- return v.saveFailedEvent(failedEvent)
-}
diff --git a/internal/auth/repository/org.go b/internal/auth/repository/org.go
index 0781ea274..13dd726fc 100644
--- a/internal/auth/repository/org.go
+++ b/internal/auth/repository/org.go
@@ -8,7 +8,6 @@ import (
)
type OrgRepository interface {
- GetIDPConfigByID(ctx context.Context, idpConfigID string) (*iam_model.IDPConfigView, error)
GetMyPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error)
GetLoginText(ctx context.Context, orgID string) ([]*domain.CustomText, error)
}
diff --git a/internal/command/idp.go b/internal/command/idp.go
index f6de9cdf8..811bc2d1d 100644
--- a/internal/command/idp.go
+++ b/internal/command/idp.go
@@ -1,6 +1,12 @@
package command
-import "github.com/zitadel/zitadel/internal/repository/idp"
+import (
+ "context"
+
+ "github.com/zitadel/zitadel/internal/api/authz"
+ "github.com/zitadel/zitadel/internal/command/preparation"
+ "github.com/zitadel/zitadel/internal/repository/idp"
+)
type GenericOAuthProvider struct {
Name string
@@ -52,3 +58,34 @@ type LDAPProvider struct {
LDAPAttributes idp.LDAPAttributes
IDPOptions idp.Options
}
+
+func ExistsIDP(ctx context.Context, filter preparation.FilterToQueryReducer, id, orgID string) (exists bool, err error) {
+ writeModel := NewOrgIDPRemoveWriteModel(orgID, id)
+ events, err := filter(ctx, writeModel.Query())
+ if err != nil {
+ return false, err
+ }
+
+ if len(events) > 0 {
+ writeModel.AppendEvents(events...)
+ if err := writeModel.Reduce(); err != nil {
+ return false, err
+ }
+ return writeModel.State.Exists(), nil
+ }
+
+ instanceWriteModel := NewInstanceIDPRemoveWriteModel(authz.GetInstance(ctx).InstanceID(), id)
+ events, err = filter(ctx, instanceWriteModel.Query())
+ if err != nil {
+ return false, err
+ }
+
+ if len(events) == 0 {
+ return false, nil
+ }
+ instanceWriteModel.AppendEvents(events...)
+ if err := instanceWriteModel.Reduce(); err != nil {
+ return false, err
+ }
+ return instanceWriteModel.State.Exists(), nil
+}
diff --git a/internal/command/instance_idp.go b/internal/command/instance_idp.go
index 37b581f55..0139b996b 100644
--- a/internal/command/instance_idp.go
+++ b/internal/command/instance_idp.go
@@ -236,6 +236,23 @@ func (c *Commands) DeleteInstanceProvider(ctx context.Context, id string) (*doma
return pushedEventsToObjectDetails(pushedEvents), nil
}
+func ExistsInstanceIDP(ctx context.Context, filter preparation.FilterToQueryReducer, id string) (exists bool, err error) {
+ instanceWriteModel := NewInstanceIDPRemoveWriteModel(authz.GetInstance(ctx).InstanceID(), id)
+ events, err := filter(ctx, instanceWriteModel.Query())
+ if err != nil {
+ return false, err
+ }
+
+ if len(events) == 0 {
+ return false, nil
+ }
+ instanceWriteModel.AppendEvents(events...)
+ if err := instanceWriteModel.Reduce(); err != nil {
+ return false, err
+ }
+ return instanceWriteModel.State.Exists(), nil
+}
+
func (c *Commands) prepareAddInstanceOAuthProvider(a *instance.Aggregate, writeModel *InstanceOAuthIDPWriteModel, provider GenericOAuthProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
diff --git a/internal/command/instance_policy_login.go b/internal/command/instance_policy_login.go
index 395c10723..d2fa5cb2d 100644
--- a/internal/command/instance_policy_login.go
+++ b/internal/command/instance_policy_login.go
@@ -41,8 +41,8 @@ func (c *Commands) AddIDPProviderToDefaultLoginPolicy(ctx context.Context, idpPr
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-GVDfe", "Errors.IAM.LoginPolicy.NotFound")
}
- _, err = c.getInstanceIDPConfigByID(ctx, idpProvider.IDPConfigID)
- if err != nil {
+ exists, err := ExistsInstanceIDP(ctx, c.eventstore.Filter, idpProvider.IDPConfigID)
+ if err != nil || !exists {
return nil, caos_errs.ThrowPreconditionFailed(err, "INSTANCE-m8fsd", "Errors.IDPConfig.NotExisting")
}
idpModel := NewInstanceIdentityProviderWriteModel(ctx, idpProvider.IDPConfigID)
diff --git a/internal/command/org_idp.go b/internal/command/org_idp.go
index fc559fdd9..734d0c5b7 100644
--- a/internal/command/org_idp.go
+++ b/internal/command/org_idp.go
@@ -225,6 +225,23 @@ func (c *Commands) DeleteOrgProvider(ctx context.Context, resourceOwner, id stri
return pushedEventsToObjectDetails(pushedEvents), nil
}
+func ExistsOrgIDP(ctx context.Context, filter preparation.FilterToQueryReducer, id, orgID string) (exists bool, err error) {
+ writeModel := NewOrgIDPRemoveWriteModel(orgID, id)
+ events, err := filter(ctx, writeModel.Query())
+ if err != nil {
+ return false, err
+ }
+
+ if len(events) == 0 {
+ return false, nil
+ }
+ writeModel.AppendEvents(events...)
+ if err := writeModel.Reduce(); err != nil {
+ return false, err
+ }
+ return writeModel.State.Exists(), nil
+}
+
func (c *Commands) prepareAddOrgOAuthProvider(a *org.Aggregate, writeModel *OrgOAuthIDPWriteModel, provider GenericOAuthProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
diff --git a/internal/command/org_policy_login.go b/internal/command/org_policy_login.go
index bee73e079..b8e551b25 100644
--- a/internal/command/org_policy_login.go
+++ b/internal/command/org_policy_login.go
@@ -146,12 +146,13 @@ func (c *Commands) AddIDPToLoginPolicy(ctx context.Context, resourceOwner string
return nil, caos_errs.ThrowNotFound(nil, "Org-Ffgw2", "Errors.Org.LoginPolicy.NotFound")
}
+ var exists bool
if idpProvider.Type == domain.IdentityProviderTypeOrg {
- _, err = c.getOrgIDPConfigByID(ctx, idpProvider.IDPConfigID, resourceOwner)
+ exists, err = ExistsOrgIDP(ctx, c.eventstore.Filter, idpProvider.IDPConfigID, resourceOwner)
} else {
- _, err = c.getInstanceIDPConfigByID(ctx, idpProvider.IDPConfigID)
+ exists, err = ExistsInstanceIDP(ctx, c.eventstore.Filter, idpProvider.IDPConfigID)
}
- if err != nil {
+ if !exists || err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "Org-3N9fs", "Errors.IDPConfig.NotExisting")
}
idpModel := NewOrgIdentityProviderWriteModel(resourceOwner, idpProvider.IDPConfigID)
diff --git a/internal/command/user_idp_link.go b/internal/command/user_idp_link.go
index ea0a3381c..9db06bf6a 100644
--- a/internal/command/user_idp_link.go
+++ b/internal/command/user_idp_link.go
@@ -66,14 +66,12 @@ func (c *Commands) addUserIDPLink(ctx context.Context, human *eventstore.Aggrega
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-6m9Kd", "Errors.User.ExternalIDP.Invalid")
}
- _, err := c.getOrgIDPConfigByID(ctx, link.IDPConfigID, human.ResourceOwner)
- if caos_errs.IsNotFound(err) {
- _, err = c.getInstanceIDPConfigByID(ctx, link.IDPConfigID)
- }
- if err != nil {
+ exists, err := ExistsIDP(ctx, c.eventstore.Filter, link.IDPConfigID, human.ResourceOwner)
+ if !exists || err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-39nfs", "Errors.IDPConfig.NotExisting")
}
return user.NewUserIDPLinkAddedEvent(ctx, human, link.IDPConfigID, link.DisplayName, link.ExternalUserID), nil
+
}
func (c *Commands) RemoveUserIDPLink(ctx context.Context, link *domain.UserIDPLink) (*domain.ObjectDetails, error) {
diff --git a/internal/domain/idp.go b/internal/domain/idp.go
index 9df87f6ba..af34731c1 100644
--- a/internal/domain/idp.go
+++ b/internal/domain/idp.go
@@ -34,3 +34,12 @@ const (
IDPTypeGitLabSelfHosted
IDPTypeGoogle
)
+
+func (t IDPType) GetCSSClass() string {
+ switch t { //nolint:exhaustive
+ case IDPTypeGoogle:
+ return "google"
+ default:
+ return ""
+ }
+}
diff --git a/internal/domain/idp_config.go b/internal/domain/idp_config.go
index 762d0da5d..e8f8b29dc 100644
--- a/internal/domain/idp_config.go
+++ b/internal/domain/idp_config.go
@@ -69,6 +69,8 @@ type JWTIDPConfig struct {
HeaderName string
}
+// IDPConfigType
+// Deprecated: use [IDPType]
type IDPConfigType int32
const (
@@ -85,6 +87,8 @@ func (f IDPConfigType) Valid() bool {
return f >= 0 && f < idpConfigTypeCount
}
+// IDPConfigState
+// Deprecated: use [IDPStateType]
type IDPConfigState int32
const (
@@ -104,6 +108,8 @@ func (s IDPConfigState) Exists() bool {
return s != IDPConfigStateUnspecified && s != IDPConfigStateRemoved
}
+// IDPConfigStylingType
+// Deprecated: use a concrete provider
type IDPConfigStylingType int32
const (
diff --git a/internal/domain/policy_login.go b/internal/domain/policy_login.go
index 823a51220..a0c9d4bce 100644
--- a/internal/domain/policy_login.go
+++ b/internal/domain/policy_login.go
@@ -55,10 +55,10 @@ type IDPProvider struct {
Type IdentityProviderType
IDPConfigID string
- Name string
- StylingType IDPConfigStylingType
- IDPConfigType IDPConfigType
- IDPState IDPConfigState
+ Name string
+ StylingType IDPConfigStylingType // deprecated
+ IDPType IDPType
+ IDPState IDPConfigState
}
func (p IDPProvider) IsValid() bool {
diff --git a/internal/iam/model/idp_provider_view.go b/internal/iam/model/idp_provider_view.go
index c6e1b4aea..562e37172 100644
--- a/internal/iam/model/idp_provider_view.go
+++ b/internal/iam/model/idp_provider_view.go
@@ -68,65 +68,3 @@ func (r *IDPProviderSearchRequest) EnsureLimit(limit uint64) error {
func (r *IDPProviderSearchRequest) AppendAggregateIDQuery(aggregateID string) {
r.Queries = append(r.Queries, &IDPProviderSearchQuery{Key: IDPProviderSearchKeyAggregateID, Method: domain.SearchMethodEquals, Value: aggregateID})
}
-
-func IdpProviderViewsToDomain(idpProviders []*IDPProviderView) []*domain.IDPProvider {
- providers := make([]*domain.IDPProvider, len(idpProviders))
- for i, provider := range idpProviders {
- p := &domain.IDPProvider{
- IDPConfigID: provider.IDPConfigID,
- Type: idpProviderTypeToDomain(provider.IDPProviderType),
- Name: provider.Name,
- IDPConfigType: idpConfigTypeToDomain(provider.IDPConfigType),
- StylingType: idpStylingTypeToDomain(provider.StylingType),
- IDPState: idpStateToDomain(provider.IDPState),
- }
- providers[i] = p
- }
- return providers
-}
-
-func idpProviderTypeToDomain(idpType IDPProviderType) domain.IdentityProviderType {
- switch idpType {
- case IDPProviderTypeSystem:
- return domain.IdentityProviderTypeSystem
- case IDPProviderTypeOrg:
- return domain.IdentityProviderTypeOrg
- default:
- return domain.IdentityProviderTypeSystem
- }
-}
-
-func idpConfigTypeToDomain(idpType IdpConfigType) domain.IDPConfigType {
- switch idpType {
- case IDPConfigTypeOIDC:
- return domain.IDPConfigTypeOIDC
- case IDPConfigTypeSAML:
- return domain.IDPConfigTypeSAML
- case IDPConfigTypeJWT:
- return domain.IDPConfigTypeJWT
- default:
- return domain.IDPConfigTypeOIDC
- }
-}
-
-func idpStylingTypeToDomain(stylingType IDPStylingType) domain.IDPConfigStylingType {
- switch stylingType {
- case IDPStylingTypeGoogle:
- return domain.IDPConfigStylingTypeGoogle
- default:
- return domain.IDPConfigStylingTypeUnspecified
- }
-}
-
-func idpStateToDomain(state IDPConfigState) domain.IDPConfigState {
- switch state {
- case IDPConfigStateActive:
- return domain.IDPConfigStateActive
- case IDPConfigStateInactive:
- return domain.IDPConfigStateInactive
- case IDPConfigStateRemoved:
- return domain.IDPConfigStateRemoved
- default:
- return domain.IDPConfigStateActive
- }
-}
diff --git a/internal/idp/providers/gitlab/gitlab.go b/internal/idp/providers/gitlab/gitlab.go
index 36e7404ff..f36aba8f2 100644
--- a/internal/idp/providers/gitlab/gitlab.go
+++ b/internal/idp/providers/gitlab/gitlab.go
@@ -18,14 +18,14 @@ type Provider struct {
}
// New creates a GitLab.com provider using the [oidc.Provider] (OIDC generic provider)
-func New(clientID, clientSecret, redirectURI string, options ...oidc.ProviderOpts) (*Provider, error) {
- return NewCustomIssuer(name, issuer, clientID, clientSecret, redirectURI, options...)
+func New(clientID, clientSecret, redirectURI string, scopes []string, options ...oidc.ProviderOpts) (*Provider, error) {
+ return NewCustomIssuer(name, issuer, clientID, clientSecret, redirectURI, scopes, options...)
}
// NewCustomIssuer creates a GitLab provider using the [oidc.Provider] (OIDC generic provider)
// with a custom issuer for self-managed instances
-func NewCustomIssuer(name, issuer, clientID, clientSecret, redirectURI string, options ...oidc.ProviderOpts) (*Provider, error) {
- rp, err := oidc.New(name, issuer, clientID, clientSecret, redirectURI, oidc.DefaultMapper, options...)
+func NewCustomIssuer(name, issuer, clientID, clientSecret, redirectURI string, scopes []string, options ...oidc.ProviderOpts) (*Provider, error) {
+ rp, err := oidc.New(name, issuer, clientID, clientSecret, redirectURI, scopes, oidc.DefaultMapper, options...)
if err != nil {
return nil, err
}
diff --git a/internal/idp/providers/gitlab/gitlab_test.go b/internal/idp/providers/gitlab/gitlab_test.go
index dd6808bb3..436a00b2c 100644
--- a/internal/idp/providers/gitlab/gitlab_test.go
+++ b/internal/idp/providers/gitlab/gitlab_test.go
@@ -16,6 +16,7 @@ func TestProvider_BeginAuth(t *testing.T) {
clientID string
clientSecret string
redirectURI string
+ scopes []string
opts []oidc.ProviderOpts
}
tests := []struct {
@@ -29,6 +30,7 @@ func TestProvider_BeginAuth(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
},
want: &oidc.Session{
AuthURL: "https://gitlab.com/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
@@ -40,7 +42,7 @@ func TestProvider_BeginAuth(t *testing.T) {
a := assert.New(t)
r := require.New(t)
- provider, err := New(tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.opts...)
+ provider, err := New(tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.opts...)
r.NoError(err)
session, err := provider.BeginAuth(context.Background(), "testState")
diff --git a/internal/idp/providers/gitlab/session_test.go b/internal/idp/providers/gitlab/session_test.go
index cdc5cd1a4..8f9337abc 100644
--- a/internal/idp/providers/gitlab/session_test.go
+++ b/internal/idp/providers/gitlab/session_test.go
@@ -22,6 +22,7 @@ func TestProvider_FetchUser(t *testing.T) {
clientID string
clientSecret string
redirectURI string
+ scopes []string
httpMock func()
authURL string
code string
@@ -55,6 +56,7 @@ func TestProvider_FetchUser(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
httpMock: func() {
gock.New("https://gitlab.com/oauth").
Get("/userinfo").
@@ -74,6 +76,7 @@ func TestProvider_FetchUser(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
httpMock: func() {
gock.New("https://gitlab.com/oauth").
Get("/userinfo").
@@ -110,6 +113,7 @@ func TestProvider_FetchUser(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
httpMock: func() {
gock.New("https://gitlab.com/oauth").
Get("/userinfo").
@@ -161,7 +165,7 @@ func TestProvider_FetchUser(t *testing.T) {
// call the real discovery endpoint
gock.New(issuer).Get(openid.DiscoveryEndpoint).EnableNetworking()
- provider, err := New(tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.options...)
+ provider, err := New(tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.options...)
require.NoError(t, err)
session := &oidc.Session{
diff --git a/internal/idp/providers/google/google.go b/internal/idp/providers/google/google.go
index c60382632..67a958692 100644
--- a/internal/idp/providers/google/google.go
+++ b/internal/idp/providers/google/google.go
@@ -20,8 +20,8 @@ type Provider struct {
}
// New creates a Google provider using the [oidc.Provider] (OIDC generic provider)
-func New(clientID, clientSecret, redirectURI string, opts ...oidc.ProviderOpts) (*Provider, error) {
- rp, err := oidc.New(name, issuer, clientID, clientSecret, redirectURI, userMapper, opts...)
+func New(clientID, clientSecret, redirectURI string, scopes []string, opts ...oidc.ProviderOpts) (*Provider, error) {
+ rp, err := oidc.New(name, issuer, clientID, clientSecret, redirectURI, scopes, userMapper, opts...)
if err != nil {
return nil, err
}
diff --git a/internal/idp/providers/google/google_test.go b/internal/idp/providers/google/google_test.go
index c6f860396..ff0a6d5d4 100644
--- a/internal/idp/providers/google/google_test.go
+++ b/internal/idp/providers/google/google_test.go
@@ -16,6 +16,7 @@ func TestProvider_BeginAuth(t *testing.T) {
clientID string
clientSecret string
redirectURI string
+ scopes []string
}
tests := []struct {
name string
@@ -28,6 +29,7 @@ func TestProvider_BeginAuth(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
},
want: &oidc.Session{
AuthURL: "https://accounts.google.com/o/oauth2/v2/auth?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
@@ -39,7 +41,7 @@ func TestProvider_BeginAuth(t *testing.T) {
a := assert.New(t)
r := require.New(t)
- provider, err := New(tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI)
+ provider, err := New(tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes)
r.NoError(err)
session, err := provider.BeginAuth(context.Background(), "testState")
diff --git a/internal/idp/providers/google/session_test.go b/internal/idp/providers/google/session_test.go
index 941060f69..f4696da71 100644
--- a/internal/idp/providers/google/session_test.go
+++ b/internal/idp/providers/google/session_test.go
@@ -22,6 +22,7 @@ func TestSession_FetchUser(t *testing.T) {
clientID string
clientSecret string
redirectURI string
+ scopes []string
httpMock func()
authURL string
code string
@@ -55,6 +56,7 @@ func TestSession_FetchUser(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
httpMock: func() {
gock.New("https://openidconnect.googleapis.com").
Get("/v1/userinfo").
@@ -74,6 +76,7 @@ func TestSession_FetchUser(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
httpMock: func() {
gock.New("https://openidconnect.googleapis.com").
Get("/v1/userinfo").
@@ -110,6 +113,7 @@ func TestSession_FetchUser(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
httpMock: func() {
gock.New("https://openidconnect.googleapis.com").
Get("/v1/userinfo").
@@ -162,7 +166,7 @@ func TestSession_FetchUser(t *testing.T) {
// call the real discovery endpoint
gock.New(issuer).Get(openid.DiscoveryEndpoint).EnableNetworking()
- provider, err := New(tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI)
+ provider, err := New(tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes)
require.NoError(t, err)
session := &oidc.Session{
diff --git a/internal/idp/providers/jwt/jwt.go b/internal/idp/providers/jwt/jwt.go
index 104e70e6a..bd2effac8 100644
--- a/internal/idp/providers/jwt/jwt.go
+++ b/internal/idp/providers/jwt/jwt.go
@@ -18,7 +18,6 @@ const (
var _ idp.Provider = (*Provider)(nil)
var (
- ErrNoTokens = errors.New("no tokens provided")
ErrMissingUserAgentID = errors.New("userAgentID missing")
)
diff --git a/internal/idp/providers/jwt/session.go b/internal/idp/providers/jwt/session.go
index 9aa29735a..fb79302db 100644
--- a/internal/idp/providers/jwt/session.go
+++ b/internal/idp/providers/jwt/session.go
@@ -2,7 +2,13 @@ package jwt
import (
"context"
+ "errors"
+ "fmt"
+ "net/http"
+ "time"
+ "github.com/zitadel/logging"
+ "github.com/zitadel/oidc/v2/pkg/client/rp"
"github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/text/language"
@@ -11,8 +17,14 @@ import (
var _ idp.Session = (*Session)(nil)
+var (
+ ErrNoTokens = errors.New("no tokens provided")
+ ErrInvalidToken = errors.New("invalid tokens provided")
+)
+
// Session is the [idp.Session] implementation for the JWT provider
type Session struct {
+ *Provider
AuthURL string
Tokens *oidc.Tokens
}
@@ -28,9 +40,48 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
if s.Tokens == nil {
return nil, ErrNoTokens
}
+ s.Tokens.IDTokenClaims, err = s.validateToken(ctx, s.Tokens.IDToken)
+ if err != nil {
+ return nil, err
+ }
return &User{s.Tokens.IDTokenClaims}, nil
}
+func (s *Session) validateToken(ctx context.Context, token string) (oidc.IDTokenClaims, error) {
+ logging.Debug("begin token validation")
+ // TODO: be able to specify them in the template: https://github.com/zitadel/zitadel/issues/5322
+ offset := 3 * time.Second
+ maxAge := time.Hour
+ claims := oidc.EmptyIDTokenClaims()
+ payload, err := oidc.ParseToken(token, claims)
+ if err != nil {
+ return nil, fmt.Errorf("%w: malformed jwt payload: %v", ErrInvalidToken, err)
+ }
+
+ if err = oidc.CheckIssuer(claims, s.Provider.issuer); err != nil {
+ return nil, fmt.Errorf("%w: invalid issuer: %v", ErrInvalidToken, err)
+ }
+
+ logging.Debug("begin signature validation")
+ keySet := rp.NewRemoteKeySet(http.DefaultClient, s.Provider.keysEndpoint)
+ if err = oidc.CheckSignature(ctx, token, payload, claims, nil, keySet); err != nil {
+ return nil, fmt.Errorf("%w: invalid signature: %v", ErrInvalidToken, err)
+ }
+
+ if !claims.GetExpiration().IsZero() {
+ if err = oidc.CheckExpiration(claims, offset); err != nil {
+ return nil, fmt.Errorf("%w: expired: %v", ErrInvalidToken, err)
+ }
+ }
+
+ if !claims.GetIssuedAt().IsZero() {
+ if err = oidc.CheckIssuedAt(claims, maxAge, offset); err != nil {
+ return nil, fmt.Errorf("%w: %v", ErrInvalidToken, err)
+ }
+ }
+ return claims, nil
+}
+
type User struct {
oidc.IDTokenClaims
}
diff --git a/internal/idp/providers/jwt/session_test.go b/internal/idp/providers/jwt/session_test.go
index d8a92a421..61f552654 100644
--- a/internal/idp/providers/jwt/session_test.go
+++ b/internal/idp/providers/jwt/session_test.go
@@ -2,25 +2,37 @@ package jwt
import (
"context"
+ "encoding/json"
"errors"
"testing"
+ "time"
+ "github.com/golang/mock/gomock"
+ "github.com/h2non/gock"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/oauth2"
"golang.org/x/text/language"
+ "gopkg.in/square/go-jose.v2"
- "github.com/zitadel/zitadel/internal/idp"
+ "github.com/zitadel/zitadel/internal/crypto"
)
func TestSession_FetchUser(t *testing.T) {
type fields struct {
- authURL string
- tokens *oidc.Tokens
+ name string
+ issuer string
+ jwtEndpoint string
+ keysEndpoint string
+ headerName string
+ encryptionAlg func(t *testing.T) crypto.EncryptionAlgorithm
+ httpMock func(issuer string)
+ authURL string
+ tokens *oidc.Tokens
}
type want struct {
err func(error) bool
- user idp.User
id string
firstName string
lastName string
@@ -41,8 +53,22 @@ func TestSession_FetchUser(t *testing.T) {
want want
}{
{
- name: "no tokens",
- fields: fields{},
+ name: "no tokens",
+ fields: fields{
+ issuer: "https://jwt.com",
+ jwtEndpoint: "https://auth.com/jwt",
+ keysEndpoint: "https://jwt.com/keys",
+ headerName: "jwt-header",
+ encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
+ return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
+ },
+ httpMock: func(issuer string) {
+ gock.New(issuer).
+ Get("/keys").
+ Reply(200).
+ JSON(keys(t))
+ },
+ },
want: want{
err: func(err error) bool {
return errors.Is(err, ErrNoTokens)
@@ -50,11 +76,53 @@ func TestSession_FetchUser(t *testing.T) {
},
},
{
- name: "successful fetch",
+ name: "invalid token",
fields: fields{
+ issuer: "https://jwt.com",
+ jwtEndpoint: "https://auth.com/jwt",
+ keysEndpoint: "https://jwt.com/keys",
+ headerName: "jwt-header",
+ encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
+ return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
+ },
+ httpMock: func(issuer string) {
+ gock.New(issuer).
+ Get("/keys").
+ Reply(200).
+ JSON(keys(t))
+ },
authURL: "https://auth.com/jwt?authRequestID=testState",
tokens: &oidc.Tokens{
- Token: &oauth2.Token{},
+ Token: &oauth2.Token{},
+ IDToken: "invalidToken",
+ },
+ },
+ want: want{
+ err: func(err error) bool {
+ return errors.Is(err, ErrInvalidToken)
+ },
+ },
+ },
+ {
+ name: "successful fetch",
+ fields: fields{
+ issuer: "https://jwt.com",
+ jwtEndpoint: "https://auth.com/jwt",
+ keysEndpoint: "https://jwt.com/keys",
+ headerName: "jwt-header",
+ encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
+ return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
+ },
+ httpMock: func(issuer string) {
+ gock.New(issuer).
+ Get("/keys").
+ Reply(200).
+ JSON(keys(t))
+ },
+ authURL: "https://auth.com/jwt?authRequestID=testState",
+ tokens: &oidc.Tokens{
+ Token: &oauth2.Token{},
+ IDToken: idToken(t, "https://jwt.com"),
IDTokenClaims: func() oidc.IDTokenClaims {
claims := oidc.EmptyIDTokenClaims()
userinfo := oidc.NewUserInfo()
@@ -75,25 +143,6 @@ func TestSession_FetchUser(t *testing.T) {
},
},
want: want{
- user: &User{
- IDTokenClaims: func() oidc.IDTokenClaims {
- claims := oidc.EmptyIDTokenClaims()
- userinfo := oidc.NewUserInfo()
- userinfo.SetSubject("sub")
- userinfo.SetPicture("picture")
- userinfo.SetName("firstname lastname")
- userinfo.SetEmail("email", true)
- userinfo.SetGivenName("firstname")
- userinfo.SetFamilyName("lastname")
- userinfo.SetNickname("nickname")
- userinfo.SetPreferredUsername("username")
- userinfo.SetProfile("profile")
- userinfo.SetPhone("phone", true)
- userinfo.SetLocale(language.English)
- claims.SetUserinfo(userinfo)
- return claims
- }(),
- },
id: "sub",
firstName: "firstname",
lastName: "lastname",
@@ -112,11 +161,24 @@ func TestSession_FetchUser(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ defer gock.Off()
+ tt.fields.httpMock(tt.fields.issuer)
a := assert.New(t)
+ provider, err := New(
+ tt.fields.name,
+ tt.fields.issuer,
+ tt.fields.jwtEndpoint,
+ tt.fields.keysEndpoint,
+ tt.fields.headerName,
+ tt.fields.encryptionAlg(t),
+ )
+ require.NoError(t, err)
+
session := &Session{
- AuthURL: tt.fields.authURL,
- Tokens: tt.fields.tokens,
+ Provider: provider,
+ AuthURL: tt.fields.authURL,
+ Tokens: tt.fields.tokens,
}
user, err := session.FetchUser(context.Background())
@@ -125,7 +187,6 @@ func TestSession_FetchUser(t *testing.T) {
}
if tt.want.err == nil {
a.NoError(err)
- a.Equal(tt.want.user, user)
a.Equal(tt.want.id, user.GetID())
a.Equal(tt.want.firstName, user.GetFirstName())
a.Equal(tt.want.lastName, user.GetLastName())
@@ -143,3 +204,96 @@ func TestSession_FetchUser(t *testing.T) {
})
}
}
+
+func idToken(t *testing.T, issuer string) string {
+ claims := oidc.NewIDTokenClaims(
+ issuer,
+ "sub",
+ []string{"clientID"},
+ time.Now().Add(1*time.Hour),
+ time.Now().Add(-1*time.Minute),
+ "",
+ "",
+ nil,
+ "clientID",
+ 0,
+ )
+ info := oidc.NewUserInfo()
+ info.SetSubject("sub")
+ info.SetGivenName("firstname")
+ info.SetFamilyName("lastname")
+ info.SetName("firstname lastname")
+ info.SetNickname("nickname")
+ info.SetPreferredUsername("username")
+ info.SetEmail("email", true)
+ info.SetPhone("phone", true)
+ info.SetLocale(language.English)
+ info.SetPicture("picture")
+ info.SetProfile("profile")
+ claims.SetUserinfo(info)
+ privateKey, err := crypto.BytesToPrivateKey([]byte(`-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAs38btwb3c7r0tMaQpGvBmY+mPwMU/LpfuPoC0k2t4RsKp0fv
+40SMl50CRrHgk395wch8PMPYbl3+8TtYAJuyrFALIj3Ff1UcKIk0hOH5DDsfh7/q
+2wFuncTmS6bifYo8CfSq2vDGnM7nZnEvxY/MfSydZdcmIqlkUpfQmtzExw9+tSe5
+Dxq6gn5JtlGgLgZGt69r5iMMrTEGhhVAXzNuMZbmlCoBru+rC8ITlTX/0V1ZcsSb
+L8tYWhthyu9x6yjo1bH85wiVI4gs0MhU8f2a+kjL/KGZbR14Ua2eo6tonBZLC5DH
+WM2TkYXgRCDPufjcgmzN0Lm91E4P8KvBcvly6QIDAQABAoIBAQCPj1nbSPcg2KZe
+73FAD+8HopyUSSK//1AP4eXfzcEECVy77g0u9+R6XlkzsZCsZ4g6NN8ounqfyw3c
+YlpAIkcFCf/dowoSjT+4LASVQyatYZwWNqjgAIU4KgMG/rKnNahPTiBYe7peMB1j
+EaPjnt8uPkCk8y7NCi3y4Pk24tt/WM5KbJK2NQhUi1csGnleDfE+0blV0l/e6C68
+W5cbnbWAroMqae/Yon3XVZiXX0m+l2f6ZzIgKaD18J+eEM8FjJC+jQKiRe1i9v3K
+nQrLwh/gn8J10FcbKn3xqslKVidzASIrNIzHT9j/Z5T9NXuAKa7IV2x+Dtdus+wq
+iBsUunwBAoGBANpYew+8i9vDwK4/SefduDTuzJ0H9lWTjtbiWQ+KYZoeJ7q3/qns
+jsmi+mjxkXxXg1RrGbNbjtbl3RXXIrUeeBB0lglRJUjc3VK7VvNoyXIWsiqhCspH
+IJ9Yuknv4mXB01m/glbSCS/xu4RTgf5aOG4jUiRb9+dCIpvDxI9gbXEVAoGBANJz
+hIJkplIJ+biTi3G1Oz17qkUkInNXzAEzKD9Atoz5AIAiR1ivOMLOlbucfjevw/Nw
+TnpkMs9xqCefKupTlsriXtZI88m7ZKzAmolYsPolOy/Jhi31h9JFVTEfKGqVS+dk
+A4ndhgdW9RUeNJPY2YVCARXQrWpueweQDA1cNaeFAoGAPJsYtXqBW6PPRM5+ZiSt
+78tk8iV2o7RMjqrPS7f+dXfvUS2nO2VVEPTzCtQarOfhpToBLT65vD6bimdn09w8
+OV0TFEz4y2u65y7m6LNqTwertpdy1ki97l0DgGhccCBH2P6GYDD2qd8wTH+dcot6
+ZF/begopGoDJ+HBzi9SZLC0CgYBZzPslHMevyBvr++GLwrallKhiWnns1/DwLiEl
+ZHrBCtuA0Z+6IwLIdZiE9tEQ+ApYTXrfVPQteqUzSwLn/IUiy5eGPpjwYushoAoR
+Q2w5QTvRN1/vKo8rVXR1woLfgBdkhFPSN1mitiNcQIhU8jpXV4PZCDOHb99FqdzK
+sqcedQKBgQCOmgbqxGsnT2WQhoOdzln+NOo6Tx+FveLLqat2KzpY59W4noeI2Awn
+HfIQgWUAW9dsjVVOXMP1jhq8U9hmH/PFWA11V/iCdk1NTxZEw87VAOeWuajpdDHG
++iex349j8h2BcQ4Zd0FWu07gGFnS/yuDJPn6jBhRusdieEcxLRjTKg==
+-----END RSA PRIVATE KEY-----
+`))
+ if err != nil {
+ t.Fatal(err)
+ }
+ signer, err := jose.NewSigner(jose.SigningKey{Key: privateKey, Algorithm: "RS256"}, &jose.SignerOptions{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ data, err := json.Marshal(claims)
+ if err != nil {
+ t.Fatal(err)
+ }
+ jws, err := signer.Sign(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+ idToken, err := jws.CompactSerialize()
+ if err != nil {
+ t.Fatal(err)
+ }
+ return idToken
+}
+
+func keys(t *testing.T) *jose.JSONWebKeySet {
+ privateKey, err := crypto.BytesToPublicKey([]byte(`-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs38btwb3c7r0tMaQpGvB
+mY+mPwMU/LpfuPoC0k2t4RsKp0fv40SMl50CRrHgk395wch8PMPYbl3+8TtYAJuy
+rFALIj3Ff1UcKIk0hOH5DDsfh7/q2wFuncTmS6bifYo8CfSq2vDGnM7nZnEvxY/M
+fSydZdcmIqlkUpfQmtzExw9+tSe5Dxq6gn5JtlGgLgZGt69r5iMMrTEGhhVAXzNu
+MZbmlCoBru+rC8ITlTX/0V1ZcsSbL8tYWhthyu9x6yjo1bH85wiVI4gs0MhU8f2a
++kjL/KGZbR14Ua2eo6tonBZLC5DHWM2TkYXgRCDPufjcgmzN0Lm91E4P8KvBcvly
+6QIDAQAB
+-----END PUBLIC KEY-----
+`))
+ if err != nil {
+ t.Fatal(err)
+ }
+ return &jose.JSONWebKeySet{Keys: []jose.JSONWebKey{{Key: privateKey, Algorithm: "RS256", Use: oidc.KeyUseSignature}}}
+}
diff --git a/internal/idp/providers/oidc/oidc.go b/internal/idp/providers/oidc/oidc.go
index 06fedbab4..890b168cb 100644
--- a/internal/idp/providers/oidc/oidc.go
+++ b/internal/idp/providers/oidc/oidc.go
@@ -68,7 +68,7 @@ var DefaultMapper UserInfoMapper = func(info oidc.UserInfo) idp.User {
}
// New creates a generic OIDC provider
-func New(name, issuer, clientID, clientSecret, redirectURI string, userInfoMapper UserInfoMapper, options ...ProviderOpts) (provider *Provider, err error) {
+func New(name, issuer, clientID, clientSecret, redirectURI string, scopes []string, userInfoMapper UserInfoMapper, options ...ProviderOpts) (provider *Provider, err error) {
provider = &Provider{
name: name,
userInfoMapper: userInfoMapper,
@@ -76,13 +76,27 @@ func New(name, issuer, clientID, clientSecret, redirectURI string, userInfoMappe
for _, option := range options {
option(provider)
}
- provider.RelyingParty, err = rp.NewRelyingPartyOIDC(issuer, clientID, clientSecret, redirectURI, []string{oidc.ScopeOpenID}, provider.options...)
+ provider.RelyingParty, err = rp.NewRelyingPartyOIDC(issuer, clientID, clientSecret, redirectURI, setDefaultScope(scopes), provider.options...)
if err != nil {
return nil, err
}
return provider, nil
}
+// setDefaultScope ensures that at least openid ist set
+// if none is provided it will request `openid profile email phone`
+func setDefaultScope(scopes []string) []string {
+ if len(scopes) == 0 {
+ return []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopePhone}
+ }
+ for _, scope := range scopes {
+ if scope == oidc.ScopeOpenID {
+ return scopes
+ }
+ }
+ return append(scopes, oidc.ScopeOpenID)
+}
+
// Name implements the [idp.Provider] interface
func (p *Provider) Name() string {
return p.name
diff --git a/internal/idp/providers/oidc/oidc_test.go b/internal/idp/providers/oidc/oidc_test.go
index 571ab4ae5..ca205d7e5 100644
--- a/internal/idp/providers/oidc/oidc_test.go
+++ b/internal/idp/providers/oidc/oidc_test.go
@@ -20,6 +20,7 @@ func TestProvider_BeginAuth(t *testing.T) {
clientID string
clientSecret string
redirectURI string
+ scopes []string
userMapper func(info oidc.UserInfo) idp.User
httpMock func(issuer string)
}
@@ -36,6 +37,7 @@ func TestProvider_BeginAuth(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
userMapper: DefaultMapper,
httpMock: func(issuer string) {
gock.New(issuer).
@@ -59,7 +61,7 @@ func TestProvider_BeginAuth(t *testing.T) {
a := assert.New(t)
r := require.New(t)
- provider, err := New(tt.fields.name, tt.fields.issuer, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.userMapper)
+ provider, err := New(tt.fields.name, tt.fields.issuer, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.userMapper)
r.NoError(err)
session, err := provider.BeginAuth(context.Background(), "testState")
@@ -77,6 +79,7 @@ func TestProvider_Options(t *testing.T) {
clientID string
clientSecret string
redirectURI string
+ scopes []string
userMapper func(info oidc.UserInfo) idp.User
opts []ProviderOpts
httpMock func(issuer string)
@@ -102,6 +105,7 @@ func TestProvider_Options(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
userMapper: DefaultMapper,
opts: nil,
httpMock: func(issuer string) {
@@ -133,6 +137,7 @@ func TestProvider_Options(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
userMapper: DefaultMapper,
opts: []ProviderOpts{
WithLinkingAllowed(),
@@ -169,7 +174,7 @@ func TestProvider_Options(t *testing.T) {
tt.fields.httpMock(tt.fields.issuer)
a := assert.New(t)
- provider, err := New(tt.fields.name, tt.fields.issuer, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.userMapper, tt.fields.opts...)
+ provider, err := New(tt.fields.name, tt.fields.issuer, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.userMapper, tt.fields.opts...)
require.NoError(t, err)
a.Equal(tt.want.name, provider.Name())
diff --git a/internal/idp/providers/oidc/session_test.go b/internal/idp/providers/oidc/session_test.go
index e31dd1383..fe18a71b7 100644
--- a/internal/idp/providers/oidc/session_test.go
+++ b/internal/idp/providers/oidc/session_test.go
@@ -27,6 +27,7 @@ func TestSession_FetchUser(t *testing.T) {
clientID string
clientSecret string
redirectURI string
+ scopes []string
userMapper func(oidc.UserInfo) idp.User
httpMock func(issuer string)
authURL string
@@ -62,6 +63,7 @@ func TestSession_FetchUser(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
userMapper: DefaultMapper,
httpMock: func(issuer string) {
gock.New(issuer).
@@ -93,6 +95,7 @@ func TestSession_FetchUser(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
userMapper: DefaultMapper,
httpMock: func(issuer string) {
gock.New(issuer).
@@ -141,6 +144,7 @@ func TestSession_FetchUser(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
userMapper: DefaultMapper,
httpMock: func(issuer string) {
gock.New(issuer).
@@ -201,6 +205,7 @@ func TestSession_FetchUser(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
+ scopes: []string{"openid"},
userMapper: DefaultMapper,
httpMock: func(issuer string) {
gock.New(issuer).
@@ -254,7 +259,7 @@ func TestSession_FetchUser(t *testing.T) {
tt.fields.httpMock(tt.fields.issuer)
a := assert.New(t)
- provider, err := New(tt.fields.name, tt.fields.issuer, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.userMapper)
+ provider, err := New(tt.fields.name, tt.fields.issuer, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.userMapper)
require.NoError(t, err)
session := &Session{
diff --git a/internal/query/idp_login_policy_link.go b/internal/query/idp_login_policy_link.go
index d0077e7d4..afa5a8f6e 100644
--- a/internal/query/idp_login_policy_link.go
+++ b/internal/query/idp_login_policy_link.go
@@ -15,9 +15,10 @@ import (
)
type IDPLoginPolicyLink struct {
- IDPID string
- IDPName string
- IDPType domain.IDPConfigType
+ IDPID string
+ IDPName string
+ IDPType domain.IDPType
+ OwnerType domain.IdentityProviderType
}
type IDPLoginPolicyLinks struct {
@@ -113,25 +114,28 @@ func (q *Queries) IDPLoginPolicyLinks(ctx context.Context, resourceOwner string,
func prepareIDPLoginPolicyLinksQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*IDPLoginPolicyLinks, error)) {
return sq.Select(
IDPLoginPolicyLinkIDPIDCol.identifier(),
- IDPNameCol.identifier(),
- IDPTypeCol.identifier(),
+ IDPTemplateNameCol.identifier(),
+ IDPTemplateTypeCol.identifier(),
+ IDPTemplateOwnerTypeCol.identifier(),
countColumn.identifier()).
From(idpLoginPolicyLinkTable.identifier()).
- LeftJoin(join(IDPIDCol, IDPLoginPolicyLinkIDPIDCol) + db.Timetravel(call.Took(ctx))).
+ LeftJoin(join(IDPTemplateIDCol, IDPLoginPolicyLinkIDPIDCol) + db.Timetravel(call.Took(ctx))).
PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*IDPLoginPolicyLinks, error) {
links := make([]*IDPLoginPolicyLink, 0)
var count uint64
for rows.Next() {
var (
- idpName = sql.NullString{}
- idpType = sql.NullInt16{}
- link = new(IDPLoginPolicyLink)
+ idpName = sql.NullString{}
+ idpType = sql.NullInt16{}
+ idpOwnerType = sql.NullInt16{}
+ link = new(IDPLoginPolicyLink)
)
err := rows.Scan(
&link.IDPID,
&idpName,
&idpType,
+ &idpOwnerType,
&count,
)
if err != nil {
@@ -140,10 +144,11 @@ func prepareIDPLoginPolicyLinksQuery(ctx context.Context, db prepareDatabase) (s
link.IDPName = idpName.String
//IDPType 0 is oidc so we have to set unspecified manually
if idpType.Valid {
- link.IDPType = domain.IDPConfigType(idpType.Int16)
+ link.IDPType = domain.IDPType(idpType.Int16)
} else {
- link.IDPType = domain.IDPConfigTypeUnspecified
+ link.IDPType = domain.IDPTypeUnspecified
}
+ link.OwnerType = domain.IdentityProviderType(idpOwnerType.Int16)
links = append(links, link)
}
diff --git a/internal/query/idp_login_policy_link_test.go b/internal/query/idp_login_policy_link_test.go
index 44d6e8d6c..f886b9f68 100644
--- a/internal/query/idp_login_policy_link_test.go
+++ b/internal/query/idp_login_policy_link_test.go
@@ -13,16 +13,18 @@ import (
var (
loginPolicyIDPLinksQuery = regexp.QuoteMeta(`SELECT projections.idp_login_policy_links4.idp_id,` +
- ` projections.idps3.name,` +
- ` projections.idps3.type,` +
+ ` projections.idp_templates2.name,` +
+ ` projections.idp_templates2.type,` +
+ ` projections.idp_templates2.owner_type,` +
` COUNT(*) OVER ()` +
` FROM projections.idp_login_policy_links4` +
- ` LEFT JOIN projections.idps3 ON projections.idp_login_policy_links4.idp_id = projections.idps3.id AND projections.idp_login_policy_links4.instance_id = projections.idps3.instance_id` +
+ ` LEFT JOIN projections.idp_templates2 ON projections.idp_login_policy_links4.idp_id = projections.idp_templates2.id AND projections.idp_login_policy_links4.instance_id = projections.idp_templates2.instance_id` +
` AS OF SYSTEM TIME '-1 ms'`)
loginPolicyIDPLinksCols = []string{
"idp_id",
"name",
"type",
+ "owner_type",
"count",
}
)
@@ -49,7 +51,8 @@ func Test_IDPLoginPolicyLinkPrepares(t *testing.T) {
{
"idp-id",
"idp-name",
- domain.IDPConfigTypeJWT,
+ domain.IDPTypeJWT,
+ domain.IdentityProviderTypeSystem,
},
},
),
@@ -60,9 +63,10 @@ func Test_IDPLoginPolicyLinkPrepares(t *testing.T) {
},
Links: []*IDPLoginPolicyLink{
{
- IDPID: "idp-id",
- IDPName: "idp-name",
- IDPType: domain.IDPConfigTypeJWT,
+ IDPID: "idp-id",
+ IDPName: "idp-name",
+ IDPType: domain.IDPTypeJWT,
+ OwnerType: domain.IdentityProviderTypeSystem,
},
},
},
@@ -79,6 +83,7 @@ func Test_IDPLoginPolicyLinkPrepares(t *testing.T) {
"idp-id",
nil,
nil,
+ nil,
},
},
),
@@ -91,7 +96,7 @@ func Test_IDPLoginPolicyLinkPrepares(t *testing.T) {
{
IDPID: "idp-id",
IDPName: "",
- IDPType: domain.IDPConfigTypeUnspecified,
+ IDPType: domain.IDPTypeUnspecified,
},
},
},
diff --git a/internal/query/idp_template.go b/internal/query/idp_template.go
index ef781eb0a..735efaf17 100644
--- a/internal/query/idp_template.go
+++ b/internal/query/idp_template.go
@@ -386,8 +386,8 @@ var (
}
)
-// IDPTemplateByIDAndResourceOwner searches for the requested id in the context of the resource owner and IAM
-func (q *Queries) IDPTemplateByIDAndResourceOwner(ctx context.Context, shouldTriggerBulk bool, id, resourceOwner string, withOwnerRemoved bool) (_ *IDPTemplate, err error) {
+// IDPTemplateByID searches for the requested id
+func (q *Queries) IDPTemplateByID(ctx context.Context, shouldTriggerBulk bool, id string, withOwnerRemoved bool, queries ...SearchQuery) (_ *IDPTemplate, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -403,20 +403,16 @@ func (q *Queries) IDPTemplateByIDAndResourceOwner(ctx context.Context, shouldTri
if !withOwnerRemoved {
eq[IDPTemplateOwnerRemovedCol.identifier()] = false
}
- where := sq.And{
- eq,
- sq.Or{
- sq.Eq{IDPTemplateResourceOwnerCol.identifier(): resourceOwner},
- sq.Eq{IDPTemplateResourceOwnerCol.identifier(): authz.GetInstance(ctx).InstanceID()},
- },
+ query, scan := prepareIDPTemplateByIDQuery(ctx, q.client)
+ for _, q := range queries {
+ query = q.toQuery(query)
}
- stmt, scan := prepareIDPTemplateByIDQuery(ctx, q.client)
- query, args, err := stmt.Where(where).ToSql()
+ stmt, args, err := query.Where(eq).ToSql()
if err != nil {
- return nil, errors.ThrowInternal(err, "QUERY-SFAew", "Errors.Query.SQLStatement")
+ return nil, errors.ThrowInternal(err, "QUERY-SFefg", "Errors.Query.SQLStatement")
}
- row := q.client.QueryRowContext(ctx, query, args...)
+ row := q.client.QueryRowContext(ctx, stmt, args...)
return scan(row)
}
diff --git a/internal/query/idp_user_link.go b/internal/query/idp_user_link.go
index 465a6a396..b2b95e85e 100644
--- a/internal/query/idp_user_link.go
+++ b/internal/query/idp_user_link.go
@@ -21,7 +21,7 @@ type IDPUserLink struct {
ProvidedUserID string
ProvidedUsername string
ResourceOwner string
- IDPType domain.IDPConfigType
+ IDPType domain.IDPType
}
type IDPUserLinks struct {
@@ -127,18 +127,22 @@ func NewIDPUserLinksResourceOwnerSearchQuery(value string) (SearchQuery, error)
return NewTextQuery(IDPUserLinkResourceOwnerCol, value, TextEquals)
}
+func NewIDPUserLinksExternalIDSearchQuery(value string) (SearchQuery, error) {
+ return NewTextQuery(IDPUserLinkExternalUserIDCol, value, TextEquals)
+}
+
func prepareIDPUserLinksQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*IDPUserLinks, error)) {
return sq.Select(
IDPUserLinkIDPIDCol.identifier(),
IDPUserLinkUserIDCol.identifier(),
- IDPNameCol.identifier(),
+ IDPTemplateNameCol.identifier(),
IDPUserLinkExternalUserIDCol.identifier(),
IDPUserLinkDisplayNameCol.identifier(),
- IDPTypeCol.identifier(),
+ IDPTemplateTypeCol.identifier(),
IDPUserLinkResourceOwnerCol.identifier(),
countColumn.identifier()).
From(idpUserLinkTable.identifier()).
- LeftJoin(join(IDPIDCol, IDPUserLinkIDPIDCol) + db.Timetravel(call.Took(ctx))).
+ LeftJoin(join(IDPTemplateIDCol, IDPUserLinkIDPIDCol) + db.Timetravel(call.Took(ctx))).
PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*IDPUserLinks, error) {
idps := make([]*IDPUserLink, 0)
@@ -165,9 +169,9 @@ func prepareIDPUserLinksQuery(ctx context.Context, db prepareDatabase) (sq.Selec
idp.IDPName = idpName.String
//IDPType 0 is oidc so we have to set unspecified manually
if idpType.Valid {
- idp.IDPType = domain.IDPConfigType(idpType.Int16)
+ idp.IDPType = domain.IDPType(idpType.Int16)
} else {
- idp.IDPType = domain.IDPConfigTypeUnspecified
+ idp.IDPType = domain.IDPTypeUnspecified
}
idps = append(idps, idp)
}
diff --git a/internal/query/idp_user_link_test.go b/internal/query/idp_user_link_test.go
index 859c164cb..0343af926 100644
--- a/internal/query/idp_user_link_test.go
+++ b/internal/query/idp_user_link_test.go
@@ -14,14 +14,14 @@ import (
var (
idpUserLinksQuery = regexp.QuoteMeta(`SELECT projections.idp_user_links3.idp_id,` +
` projections.idp_user_links3.user_id,` +
- ` projections.idps3.name,` +
+ ` projections.idp_templates2.name,` +
` projections.idp_user_links3.external_user_id,` +
` projections.idp_user_links3.display_name,` +
- ` projections.idps3.type,` +
+ ` projections.idp_templates2.type,` +
` projections.idp_user_links3.resource_owner,` +
` COUNT(*) OVER ()` +
` FROM projections.idp_user_links3` +
- ` LEFT JOIN projections.idps3 ON projections.idp_user_links3.idp_id = projections.idps3.id AND projections.idp_user_links3.instance_id = projections.idps3.instance_id` +
+ ` LEFT JOIN projections.idp_templates2 ON projections.idp_user_links3.idp_id = projections.idp_templates2.id AND projections.idp_user_links3.instance_id = projections.idp_templates2.instance_id` +
` AS OF SYSTEM TIME '-1 ms'`)
idpUserLinksCols = []string{
"idp_id",
@@ -60,7 +60,7 @@ func Test_IDPUserLinkPrepares(t *testing.T) {
"idp-name",
"external-user-id",
"display-name",
- domain.IDPConfigTypeJWT,
+ domain.IDPTypeJWT,
"ro",
},
},
@@ -77,7 +77,7 @@ func Test_IDPUserLinkPrepares(t *testing.T) {
IDPName: "idp-name",
ProvidedUserID: "external-user-id",
ProvidedUsername: "display-name",
- IDPType: domain.IDPConfigTypeJWT,
+ IDPType: domain.IDPTypeJWT,
ResourceOwner: "ro",
},
},
@@ -114,7 +114,7 @@ func Test_IDPUserLinkPrepares(t *testing.T) {
IDPName: "",
ProvidedUserID: "external-user-id",
ProvidedUsername: "display-name",
- IDPType: domain.IDPConfigTypeUnspecified,
+ IDPType: domain.IDPTypeUnspecified,
ResourceOwner: "ro",
},
},
diff --git a/internal/query/projection/idp_template.go b/internal/query/projection/idp_template.go
index c1d541646..09c3c3ad4 100644
--- a/internal/query/projection/idp_template.go
+++ b/internal/query/projection/idp_template.go
@@ -668,7 +668,7 @@ func (p *idpTemplateProjection) reduceOldConfigAdded(event eventstore.Event) (*h
handler.NewCol(IDPTemplateStateCol, domain.IDPStateActive),
handler.NewCol(IDPTemplateNameCol, idpEvent.Name),
handler.NewCol(IDPTemplateOwnerTypeCol, idpOwnerType),
- handler.NewCol(IDPTemplateTypeCol, domain.IDPTypeOIDC),
+ handler.NewCol(IDPTemplateTypeCol, domain.IDPTypeUnspecified),
handler.NewCol(IDPTemplateIsCreationAllowedCol, true),
handler.NewCol(IDPTemplateIsLinkingAllowedCol, true),
handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.AutoRegister),
@@ -727,6 +727,7 @@ func (p *idpTemplateProjection) reduceOldOIDCConfigAdded(event eventstore.Event)
[]handler.Column{
handler.NewCol(IDPTemplateChangeDateCol, idpEvent.CreationDate()),
handler.NewCol(IDPTemplateSequenceCol, idpEvent.Sequence()),
+ handler.NewCol(IDPTemplateTypeCol, domain.IDPTypeOIDC),
},
[]handler.Condition{
handler.NewCond(IDPTemplateIDCol, idpEvent.IDPConfigID),
@@ -820,6 +821,7 @@ func (p *idpTemplateProjection) reduceOldJWTConfigAdded(event eventstore.Event)
[]handler.Column{
handler.NewCol(IDPTemplateChangeDateCol, idpEvent.CreationDate()),
handler.NewCol(IDPTemplateSequenceCol, idpEvent.Sequence()),
+ handler.NewCol(IDPTemplateTypeCol, domain.IDPTypeJWT),
},
[]handler.Condition{
handler.NewCond(IDPTemplateIDCol, idpEvent.IDPConfigID),
diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto
index eb0e9493f..21bf720c8 100644
--- a/proto/zitadel/admin.proto
+++ b/proto/zitadel/admin.proto
@@ -4403,7 +4403,7 @@ message AddJWTProviderRequest {
string jwt_endpoint = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
string keys_endpoint = 4 [(validate.rules).string = {min_len: 1, max_len: 200}];
string header_name = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
- zitadel.idp.v1.Options provider_options = 6 [(validate.rules).message = {required: true}];
+ zitadel.idp.v1.Options provider_options = 6;
}
message AddJWTProviderResponse {
@@ -4418,7 +4418,7 @@ message UpdateJWTProviderRequest {
string jwt_endpoint = 4 [(validate.rules).string = {min_len: 1, max_len: 200}];
string keys_endpoint = 5 [(validate.rules).string = {max_len: 200}];
string header_name = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
- zitadel.idp.v1.Options provider_options = 7 [(validate.rules).message = {required: true}];
+ zitadel.idp.v1.Options provider_options = 7;
}
message UpdateJWTProviderResponse {