mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-05 05:42:16 +00:00
feat: add apple as idp (#6442)
* feat: manage apple idp * handle apple idp callback * add tests for provider * basic console implementation * implement flow for login UI and add logos / styling * tests * cleanup * add upload button * begin i18n * apple logo positioning, file upload component * fix add apple instance idp * add missing apple logos for login * update to go 1.21 * fix slice compare * revert permission changes * concrete error messages * translate login apple logo -y-2px * change form parsing * sign in button * fix tests * lint console --------- Co-authored-by: peintnermax <[email protected]>
This commit is contained in:
co-authored by
peintnermax
parent
0d94947d3c
commit
e17b49e4ca
@@ -405,6 +405,27 @@ func (s *Server) UpdateLDAPProvider(ctx context.Context, req *admin_pb.UpdateLDA
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddAppleProvider(ctx context.Context, req *admin_pb.AddAppleProviderRequest) (*admin_pb.AddAppleProviderResponse, error) {
|
||||
id, details, err := s.command.AddInstanceAppleProvider(ctx, addAppleProviderToCommand(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.AddAppleProviderResponse{
|
||||
Id: id,
|
||||
Details: object_pb.DomainToAddDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateAppleProvider(ctx context.Context, req *admin_pb.UpdateAppleProviderRequest) (*admin_pb.UpdateAppleProviderResponse, error) {
|
||||
details, err := s.command.UpdateInstanceAppleProvider(ctx, req.Id, updateAppleProviderToCommand(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.UpdateAppleProviderResponse{
|
||||
Details: object_pb.DomainToChangeDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteProvider(ctx context.Context, req *admin_pb.DeleteProviderRequest) (*admin_pb.DeleteProviderResponse, error) {
|
||||
details, err := s.command.DeleteInstanceProvider(ctx, req.Id)
|
||||
if err != nil {
|
||||
|
||||
@@ -440,3 +440,27 @@ func updateLDAPProviderToCommand(req *admin_pb.UpdateLDAPProviderRequest) comman
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func addAppleProviderToCommand(req *admin_pb.AddAppleProviderRequest) command.AppleProvider {
|
||||
return command.AppleProvider{
|
||||
Name: req.Name,
|
||||
ClientID: req.ClientId,
|
||||
TeamID: req.TeamId,
|
||||
KeyID: req.KeyId,
|
||||
PrivateKey: req.PrivateKey,
|
||||
Scopes: req.Scopes,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func updateAppleProviderToCommand(req *admin_pb.UpdateAppleProviderRequest) command.AppleProvider {
|
||||
return command.AppleProvider{
|
||||
Name: req.Name,
|
||||
ClientID: req.ClientId,
|
||||
TeamID: req.TeamId,
|
||||
KeyID: req.KeyId,
|
||||
PrivateKey: req.PrivateKey,
|
||||
Scopes: req.Scopes,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,6 +414,8 @@ func providerTypeToPb(idpType domain.IDPType) idp_pb.ProviderType {
|
||||
return idp_pb.ProviderType_PROVIDER_TYPE_GITLAB_SELF_HOSTED
|
||||
case domain.IDPTypeGoogle:
|
||||
return idp_pb.ProviderType_PROVIDER_TYPE_GOOGLE
|
||||
case domain.IDPTypeApple:
|
||||
return idp_pb.ProviderType_PROVIDER_TYPE_APPLE
|
||||
case domain.IDPTypeUnspecified:
|
||||
return idp_pb.ProviderType_PROVIDER_TYPE_UNSPECIFIED
|
||||
default:
|
||||
@@ -470,6 +472,10 @@ func configToPb(config *query.IDPTemplate) *idp_pb.ProviderConfig {
|
||||
ldapConfigToPb(providerConfig, config.LDAPIDPTemplate)
|
||||
return providerConfig
|
||||
}
|
||||
if config.AppleIDPTemplate != nil {
|
||||
appleConfigToPb(providerConfig, config.AppleIDPTemplate)
|
||||
return providerConfig
|
||||
}
|
||||
return providerConfig
|
||||
}
|
||||
|
||||
@@ -620,3 +626,14 @@ func ldapAttributesToPb(attributes idp.LDAPAttributes) *idp_pb.LDAPAttributes {
|
||||
ProfileAttribute: attributes.ProfileAttribute,
|
||||
}
|
||||
}
|
||||
|
||||
func appleConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.AppleIDPTemplate) {
|
||||
providerConfig.Config = &idp_pb.ProviderConfig_Apple{
|
||||
Apple: &idp_pb.AppleConfig{
|
||||
ClientId: template.ClientID,
|
||||
TeamId: template.TeamID,
|
||||
KeyId: template.KeyID,
|
||||
Scopes: template.Scopes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +397,27 @@ func (s *Server) UpdateLDAPProvider(ctx context.Context, req *mgmt_pb.UpdateLDAP
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddAppleProvider(ctx context.Context, req *mgmt_pb.AddAppleProviderRequest) (*mgmt_pb.AddAppleProviderResponse, error) {
|
||||
id, details, err := s.command.AddOrgAppleProvider(ctx, authz.GetCtxData(ctx).OrgID, addAppleProviderToCommand(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddAppleProviderResponse{
|
||||
Id: id,
|
||||
Details: object_pb.DomainToAddDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateAppleProvider(ctx context.Context, req *mgmt_pb.UpdateAppleProviderRequest) (*mgmt_pb.UpdateAppleProviderResponse, error) {
|
||||
details, err := s.command.UpdateOrgAppleProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id, updateAppleProviderToCommand(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.UpdateAppleProviderResponse{
|
||||
Details: object_pb.DomainToChangeDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteProvider(ctx context.Context, req *mgmt_pb.DeleteProviderRequest) (*mgmt_pb.DeleteProviderResponse, error) {
|
||||
details, err := s.command.DeleteOrgProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id)
|
||||
if err != nil {
|
||||
|
||||
@@ -457,3 +457,27 @@ func updateLDAPProviderToCommand(req *mgmt_pb.UpdateLDAPProviderRequest) command
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func addAppleProviderToCommand(req *mgmt_pb.AddAppleProviderRequest) command.AppleProvider {
|
||||
return command.AppleProvider{
|
||||
Name: req.Name,
|
||||
ClientID: req.ClientId,
|
||||
TeamID: req.TeamId,
|
||||
KeyID: req.KeyId,
|
||||
PrivateKey: req.PrivateKey,
|
||||
Scopes: req.Scopes,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func updateAppleProviderToCommand(req *mgmt_pb.UpdateAppleProviderRequest) command.AppleProvider {
|
||||
return command.AppleProvider{
|
||||
Name: req.Name,
|
||||
ClientID: req.ClientId,
|
||||
TeamID: req.TeamId,
|
||||
KeyID: req.KeyId,
|
||||
PrivateKey: req.PrivateKey,
|
||||
Scopes: req.Scopes,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
z_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/form"
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
||||
@@ -52,6 +53,9 @@ type externalIDPCallbackData struct {
|
||||
Code string `schema:"code"`
|
||||
Error string `schema:"error"`
|
||||
ErrorDescription string `schema:"error_description"`
|
||||
|
||||
// Apple returns a user on first registration
|
||||
User string `schema:"user"`
|
||||
}
|
||||
|
||||
// CallbackURL generates the instance specific URL to the IDP callback handler
|
||||
@@ -115,7 +119,7 @@ func (h *Handler) handleCallback(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
idpUser, idpSession, err := h.fetchIDPUser(ctx, provider, data.Code)
|
||||
idpUser, idpSession, err := h.fetchIDPUser(ctx, provider, data.Code, data.User)
|
||||
if err != nil {
|
||||
cmdErr := h.commands.FailIDPIntent(ctx, intent, err.Error())
|
||||
logging.WithFields("intent", intent.AggregateID).OnError(cmdErr).Error("failed to push failed event on idp intent")
|
||||
@@ -214,7 +218,7 @@ func redirectToFailureURL(w http.ResponseWriter, r *http.Request, i *command.IDP
|
||||
http.Redirect(w, r, i.FailureURL.String(), http.StatusFound)
|
||||
}
|
||||
|
||||
func (h *Handler) fetchIDPUser(ctx context.Context, identityProvider idp.Provider, code string) (user idp.User, idpTokens idp.Session, err error) {
|
||||
func (h *Handler) fetchIDPUser(ctx context.Context, identityProvider idp.Provider, code string, appleUser string) (user idp.User, idpTokens idp.Session, err error) {
|
||||
var session idp.Session
|
||||
switch provider := identityProvider.(type) {
|
||||
case *oauth.Provider:
|
||||
@@ -229,6 +233,8 @@ func (h *Handler) fetchIDPUser(ctx context.Context, identityProvider idp.Provide
|
||||
session = &openid.Session{Provider: provider.Provider, Code: code}
|
||||
case *google.Provider:
|
||||
session = &openid.Session{Provider: provider.Provider, Code: code}
|
||||
case *apple.Provider:
|
||||
session = &apple.Session{Session: &openid.Session{Provider: provider.Provider, Code: code}, UserFormValue: appleUser}
|
||||
case *jwt.Provider, *ldap.Provider:
|
||||
return nil, nil, z_errs.ThrowInvalidArgument(nil, "IDP-52jmn", "Errors.ExternalIDP.IDPTypeNotImplemented")
|
||||
default:
|
||||
|
||||
@@ -3,9 +3,8 @@ package login
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
|
||||
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"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/apple"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
||||
@@ -41,6 +42,9 @@ type externalIDPData struct {
|
||||
type externalIDPCallbackData struct {
|
||||
State string `schema:"state"`
|
||||
Code string `schema:"code"`
|
||||
|
||||
// Apple returns a user on first registration
|
||||
User string `schema:"user"`
|
||||
}
|
||||
|
||||
type externalNotFoundOptionFormData struct {
|
||||
@@ -159,6 +163,8 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
|
||||
provider, err = l.gitlabSelfHostedProvider(r.Context(), identityProvider)
|
||||
case domain.IDPTypeGoogle:
|
||||
provider, err = l.googleProvider(r.Context(), identityProvider)
|
||||
case domain.IDPTypeApple:
|
||||
provider, err = l.appleProvider(r.Context(), identityProvider)
|
||||
case domain.IDPTypeLDAP:
|
||||
provider, err = l.ldapProvider(r.Context(), identityProvider)
|
||||
case domain.IDPTypeUnspecified:
|
||||
@@ -180,6 +186,18 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
|
||||
http.Redirect(w, r, session.GetAuthURL(), http.StatusFound)
|
||||
}
|
||||
|
||||
// handleExternalLoginCallbackForm handles the callback from a IDP with form_post.
|
||||
// It will redirect to the "normal" callback endpoint with the form data as query parameter.
|
||||
// This way cookies will be handled correctly (same site = lax).
|
||||
func (l *Login) handleExternalLoginCallbackForm(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
l.renderLogin(w, r, nil, err)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, HandlerPrefix+EndpointExternalLoginCallback+"?"+r.Form.Encode(), 302)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -259,6 +277,13 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
session = &openid.Session{Provider: provider.(*google.Provider).Provider, Code: data.Code}
|
||||
case domain.IDPTypeApple:
|
||||
provider, err = l.appleProvider(r.Context(), identityProvider)
|
||||
if err != nil {
|
||||
l.externalAuthFailed(w, r, authReq, nil, nil, err)
|
||||
return
|
||||
}
|
||||
session = &apple.Session{Session: &openid.Session{Provider: provider.(*apple.Provider).Provider, Code: data.Code}, UserFormValue: data.User}
|
||||
case domain.IDPTypeJWT,
|
||||
domain.IDPTypeLDAP,
|
||||
domain.IDPTypeUnspecified:
|
||||
@@ -936,6 +961,21 @@ func (l *Login) gitlabSelfHostedProvider(ctx context.Context, identityProvider *
|
||||
)
|
||||
}
|
||||
|
||||
func (l *Login) appleProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*apple.Provider, error) {
|
||||
privateKey, err := crypto.Decrypt(identityProvider.AppleIDPTemplate.PrivateKey, l.idpConfigAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return apple.New(
|
||||
identityProvider.AppleIDPTemplate.ClientID,
|
||||
identityProvider.AppleIDPTemplate.TeamID,
|
||||
identityProvider.AppleIDPTemplate.KeyID,
|
||||
l.baseURL(ctx)+EndpointExternalLoginCallbackFormPost,
|
||||
privateKey,
|
||||
identityProvider.AppleIDPTemplate.Scopes,
|
||||
)
|
||||
}
|
||||
|
||||
func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserGrant, resourceOwner string) error {
|
||||
if len(userGrants) == 0 {
|
||||
return nil
|
||||
@@ -971,6 +1011,8 @@ func tokens(session idp.Session) *oidc.Tokens[*oidc.IDTokenClaims] {
|
||||
return s.Tokens
|
||||
case *azuread.Session:
|
||||
return s.Tokens
|
||||
case *apple.Session:
|
||||
return s.Tokens
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -120,6 +120,12 @@ func createCSRFInterceptor(cookieName string, csrfCookieKey []byte, externalSecu
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
// ignore form post callback
|
||||
// it will redirect to the "normal" callback, where the cookie is set again
|
||||
if r.URL.Path == EndpointExternalLoginCallbackFormPost && r.Method == http.MethodPost {
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
csrf.Protect(csrfCookieKey,
|
||||
csrf.Secure(externalSecure),
|
||||
csrf.CookieName(http_utils.SetCookiePrefix(cookieName, "", path, externalSecure)),
|
||||
|
||||
@@ -7,44 +7,45 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
EndpointRoot = "/"
|
||||
EndpointHealthz = "/healthz"
|
||||
EndpointReadiness = "/ready"
|
||||
EndpointLogin = "/login"
|
||||
EndpointExternalLogin = "/login/externalidp"
|
||||
EndpointExternalLoginCallback = "/login/externalidp/callback"
|
||||
EndpointJWTAuthorize = "/login/jwt/authorize"
|
||||
EndpointJWTCallback = "/login/jwt/callback"
|
||||
EndpointLDAPLogin = "/login/ldap"
|
||||
EndpointLDAPCallback = "/login/ldap/callback"
|
||||
EndpointPasswordlessLogin = "/login/passwordless"
|
||||
EndpointPasswordlessRegistration = "/login/passwordless/init"
|
||||
EndpointPasswordlessPrompt = "/login/passwordless/prompt"
|
||||
EndpointLoginName = "/loginname"
|
||||
EndpointUserSelection = "/userselection"
|
||||
EndpointChangeUsername = "/username/change"
|
||||
EndpointPassword = "/password"
|
||||
EndpointInitPassword = "/password/init"
|
||||
EndpointChangePassword = "/password/change"
|
||||
EndpointPasswordReset = "/password/reset"
|
||||
EndpointInitUser = "/user/init"
|
||||
EndpointMFAVerify = "/mfa/verify"
|
||||
EndpointMFAPrompt = "/mfa/prompt"
|
||||
EndpointMFAInitVerify = "/mfa/init/verify"
|
||||
EndpointMFASMSInitVerify = "/mfa/init/sms/verify"
|
||||
EndpointMFAOTPVerify = "/mfa/otp/verify"
|
||||
EndpointMFAInitU2FVerify = "/mfa/init/u2f/verify"
|
||||
EndpointU2FVerification = "/mfa/u2f/verify"
|
||||
EndpointMailVerification = "/mail/verification"
|
||||
EndpointMailVerified = "/mail/verified"
|
||||
EndpointRegisterOption = "/register/option"
|
||||
EndpointRegister = "/register"
|
||||
EndpointExternalRegister = "/register/externalidp"
|
||||
EndpointExternalRegisterCallback = "/register/externalidp/callback"
|
||||
EndpointRegisterOrg = "/register/org"
|
||||
EndpointLogoutDone = "/logout/done"
|
||||
EndpointLoginSuccess = "/login/success"
|
||||
EndpointExternalNotFoundOption = "/externaluser/option"
|
||||
EndpointRoot = "/"
|
||||
EndpointHealthz = "/healthz"
|
||||
EndpointReadiness = "/ready"
|
||||
EndpointLogin = "/login"
|
||||
EndpointExternalLogin = "/login/externalidp"
|
||||
EndpointExternalLoginCallback = "/login/externalidp/callback"
|
||||
EndpointExternalLoginCallbackFormPost = "/login/externalidp/callback/form"
|
||||
EndpointJWTAuthorize = "/login/jwt/authorize"
|
||||
EndpointJWTCallback = "/login/jwt/callback"
|
||||
EndpointLDAPLogin = "/login/ldap"
|
||||
EndpointLDAPCallback = "/login/ldap/callback"
|
||||
EndpointPasswordlessLogin = "/login/passwordless"
|
||||
EndpointPasswordlessRegistration = "/login/passwordless/init"
|
||||
EndpointPasswordlessPrompt = "/login/passwordless/prompt"
|
||||
EndpointLoginName = "/loginname"
|
||||
EndpointUserSelection = "/userselection"
|
||||
EndpointChangeUsername = "/username/change"
|
||||
EndpointPassword = "/password"
|
||||
EndpointInitPassword = "/password/init"
|
||||
EndpointChangePassword = "/password/change"
|
||||
EndpointPasswordReset = "/password/reset"
|
||||
EndpointInitUser = "/user/init"
|
||||
EndpointMFAVerify = "/mfa/verify"
|
||||
EndpointMFAPrompt = "/mfa/prompt"
|
||||
EndpointMFAInitVerify = "/mfa/init/verify"
|
||||
EndpointMFASMSInitVerify = "/mfa/init/sms/verify"
|
||||
EndpointMFAOTPVerify = "/mfa/otp/verify"
|
||||
EndpointMFAInitU2FVerify = "/mfa/init/u2f/verify"
|
||||
EndpointU2FVerification = "/mfa/u2f/verify"
|
||||
EndpointMailVerification = "/mail/verification"
|
||||
EndpointMailVerified = "/mail/verified"
|
||||
EndpointRegisterOption = "/register/option"
|
||||
EndpointRegister = "/register"
|
||||
EndpointExternalRegister = "/register/externalidp"
|
||||
EndpointExternalRegisterCallback = "/register/externalidp/callback"
|
||||
EndpointRegisterOrg = "/register/org"
|
||||
EndpointLogoutDone = "/logout/done"
|
||||
EndpointLoginSuccess = "/login/success"
|
||||
EndpointExternalNotFoundOption = "/externaluser/option"
|
||||
|
||||
EndpointResources = "/resources"
|
||||
EndpointDynamicResources = "/resources/dynamic"
|
||||
@@ -71,6 +72,7 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
|
||||
router.HandleFunc(EndpointLogin, login.handleLogin).Methods(http.MethodGet, http.MethodPost)
|
||||
router.HandleFunc(EndpointExternalLogin, login.handleExternalLogin).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointExternalLoginCallback, login.handleExternalLoginCallback).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointExternalLoginCallbackFormPost, login.handleExternalLoginCallbackForm).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointJWTAuthorize, login.handleJWTRequest).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointJWTCallback, login.handleJWTCallback).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointPasswordlessLogin, login.handlePasswordlessVerification).Methods(http.MethodPost)
|
||||
|
||||
@@ -360,6 +360,7 @@ Footer:
|
||||
PrivacyPolicy: Политика за поверителност
|
||||
Help: Помогне
|
||||
SupportEmail: Поддръжка на имейл
|
||||
SignIn: Влезте с {{.Provider}}
|
||||
Errors:
|
||||
Internal: Възникна вътрешна грешка
|
||||
AuthRequest:
|
||||
|
||||
@@ -372,6 +372,8 @@ Footer:
|
||||
Help: Hilfe
|
||||
SupportEmail: Support E-Mail
|
||||
|
||||
SignIn: Mit {{.Provider}} anmelden
|
||||
|
||||
Errors:
|
||||
Internal: Es ist ein interner Fehler aufgetreten
|
||||
AuthRequest:
|
||||
|
||||
@@ -372,6 +372,8 @@ Footer:
|
||||
Help: Help
|
||||
SupportEmail: Support E-mail
|
||||
|
||||
SignIn: Sign in with {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: An internal error occurred
|
||||
AuthRequest:
|
||||
|
||||
@@ -354,6 +354,8 @@ Footer:
|
||||
Help: Ayuda
|
||||
SupportEmail: Email de soporte
|
||||
|
||||
SignIn: Iniciar sesión con {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: Se produjo un error interno
|
||||
AuthRequest:
|
||||
|
||||
@@ -372,6 +372,8 @@ Footer:
|
||||
Help: Aide
|
||||
SupportEmail: E-mail d'assistance
|
||||
|
||||
SignIn: Connexion avec {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: Une erreur interne s'est produite
|
||||
AuthRequest:
|
||||
|
||||
@@ -372,6 +372,8 @@ Footer:
|
||||
Help: Aiuto
|
||||
SupportEmail: E-mail di supporto
|
||||
|
||||
SignIn: Accedi con {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: Si è verificato un errore interno
|
||||
AuthRequest:
|
||||
|
||||
@@ -363,6 +363,8 @@ Footer:
|
||||
PrivacyPolicy: プライバシーポリシー
|
||||
Help: ヘルプ
|
||||
|
||||
SignIn: '{{.Provider}} でサインイン'
|
||||
|
||||
Errors:
|
||||
Internal: 内部でエラーが発生しました
|
||||
AuthRequest:
|
||||
|
||||
@@ -372,6 +372,8 @@ Footer:
|
||||
Help: Помош
|
||||
SupportEmail: Е-пошта за поддршка
|
||||
|
||||
SignIn: Пријавете се со {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: Се појави внатрешна грешка
|
||||
AuthRequest:
|
||||
|
||||
@@ -372,6 +372,8 @@ Footer:
|
||||
Help: Pomoc
|
||||
SupportEmail: E-mail wsparcia
|
||||
|
||||
SignIn: Zaloguj się, używając konta {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: Wewnętrzny błąd
|
||||
AuthRequest:
|
||||
|
||||
@@ -366,6 +366,8 @@ Footer:
|
||||
Help: Ajuda
|
||||
SupportEmail: E-mail de suporte
|
||||
|
||||
SignIn: Iniciar sessão com a {{.Provider}}
|
||||
|
||||
Errors:
|
||||
Internal: Ocorreu um erro interno
|
||||
AuthRequest:
|
||||
|
||||
@@ -372,6 +372,8 @@ Footer:
|
||||
Help: 帮助
|
||||
SupportEmail: 支持邮箱
|
||||
|
||||
SignIn: 通过 {{.Provider}} 登录
|
||||
|
||||
Errors:
|
||||
Internal: 发生了内部错误
|
||||
AuthRequest:
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
|
||||
<title>White Logo Square </title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="White-Logo-Square-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
|
||||
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
|
||||
<title>Black Logo Square</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Black-Logo-Square" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
|
||||
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#000000" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
+18
-4
@@ -2,6 +2,7 @@ $lgn-idp-margin: 0.5rem 0;
|
||||
$lgn-idp-padding: 0 1px;
|
||||
$lgn-idp-provider-name-line-height: 36px;
|
||||
$lgn-idp-border-radius: 0.5rem;
|
||||
$lgn-idp-logo-size: 46px;
|
||||
|
||||
@mixin lgn-idp-base {
|
||||
display: block;
|
||||
@@ -17,14 +18,14 @@ $lgn-idp-border-radius: 0.5rem;
|
||||
transition: border-color 0.2s ease-in-out;
|
||||
|
||||
span.logo {
|
||||
height: 46px;
|
||||
width: 46px;
|
||||
height: $lgn-idp-logo-size;
|
||||
width: $lgn-idp-logo-size;
|
||||
}
|
||||
|
||||
span.provider-name {
|
||||
line-height: $lgn-idp-provider-name-line-height;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
position: relative;
|
||||
left: calc(50% - $lgn-idp-logo-size);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
@@ -75,4 +76,17 @@ $lgn-idp-border-radius: 0.5rem;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&.apple {
|
||||
span.logo {
|
||||
height: 46px;
|
||||
width: 46px;
|
||||
background-image: var(--apple-image-src);
|
||||
background-size: 25px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 5px;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,8 @@
|
||||
--zitadel-color-github-background: #ffffff;
|
||||
--zitadel-color-gitlab-text: #8b8d8d;
|
||||
--zitadel-color-gitlab-background: #ffffff;
|
||||
--zitadel-color-apple-text: #8b8d8d;
|
||||
--zitadel-color-apple-background: #ffffff;
|
||||
|
||||
--zitadel-color-qr: var(--zitadel-color-black);
|
||||
--zitadel-color-qr-background: var(--zitadel-color-white);
|
||||
@@ -125,6 +127,7 @@
|
||||
--github-image-src: url(../../../images/idp/github.png);
|
||||
--gitlab-image-src: url(../../../images/idp/gitlab.png);
|
||||
--azure-image-src: url(../../../images/idp/ms.svg);
|
||||
--apple-image-src: url(../../../images/idp/apple.svg);
|
||||
}
|
||||
|
||||
.lgn-dark-theme {
|
||||
@@ -227,9 +230,12 @@
|
||||
--zitadel-color-github-background: #ffffff;
|
||||
--zitadel-color-gitlab-text: #8b8d8d;
|
||||
--zitadel-color-gitlab-background: #ffffff;
|
||||
--zitadel-color-apple-text: #8b8d8d;
|
||||
--zitadel-color-apple-background: #ffffff;
|
||||
|
||||
--google-image-src: url(../../../images/idp/google.png);
|
||||
--github-image-src: url(../../../images/idp/github-white.png);
|
||||
--gitlab-image-src: url(../../../images/idp/gitlab.png);
|
||||
--azure-image-src: url(../../../images/idp/ms.svg);
|
||||
--apple-image-src: url(../../../images/idp/apple-dark.svg);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,11 @@
|
||||
<a href="{{ externalIDPAuthURL $reqid $provider.IDPConfigID}}"
|
||||
class="lgn-idp {{idpProviderClass $provider.IDPType}}">
|
||||
<span class="logo"></span>
|
||||
{{if $provider.IDPType.IsSignInButton}}
|
||||
<span class="provider-name">{{t "SignIn" "Provider" $provider.DisplayName}}</span>
|
||||
{{else}}
|
||||
<span class="provider-name">{{$provider.DisplayName}}</span>
|
||||
{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,11 @@
|
||||
<a href="{{ externalIDPRegisterURL $reqid $provider.IDPConfigID}}"
|
||||
class="lgn-idp {{idpProviderClass $provider.IDPType}}">
|
||||
<span class="logo"></span>
|
||||
{{if $provider.IDPType.IsSignInButton}}
|
||||
<span class="provider-name">{{t "SignIn" "Provider" $provider.DisplayName}}</span>
|
||||
{{else}}
|
||||
<span class="provider-name">{{$provider.DisplayName}}</span>
|
||||
{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -110,6 +110,16 @@ type LDAPProvider struct {
|
||||
IDPOptions idp.Options
|
||||
}
|
||||
|
||||
type AppleProvider struct {
|
||||
Name string
|
||||
ClientID string
|
||||
TeamID string
|
||||
KeyID string
|
||||
PrivateKey []byte
|
||||
Scopes []string
|
||||
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())
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/jwt"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
||||
@@ -215,6 +216,8 @@ func tokensForSucceededIDPIntent(session idp.Session, encryptionAlg crypto.Encry
|
||||
tokens = s.Tokens
|
||||
case *azuread.Session:
|
||||
tokens = s.Tokens
|
||||
case *apple.Session:
|
||||
tokens = s.Tokens
|
||||
default:
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package command
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
providers "github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
||||
@@ -1587,6 +1589,138 @@ func (wm *LDAPIDPWriteModel) GetProviderOptions() idp.Options {
|
||||
return wm.Options
|
||||
}
|
||||
|
||||
type AppleIDPWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
ID string
|
||||
Name string
|
||||
ClientID string
|
||||
TeamID string
|
||||
KeyID string
|
||||
PrivateKey *crypto.CryptoValue
|
||||
Scopes []string
|
||||
idp.Options
|
||||
|
||||
State domain.IDPState
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *idp.AppleIDPAddedEvent:
|
||||
wm.reduceAddedEvent(e)
|
||||
case *idp.AppleIDPChangedEvent:
|
||||
wm.reduceChangedEvent(e)
|
||||
case *idp.RemovedEvent:
|
||||
wm.State = domain.IDPStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) reduceAddedEvent(e *idp.AppleIDPAddedEvent) {
|
||||
wm.Name = e.Name
|
||||
wm.ClientID = e.ClientID
|
||||
wm.TeamID = e.TeamID
|
||||
wm.KeyID = e.KeyID
|
||||
wm.PrivateKey = e.PrivateKey
|
||||
wm.Scopes = e.Scopes
|
||||
wm.Options = e.Options
|
||||
wm.State = domain.IDPStateActive
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) reduceChangedEvent(e *idp.AppleIDPChangedEvent) {
|
||||
if e.Name != nil {
|
||||
wm.Name = *e.Name
|
||||
}
|
||||
if e.ClientID != nil {
|
||||
wm.ClientID = *e.ClientID
|
||||
}
|
||||
if e.PrivateKey != nil {
|
||||
wm.PrivateKey = e.PrivateKey
|
||||
}
|
||||
if e.Scopes != nil {
|
||||
wm.Scopes = e.Scopes
|
||||
}
|
||||
wm.Options.ReduceChanges(e.OptionChanges)
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) NewChanges(
|
||||
name string,
|
||||
clientID string,
|
||||
teamID string,
|
||||
keyID string,
|
||||
privateKey []byte,
|
||||
secretCrypto crypto.Crypto,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) ([]idp.AppleIDPChanges, error) {
|
||||
changes := make([]idp.AppleIDPChanges, 0)
|
||||
var encryptedKey *crypto.CryptoValue
|
||||
var err error
|
||||
if len(privateKey) != 0 {
|
||||
encryptedKey, err = crypto.Crypt(privateKey, secretCrypto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changes = append(changes, idp.ChangeApplePrivateKey(encryptedKey))
|
||||
}
|
||||
if wm.Name != name {
|
||||
changes = append(changes, idp.ChangeAppleName(name))
|
||||
}
|
||||
if wm.ClientID != clientID {
|
||||
changes = append(changes, idp.ChangeAppleClientID(clientID))
|
||||
}
|
||||
if wm.TeamID != teamID {
|
||||
changes = append(changes, idp.ChangeAppleTeamID(teamID))
|
||||
}
|
||||
if wm.KeyID != keyID {
|
||||
changes = append(changes, idp.ChangeAppleKeyID(keyID))
|
||||
}
|
||||
if slices.Compare(wm.Scopes, scopes) != 0 {
|
||||
changes = append(changes, idp.ChangeAppleScopes(scopes))
|
||||
}
|
||||
|
||||
opts := wm.Options.Changes(options)
|
||||
if !opts.IsZero() {
|
||||
changes = append(changes, idp.ChangeAppleOptions(opts))
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.EncryptionAlgorithm) (providers.Provider, error) {
|
||||
privateKey, err := crypto.Decrypt(wm.PrivateKey, idpAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts := make([]oidc.ProviderOpts, 0, 4)
|
||||
if wm.IsCreationAllowed {
|
||||
opts = append(opts, oidc.WithCreationAllowed())
|
||||
}
|
||||
if wm.IsLinkingAllowed {
|
||||
opts = append(opts, oidc.WithLinkingAllowed())
|
||||
}
|
||||
if wm.IsAutoCreation {
|
||||
opts = append(opts, oidc.WithAutoCreation())
|
||||
}
|
||||
if wm.IsAutoUpdate {
|
||||
opts = append(opts, oidc.WithAutoUpdate())
|
||||
}
|
||||
return apple.New(
|
||||
wm.ClientID,
|
||||
wm.TeamID,
|
||||
wm.KeyID,
|
||||
callbackURL,
|
||||
privateKey,
|
||||
wm.Scopes,
|
||||
opts...,
|
||||
)
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) GetProviderOptions() idp.Options {
|
||||
return wm.Options
|
||||
}
|
||||
|
||||
type IDPRemoveWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
@@ -1617,6 +1751,8 @@ func (wm *IDPRemoveWriteModel) Reduce() error {
|
||||
wm.reduceAdded(e.ID)
|
||||
case *idp.LDAPIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID)
|
||||
case *idp.AppleIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID)
|
||||
case *idp.RemovedEvent:
|
||||
wm.reduceRemoved(e.ID)
|
||||
case *idpconfig.IDPConfigAddedEvent:
|
||||
@@ -1699,6 +1835,10 @@ func (wm *IDPTypeWriteModel) Reduce() error {
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeLDAP, e.Aggregate())
|
||||
case *org.LDAPIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeLDAP, e.Aggregate())
|
||||
case *instance.AppleIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeApple, e.Aggregate())
|
||||
case *org.AppleIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeApple, e.Aggregate())
|
||||
case *instance.OIDCIDPMigratedAzureADEvent:
|
||||
wm.reduceChanged(e.ID, domain.IDPTypeAzureAD)
|
||||
case *org.OIDCIDPMigratedAzureADEvent:
|
||||
@@ -1774,6 +1914,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
instance.GitLabSelfHostedIDPAddedEventType,
|
||||
instance.GoogleIDPAddedEventType,
|
||||
instance.LDAPIDPAddedEventType,
|
||||
instance.AppleIDPAddedEventType,
|
||||
instance.OIDCIDPMigratedAzureADEventType,
|
||||
instance.OIDCIDPMigratedGoogleEventType,
|
||||
instance.IDPRemovedEventType,
|
||||
@@ -1792,6 +1933,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
org.GitLabSelfHostedIDPAddedEventType,
|
||||
org.GoogleIDPAddedEventType,
|
||||
org.LDAPIDPAddedEventType,
|
||||
org.AppleIDPAddedEventType,
|
||||
org.OIDCIDPMigratedAzureADEventType,
|
||||
org.OIDCIDPMigratedGoogleEventType,
|
||||
org.IDPRemovedEventType,
|
||||
@@ -1859,6 +2001,8 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp
|
||||
writeModel.model = NewGitLabSelfHostedInstanceIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeGoogle:
|
||||
writeModel.model = NewGoogleInstanceIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeApple:
|
||||
writeModel.model = NewAppleInstanceIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeUnspecified:
|
||||
fallthrough
|
||||
default:
|
||||
@@ -1886,6 +2030,8 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp
|
||||
writeModel.model = NewGitLabSelfHostedOrgIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeGoogle:
|
||||
writeModel.model = NewGoogleOrgIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeApple:
|
||||
writeModel.model = NewAppleOrgIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeUnspecified:
|
||||
fallthrough
|
||||
default:
|
||||
|
||||
@@ -467,6 +467,48 @@ func (c *Commands) UpdateInstanceLDAPProvider(ctx context.Context, id string, pr
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddInstanceAppleProvider(ctx context.Context, provider AppleProvider) (string, *domain.ObjectDetails, error) {
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
instanceAgg := instance.NewAggregate(instanceID)
|
||||
id, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
writeModel := NewAppleInstanceIDPWriteModel(instanceID, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddInstanceAppleProvider(instanceAgg, writeModel, provider))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return id, pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) UpdateInstanceAppleProvider(ctx context.Context, id string, provider AppleProvider) (*domain.ObjectDetails, error) {
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
instanceAgg := instance.NewAggregate(instanceID)
|
||||
writeModel := NewAppleInstanceIDPWriteModel(instanceID, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateInstanceAppleProvider(instanceAgg, writeModel, provider))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cmds) == 0 {
|
||||
// no change, so return directly
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: writeModel.ProcessedSequence,
|
||||
EventDate: writeModel.ChangeDate,
|
||||
ResourceOwner: writeModel.ResourceOwner,
|
||||
}, nil
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteInstanceProvider(ctx context.Context, id string) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareDeleteInstanceProvider(instanceAgg, id))
|
||||
@@ -1518,6 +1560,98 @@ func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, writ
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareAddInstanceAppleProvider(a *instance.Aggregate, writeModel *InstanceAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-jkn3w", "Errors.IDP.ClientIDMissing")
|
||||
}
|
||||
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Ffg32", "Errors.IDP.TeamIDMissing")
|
||||
}
|
||||
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-GDjm5", "Errors.IDP.KeyIDMissing")
|
||||
}
|
||||
if len(provider.PrivateKey) == 0 {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-GVD4n", "Errors.IDP.PrivateKeyMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privateKey, err := crypto.Encrypt(provider.PrivateKey, c.idpConfigEncryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{
|
||||
instance.NewAppleIDPAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.ClientID,
|
||||
provider.TeamID,
|
||||
provider.KeyID,
|
||||
privateKey,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareUpdateInstanceAppleProvider(a *instance.Aggregate, writeModel *InstanceAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-FRHBH", "Errors.IDMissing")
|
||||
}
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SFm4l", "Errors.IDP.ClientIDMissing")
|
||||
}
|
||||
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SG34t", "Errors.IDP.TeamIDMissing")
|
||||
}
|
||||
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Gh4z2", "Errors.IDP.KeyIDMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !writeModel.State.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "INST-SG3bh", "Errors.IDPConfig.NotExisting")
|
||||
}
|
||||
event, err := writeModel.NewChangedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.ClientID,
|
||||
provider.TeamID,
|
||||
provider.KeyID,
|
||||
provider.PrivateKey,
|
||||
c.idpConfigEncryption,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
)
|
||||
if err != nil || event == nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{event}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareDeleteInstanceProvider(a *instance.Aggregate, id string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
|
||||
@@ -793,6 +793,73 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
|
||||
return instance.NewLDAPIDPChangedEvent(ctx, aggregate, id, changes)
|
||||
}
|
||||
|
||||
type InstanceAppleIDPWriteModel struct {
|
||||
AppleIDPWriteModel
|
||||
}
|
||||
|
||||
func NewAppleInstanceIDPWriteModel(instanceID, id string) *InstanceAppleIDPWriteModel {
|
||||
return &InstanceAppleIDPWriteModel{
|
||||
AppleIDPWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: instanceID,
|
||||
ResourceOwner: instanceID,
|
||||
},
|
||||
ID: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *InstanceAppleIDPWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *instance.AppleIDPAddedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||
case *instance.AppleIDPChangedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPChangedEvent)
|
||||
case *instance.IDPRemovedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
default:
|
||||
wm.AppleIDPWriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *InstanceAppleIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(instance.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
instance.AppleIDPAddedEventType,
|
||||
instance.AppleIDPChangedEventType,
|
||||
instance.IDPRemovedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *InstanceAppleIDPWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID string,
|
||||
privateKey []byte,
|
||||
secretCrypto crypto.Crypto,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) (*instance.AppleIDPChangedEvent, error) {
|
||||
|
||||
changes, err := wm.AppleIDPWriteModel.NewChanges(name, clientID, teamID, keyID, privateKey, secretCrypto, scopes, options)
|
||||
if err != nil || len(changes) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return instance.NewAppleIDPChangedEvent(ctx, aggregate, id, changes)
|
||||
}
|
||||
|
||||
type InstanceIDPRemoveWriteModel struct {
|
||||
IDPRemoveWriteModel
|
||||
}
|
||||
@@ -832,6 +899,8 @@ func (wm *InstanceIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event)
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
|
||||
case *instance.LDAPIDPAddedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.LDAPIDPAddedEvent)
|
||||
case *instance.AppleIDPAddedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||
case *instance.IDPRemovedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
case *instance.IDPConfigAddedEvent:
|
||||
@@ -861,6 +930,7 @@ func (wm *InstanceIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
instance.GitLabSelfHostedIDPAddedEventType,
|
||||
instance.GoogleIDPAddedEventType,
|
||||
instance.LDAPIDPAddedEventType,
|
||||
instance.AppleIDPAddedEventType,
|
||||
instance.IDPRemovedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
|
||||
@@ -4857,3 +4857,464 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddInstanceAppleIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
provider AppleProvider
|
||||
}
|
||||
type res struct {
|
||||
id string
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-jkn3w", "Errors.IDP.ClientIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid teamID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Ffg32", "Errors.IDP.TeamIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid keyID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-GDjm5", "Errors.IDP.KeyIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid privateKey",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-GVD4n", "Errors.IDP.PrivateKeyMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
},
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
PrivateKey: []byte("privateKey"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok all set",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
[]string{"name", "email"},
|
||||
idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
)),
|
||||
},
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
PrivateKey: []byte("privateKey"),
|
||||
Scopes: []string{"name", "email"},
|
||||
IDPOptions: idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
idpConfigEncryption: tt.fields.secretCrypto,
|
||||
}
|
||||
id, got, err := c.AddInstanceAppleProvider(tt.args.ctx, tt.args.provider)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.id, id)
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_UpdateInstanceAppleIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
id string
|
||||
provider AppleProvider
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid id",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-FRHBH", "Errors.IDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-SFm4l", "Errors.IDP.ClientIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid teamID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-SG34t", "Errors.IDP.TeamIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid keyID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Gh4z2", "Errors.IDP.KeyIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
func() eventstore.Command {
|
||||
t := true
|
||||
event, _ := instance.NewAppleIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
[]idp.AppleIDPChanges{
|
||||
idp.ChangeAppleClientID("clientID2"),
|
||||
idp.ChangeAppleTeamID("teamID2"),
|
||||
idp.ChangeAppleKeyID("keyID2"),
|
||||
idp.ChangeApplePrivateKey(&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("newPrivateKey"),
|
||||
}),
|
||||
idp.ChangeAppleScopes([]string{"name", "email"}),
|
||||
idp.ChangeAppleOptions(idp.OptionChanges{
|
||||
IsCreationAllowed: &t,
|
||||
IsLinkingAllowed: &t,
|
||||
IsAutoCreation: &t,
|
||||
IsAutoUpdate: &t,
|
||||
}),
|
||||
},
|
||||
)
|
||||
return event
|
||||
}(),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID2",
|
||||
TeamID: "teamID2",
|
||||
KeyID: "keyID2",
|
||||
PrivateKey: []byte("newPrivateKey"),
|
||||
Scopes: []string{"name", "email"},
|
||||
IDPOptions: idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idpConfigEncryption: tt.fields.secretCrypto,
|
||||
}
|
||||
got, err := c.UpdateInstanceAppleProvider(tt.args.ctx, tt.args.id, tt.args.provider)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,6 +444,46 @@ func (c *Commands) UpdateOrgLDAPProvider(ctx context.Context, resourceOwner, id
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddOrgAppleProvider(ctx context.Context, resourceOwner string, provider AppleProvider) (string, *domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
id, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
writeModel := NewAppleOrgIDPWriteModel(resourceOwner, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgAppleProvider(orgAgg, writeModel, provider))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return id, pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) UpdateOrgAppleProvider(ctx context.Context, resourceOwner, id string, provider AppleProvider) (*domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
writeModel := NewAppleOrgIDPWriteModel(resourceOwner, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateOrgAppleProvider(orgAgg, writeModel, provider))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cmds) == 0 {
|
||||
// no change, so return directly
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: writeModel.ProcessedSequence,
|
||||
EventDate: writeModel.ChangeDate,
|
||||
ResourceOwner: writeModel.ResourceOwner,
|
||||
}, nil
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteOrgProvider(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareDeleteOrgProvider(orgAgg, resourceOwner, id))
|
||||
@@ -1507,6 +1547,98 @@ func (c *Commands) prepareUpdateOrgLDAPProvider(a *org.Aggregate, writeModel *Or
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareAddOrgAppleProvider(a *org.Aggregate, writeModel *OrgAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-jkn3w", "Errors.IDP.ClientIDMissing")
|
||||
}
|
||||
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Ffg32", "Errors.IDP.TeamIDMissing")
|
||||
}
|
||||
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-GDjm5", "Errors.IDP.KeyIDMissing")
|
||||
}
|
||||
if len(provider.PrivateKey) == 0 {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-GVD4n", "Errors.IDP.PrivateKeyMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privateKey, err := crypto.Encrypt(provider.PrivateKey, c.idpConfigEncryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{
|
||||
org.NewAppleIDPAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.ClientID,
|
||||
provider.TeamID,
|
||||
provider.KeyID,
|
||||
privateKey,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareUpdateOrgAppleProvider(a *org.Aggregate, writeModel *OrgAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-FRHBH", "Errors.IDMissing")
|
||||
}
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SFm4l", "Errors.IDP.ClientIDMissing")
|
||||
}
|
||||
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SG34t", "Errors.IDP.TeamIDMissing")
|
||||
}
|
||||
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Gh4z2", "Errors.IDP.KeyIDMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !writeModel.State.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-SG3bh", "Errors.IDPConfig.NotExisting")
|
||||
}
|
||||
event, err := writeModel.NewChangedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.ClientID,
|
||||
provider.TeamID,
|
||||
provider.KeyID,
|
||||
provider.PrivateKey,
|
||||
c.idpConfigEncryption,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
)
|
||||
if err != nil || event == nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{event}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareDeleteOrgProvider(a *org.Aggregate, resourceOwner, id string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
|
||||
@@ -803,6 +803,73 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
|
||||
return org.NewLDAPIDPChangedEvent(ctx, aggregate, id, changes)
|
||||
}
|
||||
|
||||
type OrgAppleIDPWriteModel struct {
|
||||
AppleIDPWriteModel
|
||||
}
|
||||
|
||||
func NewAppleOrgIDPWriteModel(orgID, id string) *OrgAppleIDPWriteModel {
|
||||
return &OrgAppleIDPWriteModel{
|
||||
AppleIDPWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: orgID,
|
||||
ResourceOwner: orgID,
|
||||
},
|
||||
ID: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgAppleIDPWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *org.AppleIDPAddedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||
case *org.AppleIDPChangedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPChangedEvent)
|
||||
case *org.IDPRemovedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
default:
|
||||
wm.AppleIDPWriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgAppleIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(org.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
org.AppleIDPAddedEventType,
|
||||
org.AppleIDPChangedEventType,
|
||||
org.IDPRemovedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *OrgAppleIDPWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID string,
|
||||
privateKey []byte,
|
||||
secretCrypto crypto.Crypto,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) (*org.AppleIDPChangedEvent, error) {
|
||||
|
||||
changes, err := wm.AppleIDPWriteModel.NewChanges(name, clientID, teamID, keyID, privateKey, secretCrypto, scopes, options)
|
||||
if err != nil || len(changes) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return org.NewAppleIDPChangedEvent(ctx, aggregate, id, changes)
|
||||
}
|
||||
|
||||
type OrgIDPRemoveWriteModel struct {
|
||||
IDPRemoveWriteModel
|
||||
}
|
||||
@@ -842,6 +909,8 @@ func (wm *OrgIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
|
||||
case *org.LDAPIDPAddedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.LDAPIDPAddedEvent)
|
||||
case *org.AppleIDPAddedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||
case *org.IDPRemovedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
case *org.IDPConfigAddedEvent:
|
||||
@@ -871,6 +940,7 @@ func (wm *OrgIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
org.GitLabSelfHostedIDPAddedEventType,
|
||||
org.GoogleIDPAddedEventType,
|
||||
org.LDAPIDPAddedEventType,
|
||||
org.AppleIDPAddedEventType,
|
||||
org.IDPRemovedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
|
||||
@@ -4926,6 +4926,473 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddOrgAppleIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
resourceOwner string
|
||||
provider AppleProvider
|
||||
}
|
||||
type res struct {
|
||||
id string
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-jkn3w", "Errors.IDP.ClientIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid teamID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-Ffg32", "Errors.IDP.TeamIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid keyID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-GDjm5", "Errors.IDP.KeyIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid privateKey",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-GVD4n", "Errors.IDP.PrivateKeyMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
org.NewAppleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
PrivateKey: []byte("privateKey"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok all set",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
org.NewAppleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
[]string{"name", "email"},
|
||||
idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
PrivateKey: []byte("privateKey"),
|
||||
Scopes: []string{"name", "email"},
|
||||
IDPOptions: idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
idpConfigEncryption: tt.fields.secretCrypto,
|
||||
}
|
||||
id, got, err := c.AddOrgAppleProvider(tt.args.ctx, tt.args.resourceOwner, tt.args.provider)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.id, id)
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_UpdateOrgAppleIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
resourceOwner string
|
||||
id string
|
||||
provider AppleProvider
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid id",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-FRHBH", "Errors.IDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-SFm4l", "Errors.IDP.ClientIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid teamID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-SG34t", "Errors.IDP.TeamIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid keyID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-Gh4z2", "Errors.IDP.KeyIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewAppleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewAppleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
func() eventstore.Command {
|
||||
t := true
|
||||
event, _ := org.NewAppleIDPChangedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
[]idp.AppleIDPChanges{
|
||||
idp.ChangeAppleClientID("clientID2"),
|
||||
idp.ChangeAppleTeamID("teamID2"),
|
||||
idp.ChangeAppleKeyID("keyID2"),
|
||||
idp.ChangeApplePrivateKey(&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("newPrivateKey"),
|
||||
}),
|
||||
idp.ChangeAppleScopes([]string{"name", "email"}),
|
||||
idp.ChangeAppleOptions(idp.OptionChanges{
|
||||
IsCreationAllowed: &t,
|
||||
IsLinkingAllowed: &t,
|
||||
IsAutoCreation: &t,
|
||||
IsAutoUpdate: &t,
|
||||
}),
|
||||
},
|
||||
)
|
||||
return event
|
||||
}(),
|
||||
),
|
||||
),
|
||||
),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID2",
|
||||
TeamID: "teamID2",
|
||||
KeyID: "keyID2",
|
||||
PrivateKey: []byte("newPrivateKey"),
|
||||
Scopes: []string{"name", "email"},
|
||||
IDPOptions: idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idpConfigEncryption: tt.fields.secretCrypto,
|
||||
}
|
||||
got, err := c.UpdateOrgAppleProvider(tt.args.ctx, tt.args.resourceOwner, tt.args.id, tt.args.provider)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func stringPointer(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ const (
|
||||
IDPTypeGitLab
|
||||
IDPTypeGitLabSelfHosted
|
||||
IDPTypeGoogle
|
||||
IDPTypeApple
|
||||
)
|
||||
|
||||
func (t IDPType) GetCSSClass() string {
|
||||
@@ -50,6 +51,8 @@ func (t IDPType) GetCSSClass() string {
|
||||
return "gitlab"
|
||||
case IDPTypeAzureAD:
|
||||
return "azure"
|
||||
case IDPTypeApple:
|
||||
return "apple"
|
||||
case IDPTypeUnspecified,
|
||||
IDPTypeOIDC,
|
||||
IDPTypeJWT,
|
||||
@@ -78,6 +81,8 @@ func (t IDPType) DisplayName() string {
|
||||
return "GitLab"
|
||||
case IDPTypeGoogle:
|
||||
return "Google"
|
||||
case IDPTypeApple:
|
||||
return "Apple"
|
||||
case IDPTypeUnspecified,
|
||||
IDPTypeOIDC,
|
||||
IDPTypeJWT,
|
||||
@@ -94,6 +99,12 @@ func (t IDPType) DisplayName() string {
|
||||
}
|
||||
}
|
||||
|
||||
// IsSignInButton returns if the button should be displayed with a translated
|
||||
// "Sign in with {{.DisplayName}}", e.g. "Sign in with Apple"
|
||||
func (t IDPType) IsSignInButton() bool {
|
||||
return t == IDPTypeApple
|
||||
}
|
||||
|
||||
type IDPIntentState int32
|
||||
|
||||
const (
|
||||
|
||||
@@ -3,9 +3,9 @@ package form
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package apple
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/crypto"
|
||||
openid "github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "Apple"
|
||||
issuer = "https://appleid.apple.com"
|
||||
)
|
||||
|
||||
var _ idp.Provider = (*Provider)(nil)
|
||||
|
||||
// Provider is the [idp.Provider] implementation for Apple
|
||||
type Provider struct {
|
||||
*oidc.Provider
|
||||
}
|
||||
|
||||
func New(clientID, teamID, keyID, callbackURL string, key []byte, scopes []string, options ...oidc.ProviderOpts) (*Provider, error) {
|
||||
secret, err := clientSecretFromPrivateKey(key, teamID, clientID, keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options = append(options, oidc.WithResponseMode("form_post"))
|
||||
rp, err := oidc.New(name, issuer, clientID, secret, callbackURL, scopes, oidc.DefaultMapper, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Provider{
|
||||
Provider: rp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// clientSecretFromPrivateKey uses the private key to create and sign a JWT, which has to be used as client_secret at Apple.
|
||||
func clientSecretFromPrivateKey(key []byte, teamID, clientID, keyID string) (string, error) {
|
||||
block, _ := pem.Decode(key)
|
||||
b := block.Bytes
|
||||
pk, err := x509.ParsePKCS8PrivateKey(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signingKey := jose.SigningKey{
|
||||
Algorithm: jose.ES256,
|
||||
Key: &jose.JSONWebKey{Key: pk, KeyID: keyID},
|
||||
}
|
||||
signer, err := jose.NewSigner(signingKey, &jose.SignerOptions{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
iat := time.Now()
|
||||
exp := iat.Add(time.Hour)
|
||||
return crypto.Sign(&openid.JWTTokenRequest{
|
||||
Issuer: teamID,
|
||||
Subject: clientID,
|
||||
Audience: []string{issuer},
|
||||
ExpiresAt: openid.FromTime(exp),
|
||||
IssuedAt: openid.FromTime(iat),
|
||||
}, signer)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package apple
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
privateKey = `-----BEGIN PRIVATE KEY-----
|
||||
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgXn/LDURaetCoymSj
|
||||
fRslBiBwzBSa8ifiyfYGIWNStYGgCgYIKoZIzj0DAQehRANCAATymZXIsGrXnl6b
|
||||
+80miSiVOCcLnyaYa2uQBQvQwgB7GibXhrzF+D/MRTV4P7P8+Lg1K9Khkjc59eNK
|
||||
4RrQP4g7
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
)
|
||||
|
||||
func TestProvider_BeginAuth(t *testing.T) {
|
||||
type fields struct {
|
||||
clientID string
|
||||
teamID string
|
||||
keyID string
|
||||
privateKey []byte
|
||||
redirectURI string
|
||||
scopes []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want idp.Session
|
||||
}{
|
||||
{
|
||||
name: "successful auth",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
teamID: "teamID",
|
||||
keyID: "keyID",
|
||||
privateKey: []byte(privateKey),
|
||||
redirectURI: "redirectURI",
|
||||
scopes: []string{"openid"},
|
||||
},
|
||||
want: &Session{
|
||||
Session: &oidc.Session{
|
||||
AuthURL: "https://appleid.apple.com/auth/authorize?client_id=clientID&redirect_uri=redirectURI&response_mode=form_post&response_type=code&scope=openid&state=testState",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
r := require.New(t)
|
||||
|
||||
provider, err := New(tt.fields.clientID, tt.fields.teamID, tt.fields.keyID, tt.fields.redirectURI, tt.fields.privateKey, tt.fields.scopes)
|
||||
r.NoError(err)
|
||||
|
||||
session, err := provider.BeginAuth(context.Background(), "testState")
|
||||
r.NoError(err)
|
||||
|
||||
a.Equal(tt.want.GetAuthURL(), session.GetAuthURL())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package apple
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
openid "github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
)
|
||||
|
||||
// Session extends the [oidc.Session] with the formValues returned from the callback.
|
||||
// This enables to parse the user (name and email), which Apple only returns as form params on registration
|
||||
type Session struct {
|
||||
*oidc.Session
|
||||
UserFormValue string
|
||||
}
|
||||
|
||||
type userFormValue struct {
|
||||
Name userNamesFormValue `json:"name,omitempty" schema:"name"`
|
||||
}
|
||||
|
||||
type userNamesFormValue struct {
|
||||
FirstName string `json:"firstName,omitempty" schema:"firstName"`
|
||||
LastName string `json:"lastName,omitempty" schema:"lastName"`
|
||||
}
|
||||
|
||||
// FetchUser implements the [idp.Session] interface.
|
||||
// It will execute an OIDC code exchange if needed to retrieve the tokens,
|
||||
// extract the information from the id_token and if available also from the `user` form value.
|
||||
// The information will be mapped into an [idp.User].
|
||||
func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
if s.Tokens == nil {
|
||||
if err = s.Authorize(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
info := s.Tokens.IDTokenClaims.GetUserInfo()
|
||||
userName := userFormValue{}
|
||||
if s.UserFormValue != "" {
|
||||
if err = json.Unmarshal([]byte(s.UserFormValue), &userName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewUser(info, userName.Name), nil
|
||||
}
|
||||
|
||||
func NewUser(info *openid.UserInfo, names userNamesFormValue) *User {
|
||||
user := oidc.NewUser(info)
|
||||
user.GivenName = names.FirstName
|
||||
user.FamilyName = names.LastName
|
||||
return &User{User: user}
|
||||
}
|
||||
|
||||
// User extends the [oidc.User] by returning the email as preferred_username, since Apple does not return the latter.
|
||||
type User struct {
|
||||
*oidc.User
|
||||
}
|
||||
|
||||
func (u *User) GetPreferredUsername() string {
|
||||
return u.Email
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package apple
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/h2non/gock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
openid "github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
)
|
||||
|
||||
func TestSession_FetchUser(t *testing.T) {
|
||||
type fields struct {
|
||||
clientID string
|
||||
teamID string
|
||||
keyID string
|
||||
privateKey []byte
|
||||
redirectURI string
|
||||
scopes []string
|
||||
httpMock func()
|
||||
authURL string
|
||||
code string
|
||||
tokens *openid.Tokens[*openid.IDTokenClaims]
|
||||
userFormValue string
|
||||
}
|
||||
type want struct {
|
||||
err error
|
||||
id string
|
||||
firstName string
|
||||
lastName string
|
||||
displayName string
|
||||
nickName string
|
||||
preferredUsername string
|
||||
email string
|
||||
isEmailVerified bool
|
||||
phone string
|
||||
isPhoneVerified bool
|
||||
preferredLanguage language.Tag
|
||||
avatarURL string
|
||||
profile string
|
||||
nonceSupported bool
|
||||
isPrivateEmail bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "unauthenticated session, error",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
teamID: "teamID",
|
||||
keyID: "keyID",
|
||||
privateKey: []byte(privateKey),
|
||||
redirectURI: "redirectURI",
|
||||
scopes: []string{"openid"},
|
||||
httpMock: func() {},
|
||||
authURL: "https://appleid.apple.com/auth/authorize?client_id=clientID&redirect_uri=redirectURI&response_mode=form_post&response_type=code&scope=openid&state=testState",
|
||||
tokens: nil,
|
||||
},
|
||||
want: want{
|
||||
err: oidc.ErrCodeMissing,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no user param",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
teamID: "teamID",
|
||||
keyID: "keyID",
|
||||
privateKey: []byte(privateKey),
|
||||
redirectURI: "redirectURI",
|
||||
scopes: []string{"openid"},
|
||||
httpMock: func() {},
|
||||
authURL: "https://appleid.apple.com/auth/authorize?client_id=clientID&redirect_uri=redirectURI&response_mode=form_post&response_type=code&scope=openid&state=testState",
|
||||
tokens: &openid.Tokens[*openid.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: openid.BearerToken,
|
||||
},
|
||||
IDTokenClaims: id_token(),
|
||||
},
|
||||
userFormValue: "",
|
||||
},
|
||||
want: want{
|
||||
id: "sub",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
displayName: "",
|
||||
nickName: "",
|
||||
preferredUsername: "email",
|
||||
email: "email",
|
||||
isEmailVerified: true,
|
||||
phone: "",
|
||||
isPhoneVerified: false,
|
||||
preferredLanguage: language.Und,
|
||||
avatarURL: "",
|
||||
profile: "",
|
||||
nonceSupported: true,
|
||||
isPrivateEmail: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with user param",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
teamID: "teamID",
|
||||
keyID: "keyID",
|
||||
privateKey: []byte(privateKey),
|
||||
redirectURI: "redirectURI",
|
||||
scopes: []string{"openid"},
|
||||
httpMock: func() {},
|
||||
authURL: "https://appleid.apple.com/auth/authorize?client_id=clientID&redirect_uri=redirectURI&response_mode=form_post&response_type=code&scope=openid&state=testState",
|
||||
tokens: &openid.Tokens[*openid.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: openid.BearerToken,
|
||||
},
|
||||
IDTokenClaims: id_token(),
|
||||
},
|
||||
userFormValue: `{"name": {"firstName": "firstName", "lastName": "lastName"}}`,
|
||||
},
|
||||
want: want{
|
||||
id: "sub",
|
||||
firstName: "firstName",
|
||||
lastName: "lastName",
|
||||
displayName: "",
|
||||
nickName: "",
|
||||
preferredUsername: "email",
|
||||
email: "email",
|
||||
isEmailVerified: true,
|
||||
phone: "",
|
||||
isPhoneVerified: false,
|
||||
preferredLanguage: language.Und,
|
||||
avatarURL: "",
|
||||
profile: "",
|
||||
nonceSupported: true,
|
||||
isPrivateEmail: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer gock.Off()
|
||||
tt.fields.httpMock()
|
||||
a := assert.New(t)
|
||||
|
||||
// call the real discovery endpoint
|
||||
gock.New(issuer).Get(openid.DiscoveryEndpoint).EnableNetworking()
|
||||
provider, err := New(tt.fields.clientID, tt.fields.teamID, tt.fields.keyID, tt.fields.redirectURI, tt.fields.privateKey, tt.fields.scopes)
|
||||
require.NoError(t, err)
|
||||
|
||||
session := &Session{
|
||||
Session: &oidc.Session{
|
||||
Provider: provider.Provider,
|
||||
AuthURL: tt.fields.authURL,
|
||||
Code: tt.fields.code,
|
||||
Tokens: tt.fields.tokens,
|
||||
},
|
||||
UserFormValue: tt.fields.userFormValue,
|
||||
}
|
||||
|
||||
user, err := session.FetchUser(context.Background())
|
||||
if tt.want.err != nil && !errors.Is(err, tt.want.err) {
|
||||
a.Fail("invalid error", "expected %v, got %v", tt.want.err, err)
|
||||
}
|
||||
if tt.want.err == nil {
|
||||
a.NoError(err)
|
||||
a.Equal(tt.want.id, user.GetID())
|
||||
a.Equal(tt.want.firstName, user.GetFirstName())
|
||||
a.Equal(tt.want.lastName, user.GetLastName())
|
||||
a.Equal(tt.want.displayName, user.GetDisplayName())
|
||||
a.Equal(tt.want.nickName, user.GetNickname())
|
||||
a.Equal(tt.want.preferredUsername, user.GetPreferredUsername())
|
||||
a.Equal(domain.EmailAddress(tt.want.email), user.GetEmail())
|
||||
a.Equal(tt.want.isEmailVerified, user.IsEmailVerified())
|
||||
a.Equal(domain.PhoneNumber(tt.want.phone), user.GetPhone())
|
||||
a.Equal(tt.want.isPhoneVerified, user.IsPhoneVerified())
|
||||
a.Equal(tt.want.preferredLanguage, user.GetPreferredLanguage())
|
||||
a.Equal(tt.want.avatarURL, user.GetAvatarURL())
|
||||
a.Equal(tt.want.profile, user.GetProfile())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func id_token() *openid.IDTokenClaims {
|
||||
return &openid.IDTokenClaims{
|
||||
TokenClaims: openid.TokenClaims{
|
||||
Issuer: issuer,
|
||||
Subject: "sub",
|
||||
Audience: []string{"clientID"},
|
||||
Expiration: openid.FromTime(time.Now().Add(1 * time.Hour)),
|
||||
IssuedAt: openid.FromTime(time.Now().Add(-1 * time.Second)),
|
||||
AuthTime: openid.FromTime(time.Now().Add(-1 * time.Second)),
|
||||
Nonce: "nonce",
|
||||
ClientID: "clientID",
|
||||
},
|
||||
UserInfoEmail: openid.UserInfoEmail{
|
||||
Email: "email",
|
||||
EmailVerified: true,
|
||||
},
|
||||
Claims: map[string]any{
|
||||
"nonce_supported": true,
|
||||
"is_private_email": true,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,14 @@ func WithSelectAccount() ProviderOpts {
|
||||
}
|
||||
}
|
||||
|
||||
// WithResponseMode sets the `response_mode` params in the auth request
|
||||
func WithResponseMode(mode oidc.ResponseMode) ProviderOpts {
|
||||
return func(p *Provider) {
|
||||
paramOpt := rp.WithResponseModeURLParam(mode)
|
||||
p.authOptions = append(p.authOptions, rp.AuthURLOpt(paramOpt))
|
||||
}
|
||||
}
|
||||
|
||||
type UserInfoMapper func(info *oidc.UserInfo) idp.User
|
||||
|
||||
var DefaultMapper UserInfoMapper = func(info *oidc.UserInfo) idp.User {
|
||||
|
||||
@@ -34,7 +34,7 @@ func (s *Session) GetAuthURL() string {
|
||||
// call the userinfo endpoint and map the received information into an [idp.User].
|
||||
func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
if s.Tokens == nil {
|
||||
if err = s.authorize(ctx); err != nil {
|
||||
if err = s.Authorize(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (s *Session) authorize(ctx context.Context) (err error) {
|
||||
func (s *Session) Authorize(ctx context.Context) (err error) {
|
||||
if s.Code == "" {
|
||||
return ErrCodeMissing
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ type IDPTemplate struct {
|
||||
*GitLabSelfHostedIDPTemplate
|
||||
*GoogleIDPTemplate
|
||||
*LDAPIDPTemplate
|
||||
*AppleIDPTemplate
|
||||
}
|
||||
|
||||
type IDPTemplates struct {
|
||||
@@ -140,6 +141,15 @@ type LDAPIDPTemplate struct {
|
||||
idp.LDAPAttributes
|
||||
}
|
||||
|
||||
type AppleIDPTemplate struct {
|
||||
IDPID string
|
||||
ClientID string
|
||||
TeamID string
|
||||
KeyID string
|
||||
PrivateKey *crypto.CryptoValue
|
||||
Scopes database.StringArray
|
||||
}
|
||||
|
||||
var (
|
||||
idpTemplateTable = table{
|
||||
name: projection.IDPTemplateTable,
|
||||
@@ -605,6 +615,41 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
appleIdpTemplateTable = table{
|
||||
name: projection.IDPTemplateAppleTable,
|
||||
instanceIDCol: projection.AppleInstanceIDCol,
|
||||
}
|
||||
AppleIDCol = Column{
|
||||
name: projection.AppleIDCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
AppleInstanceIDCol = Column{
|
||||
name: projection.AppleInstanceIDCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
AppleClientIDCol = Column{
|
||||
name: projection.AppleClientIDCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
AppleTeamIDCol = Column{
|
||||
name: projection.AppleTeamIDCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
AppleKeyIDCol = Column{
|
||||
name: projection.AppleKeyIDCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
ApplePrivateKeyCol = Column{
|
||||
name: projection.ApplePrivateKeyCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
AppleScopesCol = Column{
|
||||
name: projection.AppleScopesCol,
|
||||
table: appleIdpTemplateTable,
|
||||
}
|
||||
)
|
||||
|
||||
// IDPTemplateByID searches for the requested id
|
||||
func (q *Queries) IDPTemplateByID(ctx context.Context, shouldTriggerBulk bool, id string, withOwnerRemoved bool, queries ...SearchQuery) (template *IDPTemplate, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
@@ -799,6 +844,13 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
LDAPPreferredLanguageAttributeCol.identifier(),
|
||||
LDAPAvatarURLAttributeCol.identifier(),
|
||||
LDAPProfileAttributeCol.identifier(),
|
||||
// apple
|
||||
AppleIDCol.identifier(),
|
||||
AppleClientIDCol.identifier(),
|
||||
AppleTeamIDCol.identifier(),
|
||||
AppleKeyIDCol.identifier(),
|
||||
ApplePrivateKeyCol.identifier(),
|
||||
AppleScopesCol.identifier(),
|
||||
).From(idpTemplateTable.identifier()).
|
||||
LeftJoin(join(OAuthIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(OIDCIDCol, IDPTemplateIDCol)).
|
||||
@@ -809,7 +861,8 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
LeftJoin(join(GitLabIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(GitLabSelfHostedIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(GoogleIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol) + db.Timetravel(call.Took(ctx))).
|
||||
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(AppleIDCol, IDPTemplateIDCol) + db.Timetravel(call.Took(ctx))).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*IDPTemplate, error) {
|
||||
idpTemplate := new(IDPTemplate)
|
||||
@@ -898,6 +951,13 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
ldapAvatarURLAttribute := sql.NullString{}
|
||||
ldapProfileAttribute := sql.NullString{}
|
||||
|
||||
appleID := sql.NullString{}
|
||||
appleClientID := sql.NullString{}
|
||||
appleTeamID := sql.NullString{}
|
||||
appleKeyID := sql.NullString{}
|
||||
applePrivateKey := new(crypto.CryptoValue)
|
||||
appleScopes := database.StringArray{}
|
||||
|
||||
err := row.Scan(
|
||||
&idpTemplate.ID,
|
||||
&idpTemplate.ResourceOwner,
|
||||
@@ -994,6 +1054,13 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
&ldapPreferredLanguageAttribute,
|
||||
&ldapAvatarURLAttribute,
|
||||
&ldapProfileAttribute,
|
||||
// apple
|
||||
&appleID,
|
||||
&appleClientID,
|
||||
&appleTeamID,
|
||||
&appleKeyID,
|
||||
&applePrivateKey,
|
||||
&appleScopes,
|
||||
)
|
||||
if err != nil {
|
||||
if errs.Is(err, sql.ErrNoRows) {
|
||||
@@ -1118,6 +1185,16 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
},
|
||||
}
|
||||
}
|
||||
if appleID.Valid {
|
||||
idpTemplate.AppleIDPTemplate = &AppleIDPTemplate{
|
||||
IDPID: appleID.String,
|
||||
ClientID: appleClientID.String,
|
||||
TeamID: appleTeamID.String,
|
||||
KeyID: appleKeyID.String,
|
||||
PrivateKey: applePrivateKey,
|
||||
Scopes: appleScopes,
|
||||
}
|
||||
}
|
||||
|
||||
return idpTemplate, nil
|
||||
}
|
||||
@@ -1220,6 +1297,14 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
LDAPPreferredLanguageAttributeCol.identifier(),
|
||||
LDAPAvatarURLAttributeCol.identifier(),
|
||||
LDAPProfileAttributeCol.identifier(),
|
||||
// apple
|
||||
AppleIDCol.identifier(),
|
||||
AppleClientIDCol.identifier(),
|
||||
AppleTeamIDCol.identifier(),
|
||||
AppleKeyIDCol.identifier(),
|
||||
ApplePrivateKeyCol.identifier(),
|
||||
AppleScopesCol.identifier(),
|
||||
// count
|
||||
countColumn.identifier(),
|
||||
).From(idpTemplateTable.identifier()).
|
||||
LeftJoin(join(OAuthIDCol, IDPTemplateIDCol)).
|
||||
@@ -1231,7 +1316,8 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
LeftJoin(join(GitLabIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(GitLabSelfHostedIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(GoogleIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol) + db.Timetravel(call.Took(ctx))).
|
||||
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol)).
|
||||
LeftJoin(join(AppleIDCol, IDPTemplateIDCol) + db.Timetravel(call.Took(ctx))).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(rows *sql.Rows) (*IDPTemplates, error) {
|
||||
templates := make([]*IDPTemplate, 0)
|
||||
@@ -1323,6 +1409,13 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
ldapAvatarURLAttribute := sql.NullString{}
|
||||
ldapProfileAttribute := sql.NullString{}
|
||||
|
||||
appleID := sql.NullString{}
|
||||
appleClientID := sql.NullString{}
|
||||
appleTeamID := sql.NullString{}
|
||||
appleKeyID := sql.NullString{}
|
||||
applePrivateKey := new(crypto.CryptoValue)
|
||||
appleScopes := database.StringArray{}
|
||||
|
||||
err := rows.Scan(
|
||||
&idpTemplate.ID,
|
||||
&idpTemplate.ResourceOwner,
|
||||
@@ -1419,6 +1512,13 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
&ldapPreferredLanguageAttribute,
|
||||
&ldapAvatarURLAttribute,
|
||||
&ldapProfileAttribute,
|
||||
// apple
|
||||
&appleID,
|
||||
&appleClientID,
|
||||
&appleTeamID,
|
||||
&appleKeyID,
|
||||
&applePrivateKey,
|
||||
&appleScopes,
|
||||
&count,
|
||||
)
|
||||
|
||||
@@ -1542,6 +1642,16 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
},
|
||||
}
|
||||
}
|
||||
if appleID.Valid {
|
||||
idpTemplate.AppleIDPTemplate = &AppleIDPTemplate{
|
||||
IDPID: appleID.String,
|
||||
ClientID: appleClientID.String,
|
||||
TeamID: appleTeamID.String,
|
||||
KeyID: appleKeyID.String,
|
||||
PrivateKey: applePrivateKey,
|
||||
Scopes: appleScopes,
|
||||
}
|
||||
}
|
||||
templates = append(templates, idpTemplate)
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,14 @@ var (
|
||||
` projections.idp_templates5_ldap2.phone_verified_attribute,` +
|
||||
` projections.idp_templates5_ldap2.preferred_language_attribute,` +
|
||||
` projections.idp_templates5_ldap2.avatar_url_attribute,` +
|
||||
` projections.idp_templates5_ldap2.profile_attribute` +
|
||||
` projections.idp_templates5_ldap2.profile_attribute,` +
|
||||
// apple
|
||||
` projections.idp_templates5_apple.idp_id,` +
|
||||
` projections.idp_templates5_apple.client_id,` +
|
||||
` projections.idp_templates5_apple.team_id,` +
|
||||
` projections.idp_templates5_apple.key_id,` +
|
||||
` projections.idp_templates5_apple.private_key,` +
|
||||
` projections.idp_templates5_apple.scopes` +
|
||||
` FROM projections.idp_templates5` +
|
||||
` LEFT JOIN projections.idp_templates5_oauth2 ON projections.idp_templates5.id = projections.idp_templates5_oauth2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oauth2.instance_id` +
|
||||
` LEFT JOIN projections.idp_templates5_oidc ON projections.idp_templates5.id = projections.idp_templates5_oidc.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oidc.instance_id` +
|
||||
@@ -122,6 +129,7 @@ var (
|
||||
` LEFT JOIN projections.idp_templates5_gitlab_self_hosted ON projections.idp_templates5.id = projections.idp_templates5_gitlab_self_hosted.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_gitlab_self_hosted.instance_id` +
|
||||
` LEFT JOIN projections.idp_templates5_google ON projections.idp_templates5.id = projections.idp_templates5_google.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_google.instance_id` +
|
||||
` LEFT JOIN projections.idp_templates5_ldap2 ON projections.idp_templates5.id = projections.idp_templates5_ldap2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_ldap2.instance_id` +
|
||||
` LEFT JOIN projections.idp_templates5_apple ON projections.idp_templates5.id = projections.idp_templates5_apple.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_apple.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
idpTemplateCols = []string{
|
||||
"id",
|
||||
@@ -219,6 +227,13 @@ var (
|
||||
"preferred_language_attribute",
|
||||
"avatar_url_attribute",
|
||||
"profile_attribute",
|
||||
// apple config
|
||||
"idp_id",
|
||||
"client_id",
|
||||
"team_id",
|
||||
"key_id",
|
||||
"private_key",
|
||||
"scopes",
|
||||
}
|
||||
idpTemplatesQuery = `SELECT projections.idp_templates5.id,` +
|
||||
` projections.idp_templates5.resource_owner,` +
|
||||
@@ -315,6 +330,13 @@ var (
|
||||
` projections.idp_templates5_ldap2.preferred_language_attribute,` +
|
||||
` projections.idp_templates5_ldap2.avatar_url_attribute,` +
|
||||
` projections.idp_templates5_ldap2.profile_attribute,` +
|
||||
// apple
|
||||
` projections.idp_templates5_apple.idp_id,` +
|
||||
` projections.idp_templates5_apple.client_id,` +
|
||||
` projections.idp_templates5_apple.team_id,` +
|
||||
` projections.idp_templates5_apple.key_id,` +
|
||||
` projections.idp_templates5_apple.private_key,` +
|
||||
` projections.idp_templates5_apple.scopes,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.idp_templates5` +
|
||||
` LEFT JOIN projections.idp_templates5_oauth2 ON projections.idp_templates5.id = projections.idp_templates5_oauth2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_oauth2.instance_id` +
|
||||
@@ -327,6 +349,7 @@ var (
|
||||
` LEFT JOIN projections.idp_templates5_gitlab_self_hosted ON projections.idp_templates5.id = projections.idp_templates5_gitlab_self_hosted.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_gitlab_self_hosted.instance_id` +
|
||||
` LEFT JOIN projections.idp_templates5_google ON projections.idp_templates5.id = projections.idp_templates5_google.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_google.instance_id` +
|
||||
` LEFT JOIN projections.idp_templates5_ldap2 ON projections.idp_templates5.id = projections.idp_templates5_ldap2.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_ldap2.instance_id` +
|
||||
` LEFT JOIN projections.idp_templates5_apple ON projections.idp_templates5.id = projections.idp_templates5_apple.idp_id AND projections.idp_templates5.instance_id = projections.idp_templates5_apple.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
idpTemplatesCols = []string{
|
||||
"id",
|
||||
@@ -424,6 +447,13 @@ var (
|
||||
"preferred_language_attribute",
|
||||
"avatar_url_attribute",
|
||||
"profile_attribute",
|
||||
// apple config
|
||||
"idp_id",
|
||||
"client_id",
|
||||
"team_id",
|
||||
"key_id",
|
||||
"private_key",
|
||||
"scopes",
|
||||
"count",
|
||||
}
|
||||
)
|
||||
@@ -560,6 +590,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -692,6 +729,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -822,6 +866,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -951,6 +1002,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -1079,6 +1137,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -1207,6 +1272,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -1336,6 +1408,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -1464,6 +1543,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
"lang",
|
||||
"avatar",
|
||||
"profile",
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -1509,6 +1595,143 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareIDPTemplateByIDQuery apple idp",
|
||||
prepare: prepareIDPTemplateByIDQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQuery(
|
||||
regexp.QuoteMeta(idpTemplateQuery),
|
||||
idpTemplateCols,
|
||||
[]driver.Value{
|
||||
"idp-id",
|
||||
"ro",
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211109),
|
||||
domain.IDPConfigStateActive,
|
||||
"idp-name",
|
||||
domain.IDPTypeApple,
|
||||
domain.IdentityProviderTypeOrg,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
// oauth
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// oidc
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// jwt
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// azure
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// github
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// github enterprise
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// gitlab
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// gitlab self hosted
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// google
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
"idp-id",
|
||||
"client_id",
|
||||
"team_id",
|
||||
"key_id",
|
||||
nil,
|
||||
database.StringArray{"profile"},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &IDPTemplate{
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211109,
|
||||
ResourceOwner: "ro",
|
||||
ID: "idp-id",
|
||||
State: domain.IDPStateActive,
|
||||
Name: "idp-name",
|
||||
Type: domain.IDPTypeApple,
|
||||
OwnerType: domain.IdentityProviderTypeOrg,
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
AppleIDPTemplate: &AppleIDPTemplate{
|
||||
IDPID: "idp-id",
|
||||
ClientID: "client_id",
|
||||
TeamID: "team_id",
|
||||
KeyID: "key_id",
|
||||
PrivateKey: nil,
|
||||
Scopes: []string{"profile"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareIDPTemplateByIDQuery no config",
|
||||
prepare: prepareIDPTemplateByIDQuery,
|
||||
@@ -1612,6 +1835,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -1770,6 +2000,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
"lang",
|
||||
"avatar",
|
||||
"profile",
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -1927,6 +2164,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -2058,6 +2302,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
"lang",
|
||||
"avatar",
|
||||
"profile",
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"idp-id-google",
|
||||
@@ -2155,6 +2406,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"idp-id-oauth",
|
||||
@@ -2252,6 +2510,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"idp-id-oidc",
|
||||
@@ -2349,6 +2614,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"idp-id-jwt",
|
||||
@@ -2446,6 +2718,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// apple
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
|
||||
@@ -28,6 +28,7 @@ const (
|
||||
IDPTemplateGitLabSelfHostedTable = IDPTemplateTable + "_" + IDPTemplateGitLabSelfHostedSuffix
|
||||
IDPTemplateGoogleTable = IDPTemplateTable + "_" + IDPTemplateGoogleSuffix
|
||||
IDPTemplateLDAPTable = IDPTemplateTable + "_" + IDPTemplateLDAPSuffix
|
||||
IDPTemplateAppleTable = IDPTemplateTable + "_" + IDPTemplateAppleSuffix
|
||||
|
||||
IDPTemplateOAuthSuffix = "oauth2"
|
||||
IDPTemplateOIDCSuffix = "oidc"
|
||||
@@ -39,6 +40,7 @@ const (
|
||||
IDPTemplateGitLabSelfHostedSuffix = "gitlab_self_hosted"
|
||||
IDPTemplateGoogleSuffix = "google"
|
||||
IDPTemplateLDAPSuffix = "ldap2"
|
||||
IDPTemplateAppleSuffix = "apple"
|
||||
|
||||
IDPTemplateIDCol = "id"
|
||||
IDPTemplateCreationDateCol = "creation_date"
|
||||
@@ -147,6 +149,14 @@ const (
|
||||
LDAPPreferredLanguageAttributeCol = "preferred_language_attribute"
|
||||
LDAPAvatarURLAttributeCol = "avatar_url_attribute"
|
||||
LDAPProfileAttributeCol = "profile_attribute"
|
||||
|
||||
AppleIDCol = "idp_id"
|
||||
AppleInstanceIDCol = "instance_id"
|
||||
AppleClientIDCol = "client_id"
|
||||
AppleTeamIDCol = "team_id"
|
||||
AppleKeyIDCol = "key_id"
|
||||
ApplePrivateKeyCol = "private_key"
|
||||
AppleScopesCol = "scopes"
|
||||
)
|
||||
|
||||
type idpTemplateProjection struct {
|
||||
@@ -321,6 +331,19 @@ func newIDPTemplateProjection(ctx context.Context, config crdb.StatementHandlerC
|
||||
IDPTemplateLDAPSuffix,
|
||||
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()),
|
||||
),
|
||||
crdb.NewSuffixedTable([]*crdb.Column{
|
||||
crdb.NewColumn(AppleIDCol, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(AppleInstanceIDCol, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(AppleClientIDCol, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(AppleTeamIDCol, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(AppleKeyIDCol, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(ApplePrivateKeyCol, crdb.ColumnTypeJSONB),
|
||||
crdb.NewColumn(AppleScopesCol, crdb.ColumnTypeTextArray, crdb.Nullable()),
|
||||
},
|
||||
crdb.NewPrimaryKey(AppleInstanceIDCol, AppleIDCol),
|
||||
IDPTemplateAppleSuffix,
|
||||
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()),
|
||||
),
|
||||
)
|
||||
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
||||
return p
|
||||
@@ -443,6 +466,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
|
||||
Event: instance.LDAPIDPChangedEventType,
|
||||
Reduce: p.reduceLDAPIDPChanged,
|
||||
},
|
||||
{
|
||||
Event: instance.AppleIDPAddedEventType,
|
||||
Reduce: p.reduceAppleIDPAdded,
|
||||
},
|
||||
{
|
||||
Event: instance.AppleIDPChangedEventType,
|
||||
Reduce: p.reduceAppleIDPChanged,
|
||||
},
|
||||
{
|
||||
Event: instance.IDPConfigRemovedEventType,
|
||||
Reduce: p.reduceIDPConfigRemoved,
|
||||
@@ -572,6 +603,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
|
||||
Event: org.LDAPIDPChangedEventType,
|
||||
Reduce: p.reduceLDAPIDPChanged,
|
||||
},
|
||||
{
|
||||
Event: org.AppleIDPAddedEventType,
|
||||
Reduce: p.reduceAppleIDPAdded,
|
||||
},
|
||||
{
|
||||
Event: org.AppleIDPChangedEventType,
|
||||
Reduce: p.reduceAppleIDPChanged,
|
||||
},
|
||||
{
|
||||
Event: org.IDPConfigRemovedEventType,
|
||||
Reduce: p.reduceIDPConfigRemoved,
|
||||
@@ -1858,6 +1897,97 @@ func (p *idpTemplateProjection) reduceLDAPIDPChanged(event eventstore.Event) (*h
|
||||
ops...,
|
||||
), nil
|
||||
}
|
||||
|
||||
func (p *idpTemplateProjection) reduceAppleIDPAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
var idpEvent idp.AppleIDPAddedEvent
|
||||
var idpOwnerType domain.IdentityProviderType
|
||||
switch e := event.(type) {
|
||||
case *org.AppleIDPAddedEvent:
|
||||
idpEvent = e.AppleIDPAddedEvent
|
||||
idpOwnerType = domain.IdentityProviderTypeOrg
|
||||
case *instance.AppleIDPAddedEvent:
|
||||
idpEvent = e.AppleIDPAddedEvent
|
||||
idpOwnerType = domain.IdentityProviderTypeSystem
|
||||
default:
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-SFvg3", "reduce.wrong.event.type %v", []eventstore.EventType{org.AppleIDPAddedEventType /*, instance.AppleIDPAddedEventType*/})
|
||||
}
|
||||
|
||||
return crdb.NewMultiStatement(
|
||||
&idpEvent,
|
||||
crdb.AddCreateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(IDPTemplateIDCol, idpEvent.ID),
|
||||
handler.NewCol(IDPTemplateCreationDateCol, idpEvent.CreationDate()),
|
||||
handler.NewCol(IDPTemplateChangeDateCol, idpEvent.CreationDate()),
|
||||
handler.NewCol(IDPTemplateSequenceCol, idpEvent.Sequence()),
|
||||
handler.NewCol(IDPTemplateResourceOwnerCol, idpEvent.Aggregate().ResourceOwner),
|
||||
handler.NewCol(IDPTemplateInstanceIDCol, idpEvent.Aggregate().InstanceID),
|
||||
handler.NewCol(IDPTemplateStateCol, domain.IDPStateActive),
|
||||
handler.NewCol(IDPTemplateNameCol, idpEvent.Name),
|
||||
handler.NewCol(IDPTemplateOwnerTypeCol, idpOwnerType),
|
||||
handler.NewCol(IDPTemplateTypeCol, domain.IDPTypeApple),
|
||||
handler.NewCol(IDPTemplateIsCreationAllowedCol, idpEvent.IsCreationAllowed),
|
||||
handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed),
|
||||
handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation),
|
||||
handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate),
|
||||
},
|
||||
),
|
||||
crdb.AddCreateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(AppleIDCol, idpEvent.ID),
|
||||
handler.NewCol(AppleInstanceIDCol, idpEvent.Aggregate().InstanceID),
|
||||
handler.NewCol(AppleClientIDCol, idpEvent.ClientID),
|
||||
handler.NewCol(AppleTeamIDCol, idpEvent.TeamID),
|
||||
handler.NewCol(AppleKeyIDCol, idpEvent.KeyID),
|
||||
handler.NewCol(ApplePrivateKeyCol, idpEvent.PrivateKey),
|
||||
handler.NewCol(AppleScopesCol, database.StringArray(idpEvent.Scopes)),
|
||||
},
|
||||
crdb.WithTableSuffix(IDPTemplateAppleSuffix),
|
||||
),
|
||||
), nil
|
||||
}
|
||||
|
||||
func (p *idpTemplateProjection) reduceAppleIDPChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||
var idpEvent idp.AppleIDPChangedEvent
|
||||
switch e := event.(type) {
|
||||
case *org.AppleIDPChangedEvent:
|
||||
idpEvent = e.AppleIDPChangedEvent
|
||||
case *instance.AppleIDPChangedEvent:
|
||||
idpEvent = e.AppleIDPChangedEvent
|
||||
default:
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-GBez3", "reduce.wrong.event.type %v", []eventstore.EventType{org.AppleIDPChangedEventType /*, instance.AppleIDPChangedEventType*/})
|
||||
}
|
||||
|
||||
ops := make([]func(eventstore.Event) crdb.Exec, 0, 2)
|
||||
ops = append(ops,
|
||||
crdb.AddUpdateStatement(
|
||||
reduceIDPChangedTemplateColumns(idpEvent.Name, idpEvent.CreationDate(), idpEvent.Sequence(), idpEvent.OptionChanges),
|
||||
[]handler.Condition{
|
||||
handler.NewCond(IDPTemplateIDCol, idpEvent.ID),
|
||||
handler.NewCond(IDPTemplateInstanceIDCol, idpEvent.Aggregate().InstanceID),
|
||||
},
|
||||
),
|
||||
)
|
||||
appleCols := reduceAppleIDPChangedColumns(idpEvent)
|
||||
if len(appleCols) > 0 {
|
||||
ops = append(ops,
|
||||
crdb.AddUpdateStatement(
|
||||
appleCols,
|
||||
[]handler.Condition{
|
||||
handler.NewCond(AppleIDCol, idpEvent.ID),
|
||||
handler.NewCond(AppleInstanceIDCol, idpEvent.Aggregate().InstanceID),
|
||||
},
|
||||
crdb.WithTableSuffix(IDPTemplateAppleSuffix),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return crdb.NewMultiStatement(
|
||||
&idpEvent,
|
||||
ops...,
|
||||
), nil
|
||||
}
|
||||
|
||||
func (p *idpTemplateProjection) reduceIDPConfigRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
var idpEvent idpconfig.IDPConfigRemovedEvent
|
||||
switch e := event.(type) {
|
||||
@@ -2176,3 +2306,23 @@ func reduceLDAPIDPChangedColumns(idpEvent idp.LDAPIDPChangedEvent) []handler.Col
|
||||
}
|
||||
return ldapCols
|
||||
}
|
||||
|
||||
func reduceAppleIDPChangedColumns(idpEvent idp.AppleIDPChangedEvent) []handler.Column {
|
||||
appleCols := make([]handler.Column, 0, 5)
|
||||
if idpEvent.ClientID != nil {
|
||||
appleCols = append(appleCols, handler.NewCol(AppleClientIDCol, *idpEvent.ClientID))
|
||||
}
|
||||
if idpEvent.TeamID != nil {
|
||||
appleCols = append(appleCols, handler.NewCol(AppleTeamIDCol, *idpEvent.TeamID))
|
||||
}
|
||||
if idpEvent.KeyID != nil {
|
||||
appleCols = append(appleCols, handler.NewCol(AppleKeyIDCol, *idpEvent.KeyID))
|
||||
}
|
||||
if idpEvent.PrivateKey != nil {
|
||||
appleCols = append(appleCols, handler.NewCol(ApplePrivateKeyCol, *idpEvent.PrivateKey))
|
||||
}
|
||||
if idpEvent.Scopes != nil {
|
||||
appleCols = append(appleCols, handler.NewCol(AppleScopesCol, database.StringArray(idpEvent.Scopes)))
|
||||
}
|
||||
return appleCols
|
||||
}
|
||||
|
||||
@@ -2440,6 +2440,268 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDPTemplateProjection_reducesApple(t *testing.T) {
|
||||
type args struct {
|
||||
event func(t *testing.T) eventstore.Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
reduce func(event eventstore.Event) (*handler.Statement, error)
|
||||
want wantReduce
|
||||
}{
|
||||
{
|
||||
name: "instance reduceAppleIDPAdded",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(instance.AppleIDPAddedEventType),
|
||||
instance.AggregateType,
|
||||
[]byte(`{
|
||||
"id": "idp-id",
|
||||
"clientId": "client_id",
|
||||
"teamId": "team_id",
|
||||
"keyId": "key_id",
|
||||
"privateKey": {
|
||||
"cryptoType": 0,
|
||||
"algorithm": "RSA-265",
|
||||
"keyId": "key-id"
|
||||
},
|
||||
"scopes": ["name"],
|
||||
"isCreationAllowed": true,
|
||||
"isLinkingAllowed": true,
|
||||
"isAutoCreation": true,
|
||||
"isAutoUpdate": true
|
||||
}`),
|
||||
), instance.AppleIDPAddedEventMapper),
|
||||
},
|
||||
reduce: (&idpTemplateProjection{}).reduceAppleIDPAdded,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("instance"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: idpTemplateInsertStmt,
|
||||
expectedArgs: []interface{}{
|
||||
"idp-id",
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"ro-id",
|
||||
"instance-id",
|
||||
domain.IDPStateActive,
|
||||
"",
|
||||
domain.IdentityProviderTypeSystem,
|
||||
domain.IDPTypeApple,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.idp_templates5_apple (idp_id, instance_id, client_id, team_id, key_id, private_key, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
expectedArgs: []interface{}{
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
"client_id",
|
||||
"team_id",
|
||||
"key_id",
|
||||
anyArg{},
|
||||
database.StringArray{"name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org reduceAppleIDPAdded",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(org.AppleIDPAddedEventType),
|
||||
org.AggregateType,
|
||||
[]byte(`{
|
||||
"id": "idp-id",
|
||||
"clientId": "client_id",
|
||||
"teamId": "team_id",
|
||||
"keyId": "key_id",
|
||||
"privateKey": {
|
||||
"cryptoType": 0,
|
||||
"algorithm": "RSA-265",
|
||||
"keyId": "key-id"
|
||||
},
|
||||
"scopes": ["name"],
|
||||
"isCreationAllowed": true,
|
||||
"isLinkingAllowed": true,
|
||||
"isAutoCreation": true,
|
||||
"isAutoUpdate": true
|
||||
}`),
|
||||
), org.AppleIDPAddedEventMapper),
|
||||
},
|
||||
reduce: (&idpTemplateProjection{}).reduceAppleIDPAdded,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("org"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: idpTemplateInsertStmt,
|
||||
expectedArgs: []interface{}{
|
||||
"idp-id",
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"ro-id",
|
||||
"instance-id",
|
||||
domain.IDPStateActive,
|
||||
"",
|
||||
domain.IdentityProviderTypeOrg,
|
||||
domain.IDPTypeApple,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.idp_templates5_apple (idp_id, instance_id, client_id, team_id, key_id, private_key, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
expectedArgs: []interface{}{
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
"client_id",
|
||||
"team_id",
|
||||
"key_id",
|
||||
anyArg{},
|
||||
database.StringArray{"name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instance reduceAppleIDPChanged minimal",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(instance.AppleIDPChangedEventType),
|
||||
instance.AggregateType,
|
||||
[]byte(`{
|
||||
"id": "idp-id",
|
||||
"isCreationAllowed": true,
|
||||
"clientId": "id"
|
||||
}`),
|
||||
), instance.AppleIDPChangedEventMapper),
|
||||
},
|
||||
reduce: (&idpTemplateProjection{}).reduceAppleIDPChanged,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("instance"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: idpTemplateUpdateMinimalStmt,
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.idp_templates5_apple SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
"id",
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instance reduceAppleIDPChanged",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(instance.AppleIDPChangedEventType),
|
||||
instance.AggregateType,
|
||||
[]byte(`{
|
||||
"id": "idp-id",
|
||||
"name": "name",
|
||||
"clientId": "client_id",
|
||||
"teamId": "team_id",
|
||||
"keyId": "key_id",
|
||||
"privateKey": {
|
||||
"cryptoType": 0,
|
||||
"algorithm": "RSA-265",
|
||||
"keyId": "key-id"
|
||||
},
|
||||
"scopes": ["name"],
|
||||
"isCreationAllowed": true,
|
||||
"isLinkingAllowed": true,
|
||||
"isAutoCreation": true,
|
||||
"isAutoUpdate": true
|
||||
}`),
|
||||
), instance.AppleIDPChangedEventMapper),
|
||||
},
|
||||
reduce: (&idpTemplateProjection{}).reduceAppleIDPChanged,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("instance"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: idpTemplateUpdateStmt,
|
||||
expectedArgs: []interface{}{
|
||||
"name",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.idp_templates5_apple SET (client_id, team_id, key_id, private_key, scopes) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)",
|
||||
expectedArgs: []interface{}{
|
||||
"client_id",
|
||||
"team_id",
|
||||
"key_id",
|
||||
anyArg{},
|
||||
database.StringArray{"name"},
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
event := baseEvent(t)
|
||||
got, err := tt.reduce(event)
|
||||
if !errors.IsErrorInvalidArgument(err) {
|
||||
t.Errorf("no wrong event mapping: %v, got: %v", err, got)
|
||||
}
|
||||
|
||||
event = tt.args.event(t)
|
||||
got, err = tt.reduce(event)
|
||||
assertReduce(t, got, err, IDPTemplateTable, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDPTemplateProjection_reducesOIDC(t *testing.T) {
|
||||
type args struct {
|
||||
event func(t *testing.T) eventstore.Event
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
package idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
)
|
||||
|
||||
type AppleIDPAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ClientID string `json:"clientId"`
|
||||
TeamID string `json:"teamId"`
|
||||
KeyID string `json:"keyId"`
|
||||
PrivateKey *crypto.CryptoValue `json:"privateKey"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
Options
|
||||
}
|
||||
|
||||
func NewAppleIDPAddedEvent(
|
||||
base *eventstore.BaseEvent,
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID string,
|
||||
privateKey *crypto.CryptoValue,
|
||||
scopes []string,
|
||||
options Options,
|
||||
) *AppleIDPAddedEvent {
|
||||
return &AppleIDPAddedEvent{
|
||||
BaseEvent: *base,
|
||||
ID: id,
|
||||
Name: name,
|
||||
ClientID: clientID,
|
||||
TeamID: teamID,
|
||||
KeyID: keyID,
|
||||
PrivateKey: privateKey,
|
||||
Scopes: scopes,
|
||||
Options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *AppleIDPAddedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *AppleIDPAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func AppleIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e := &AppleIDPAddedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "IDP-Beqss", "unable to unmarshal event")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type AppleIDPChangedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
ID string `json:"id"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
ClientID *string `json:"clientId,omitempty"`
|
||||
TeamID *string `json:"teamId,omitempty"`
|
||||
KeyID *string `json:"keyId,omitempty"`
|
||||
PrivateKey *crypto.CryptoValue `json:"privateKey,omitempty"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
OptionChanges
|
||||
}
|
||||
|
||||
func NewAppleIDPChangedEvent(
|
||||
base *eventstore.BaseEvent,
|
||||
id string,
|
||||
changes []AppleIDPChanges,
|
||||
) (*AppleIDPChangedEvent, error) {
|
||||
if len(changes) == 0 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "IDP-SF3h2", "Errors.NoChangesFound")
|
||||
}
|
||||
changedEvent := &AppleIDPChangedEvent{
|
||||
BaseEvent: *base,
|
||||
ID: id,
|
||||
}
|
||||
for _, change := range changes {
|
||||
change(changedEvent)
|
||||
}
|
||||
return changedEvent, nil
|
||||
}
|
||||
|
||||
type AppleIDPChanges func(*AppleIDPChangedEvent)
|
||||
|
||||
func ChangeAppleName(name string) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.Name = &name
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeAppleClientID(clientID string) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.ClientID = &clientID
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeAppleTeamID(teamID string) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.TeamID = &teamID
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeAppleKeyID(keyID string) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.KeyID = &keyID
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeApplePrivateKey(privateKey *crypto.CryptoValue) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.PrivateKey = privateKey
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeAppleScopes(scopes []string) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.Scopes = scopes
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeAppleOptions(options OptionChanges) func(*AppleIDPChangedEvent) {
|
||||
return func(e *AppleIDPChangedEvent) {
|
||||
e.OptionChanges = options
|
||||
}
|
||||
}
|
||||
|
||||
func (e *AppleIDPChangedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *AppleIDPChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func AppleIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e := &AppleIDPChangedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "IDP-NBe1s", "unable to unmarshal event")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
@@ -92,6 +92,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(AggregateType, GoogleIDPChangedEventType, GoogleIDPChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LDAPIDPAddedEventType, LDAPIDPAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LDAPIDPChangedEventType, LDAPIDPChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, AppleIDPAddedEventType, AppleIDPAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, AppleIDPChangedEventType, AppleIDPChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, IDPRemovedEventType, IDPRemovedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LoginPolicyIDPProviderAddedEventType, IdentityProviderAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LoginPolicyIDPProviderRemovedEventType, IdentityProviderRemovedEventMapper).
|
||||
|
||||
@@ -33,6 +33,8 @@ const (
|
||||
GoogleIDPChangedEventType eventstore.EventType = "instance.idp.google.changed"
|
||||
LDAPIDPAddedEventType eventstore.EventType = "instance.idp.ldap.v2.added"
|
||||
LDAPIDPChangedEventType eventstore.EventType = "instance.idp.ldap.v2.changed"
|
||||
AppleIDPAddedEventType eventstore.EventType = "instance.idp.apple.added"
|
||||
AppleIDPChangedEventType eventstore.EventType = "instance.idp.apple.changed"
|
||||
IDPRemovedEventType eventstore.EventType = "instance.idp.removed"
|
||||
)
|
||||
|
||||
@@ -920,6 +922,86 @@ func LDAPIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error
|
||||
return &LDAPIDPChangedEvent{LDAPIDPChangedEvent: *e.(*idp.LDAPIDPChangedEvent)}, nil
|
||||
}
|
||||
|
||||
type AppleIDPAddedEvent struct {
|
||||
idp.AppleIDPAddedEvent
|
||||
}
|
||||
|
||||
func NewAppleIDPAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID string,
|
||||
privateKey *crypto.CryptoValue,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) *AppleIDPAddedEvent {
|
||||
|
||||
return &AppleIDPAddedEvent{
|
||||
AppleIDPAddedEvent: *idp.NewAppleIDPAddedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
AppleIDPAddedEventType,
|
||||
),
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID,
|
||||
privateKey,
|
||||
scopes,
|
||||
options,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func AppleIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := idp.AppleIDPAddedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AppleIDPAddedEvent{AppleIDPAddedEvent: *e.(*idp.AppleIDPAddedEvent)}, nil
|
||||
}
|
||||
|
||||
type AppleIDPChangedEvent struct {
|
||||
idp.AppleIDPChangedEvent
|
||||
}
|
||||
|
||||
func NewAppleIDPChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id string,
|
||||
changes []idp.AppleIDPChanges,
|
||||
) (*AppleIDPChangedEvent, error) {
|
||||
|
||||
changedEvent, err := idp.NewAppleIDPChangedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
AppleIDPChangedEventType,
|
||||
),
|
||||
id,
|
||||
changes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AppleIDPChangedEvent{AppleIDPChangedEvent: *changedEvent}, nil
|
||||
}
|
||||
|
||||
func AppleIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := idp.AppleIDPChangedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AppleIDPChangedEvent{AppleIDPChangedEvent: *e.(*idp.AppleIDPChangedEvent)}, nil
|
||||
}
|
||||
|
||||
type IDPRemovedEvent struct {
|
||||
idp.RemovedEvent
|
||||
}
|
||||
|
||||
@@ -101,6 +101,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(AggregateType, GoogleIDPChangedEventType, GoogleIDPChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LDAPIDPAddedEventType, LDAPIDPAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LDAPIDPChangedEventType, LDAPIDPChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, AppleIDPAddedEventType, AppleIDPAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, AppleIDPChangedEventType, AppleIDPChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, IDPRemovedEventType, IDPRemovedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, TriggerActionsSetEventType, TriggerActionsSetEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, TriggerActionsCascadeRemovedEventType, TriggerActionsCascadeRemovedEventMapper).
|
||||
|
||||
@@ -33,6 +33,8 @@ const (
|
||||
GoogleIDPChangedEventType eventstore.EventType = "org.idp.google.changed"
|
||||
LDAPIDPAddedEventType eventstore.EventType = "org.idp.ldap.added"
|
||||
LDAPIDPChangedEventType eventstore.EventType = "org.idp.ldap.changed"
|
||||
AppleIDPAddedEventType eventstore.EventType = "org.idp.apple.added"
|
||||
AppleIDPChangedEventType eventstore.EventType = "org.idp.apple.changed"
|
||||
IDPRemovedEventType eventstore.EventType = "org.idp.removed"
|
||||
)
|
||||
|
||||
@@ -920,6 +922,86 @@ func LDAPIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error
|
||||
return &LDAPIDPChangedEvent{LDAPIDPChangedEvent: *e.(*idp.LDAPIDPChangedEvent)}, nil
|
||||
}
|
||||
|
||||
type AppleIDPAddedEvent struct {
|
||||
idp.AppleIDPAddedEvent
|
||||
}
|
||||
|
||||
func NewAppleIDPAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID string,
|
||||
privateKey *crypto.CryptoValue,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) *AppleIDPAddedEvent {
|
||||
|
||||
return &AppleIDPAddedEvent{
|
||||
AppleIDPAddedEvent: *idp.NewAppleIDPAddedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
AppleIDPAddedEventType,
|
||||
),
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID,
|
||||
privateKey,
|
||||
scopes,
|
||||
options,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func AppleIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := idp.AppleIDPAddedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AppleIDPAddedEvent{AppleIDPAddedEvent: *e.(*idp.AppleIDPAddedEvent)}, nil
|
||||
}
|
||||
|
||||
type AppleIDPChangedEvent struct {
|
||||
idp.AppleIDPChangedEvent
|
||||
}
|
||||
|
||||
func NewAppleIDPChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id string,
|
||||
changes []idp.AppleIDPChanges,
|
||||
) (*AppleIDPChangedEvent, error) {
|
||||
|
||||
changedEvent, err := idp.NewAppleIDPChangedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
AppleIDPChangedEventType,
|
||||
),
|
||||
id,
|
||||
changes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AppleIDPChangedEvent{AppleIDPChangedEvent: *changedEvent}, nil
|
||||
}
|
||||
|
||||
func AppleIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := idp.AppleIDPChangedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AppleIDPChangedEvent{AppleIDPChangedEvent: *e.(*idp.AppleIDPChangedEvent)}, nil
|
||||
}
|
||||
|
||||
type IDPRemovedEvent struct {
|
||||
idp.RemovedEvent
|
||||
}
|
||||
|
||||
@@ -201,6 +201,10 @@ Errors:
|
||||
InvalidCharacter: 'Само буквено-цифрови знаци, . '
|
||||
IDP:
|
||||
InvalidSearchQuery: Невалидна заявка за търсене
|
||||
ClientIDMissing: Липсва ClientID
|
||||
TeamIDMissing: TeamID липсва
|
||||
KeyIDMissing: Липсва KeyID
|
||||
PrivateKeyMissing: Липсва частен ключ
|
||||
LoginPolicy:
|
||||
NotFound: Правилата за влизане не са намерени
|
||||
Invalid: Правилата за влизане са невалидни
|
||||
|
||||
@@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: Nur alphanumerische Zeichen, . und - sind für eine Domäne erlaubt
|
||||
IDP:
|
||||
InvalidSearchQuery: Ungültiger Suchparameter
|
||||
ClientIDMissing: ClientID fehlt
|
||||
TeamIDMissing: TeamID fehlt
|
||||
KeyIDMissing: KeyID fehlt
|
||||
PrivateKeyMissing: Private Key fehlt
|
||||
LoginPolicy:
|
||||
NotFound: Login Policy konnte nicht gefunden werden
|
||||
Invalid: Login Policy ist ungültig
|
||||
|
||||
@@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: Only alphanumeric characters, . and - are allowed for a domain
|
||||
IDP:
|
||||
InvalidSearchQuery: Invalid search query
|
||||
ClientIDMissing: ClientID missing
|
||||
TeamIDMissing: TeamID missing
|
||||
KeyIDMissing: KeyID missing
|
||||
PrivateKeyMissing: Private Key missing
|
||||
LoginPolicy:
|
||||
NotFound: Login Policy not found
|
||||
Invalid: Login Policy is invalid
|
||||
|
||||
@@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: Solo caracteres alfanuméricos, . y - se permiten para un dominio
|
||||
IDP:
|
||||
InvalidSearchQuery: Consulta de búsqueda no válida
|
||||
ClientIDMissing: Falta ClientID
|
||||
TeamIDMissing: Falta TeamID
|
||||
KeyIDMissing: Falta KeyID
|
||||
PrivateKeyMissing: Falta la clave privada
|
||||
LoginPolicy:
|
||||
NotFound: Política de inicio de sesión no encontrada
|
||||
Invalid: Política de inicio de sesión no es válida
|
||||
|
||||
@@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: Seuls les caractères alphanumériques, . et - sont autorisés pour un domaine
|
||||
IDP:
|
||||
InvalidSearchQuery: Paramètre de recherche non valide
|
||||
ClientIDMissing: ID client manquant
|
||||
TeamIDMissing: TeamID manquant
|
||||
KeyIDMissing: ID de clé manquant
|
||||
PrivateKeyMissing: clé privée manquante
|
||||
LoginPolicy:
|
||||
NotFound: Politique de connexion non trouvée
|
||||
Invalid: La politique de connexion n'est pas valide
|
||||
|
||||
@@ -199,6 +199,10 @@ Errors:
|
||||
IDP:
|
||||
InvalidSearchQuery: Parametro di ricerca non valido
|
||||
InvalidCharacter: Per un dominio sono ammessi solo caratteri alfanumerici, . e -
|
||||
ClientIDMissing: ClientID mancante
|
||||
TeamIDMissing: TeamID mancante
|
||||
KeyIDMissing: ID chiave mancante
|
||||
PrivateKeyMissing: Chiave privata mancante
|
||||
LoginPolicy:
|
||||
NotFound: Impostazioni di accesso non trovati
|
||||
Invalid: Impostazioni di accesso non sono validi
|
||||
|
||||
@@ -191,6 +191,10 @@ Errors:
|
||||
InvalidCharacter: ドメインは英数字、'.'、'-'のみ使用可能です。
|
||||
IDP:
|
||||
InvalidSearchQuery: 無効な検索クエリです
|
||||
ClientIDMissing: クライアントIDがありません
|
||||
TeamIDMissing: チームIDがありません
|
||||
KeyIDMissing: キーIDがありません
|
||||
PrivateKeyMissing: 秘密キーがありません
|
||||
LoginPolicy:
|
||||
NotFound: ログインポリシーが見つかりません
|
||||
Invalid: 無効なログインポリシーです
|
||||
|
||||
@@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: Дозволени се само алфанумерички знаци, . и - се дозволени за домен
|
||||
IDP:
|
||||
InvalidSearchQuery: Невалидно пребарување
|
||||
ClientID Missing: ClientID недостасува
|
||||
TeamIDMissing: TeamID недостасува
|
||||
Клучен ID Недостасува: Недостасува ID на клуч
|
||||
PrivateKeyMissing: Недостасува приватен клуч
|
||||
LoginPolicy:
|
||||
NotFound: Политиката за најавување не е пронајдена
|
||||
Invalid: Политиката за најавување е невалидна
|
||||
|
||||
@@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: Tylko znaki alfanumeryczne, . i - są dozwolone dla domeny
|
||||
IDP:
|
||||
InvalidSearchQuery: Nieprawidłowe zapytanie wyszukiwania
|
||||
ClientIDMissing: Brak ClientID
|
||||
TeamIDMissing: Brak TeamID
|
||||
KeyIDMissing: Brak KeyID
|
||||
PrivateKeyMissing: Brak klucza prywatnego
|
||||
LoginPolicy:
|
||||
NotFound: Polityka logowania nie znaleziona
|
||||
Invalid: Polityka logowania jest nieprawidłowa
|
||||
|
||||
@@ -197,6 +197,10 @@ Errors:
|
||||
InvalidCharacter: Apenas caracteres alfanuméricos, . e - são permitidos para um domínio
|
||||
IDP:
|
||||
InvalidSearchQuery: Consulta de pesquisa inválida
|
||||
ClientIDMissing: ClientID ausente
|
||||
TeamIDMissing: TeamID ausente
|
||||
KeyIDMissing: KeyID ausente
|
||||
PrivateKeyMissing: Chave privada ausente
|
||||
LoginPolicy:
|
||||
NotFound: Política de login não encontrada
|
||||
Invalid: Política de login é inválida
|
||||
|
||||
@@ -199,6 +199,10 @@ Errors:
|
||||
InvalidCharacter: 只有字母数字字符,.和 - 允许用于域名中
|
||||
IDP:
|
||||
InvalidSearchQuery: 无效的搜索查询
|
||||
ClientIDMissing: 客户端 ID 丢失
|
||||
TeamIDMissing: 团队 ID 丢失
|
||||
KeyIDMissing: 密钥 ID 丢失
|
||||
PrivateKeyMissing: 私钥丢失
|
||||
LoginPolicy:
|
||||
NotFound: 未找到登录策略
|
||||
Invalid: 登录策略无效
|
||||
|
||||
Reference in New Issue
Block a user