diff --git a/go.mod b/go.mod
index e388abc2a6..8a1d1c0e1a 100644
--- a/go.mod
+++ b/go.mod
@@ -53,7 +53,7 @@ require (
github.com/sony/sonyflake v1.1.0
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.15.0
- github.com/stretchr/testify v1.8.1
+ github.com/stretchr/testify v1.8.2
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203
github.com/ttacon/libphonenumber v1.2.1
github.com/zitadel/logging v0.3.4
diff --git a/go.sum b/go.sum
index e2f474e886..30b15fcc10 100644
--- a/go.sum
+++ b/go.sum
@@ -1087,8 +1087,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
diff --git a/internal/api/grpc/admin/idp_converter.go b/internal/api/grpc/admin/idp_converter.go
index 0cb4ae499d..7aeb7ec332 100644
--- a/internal/api/grpc/admin/idp_converter.go
+++ b/internal/api/grpc/admin/idp_converter.go
@@ -407,32 +407,34 @@ func updateGoogleProviderToCommand(req *admin_pb.UpdateGoogleProviderRequest) co
func addLDAPProviderToCommand(req *admin_pb.AddLDAPProviderRequest) command.LDAPProvider {
return command.LDAPProvider{
- Name: req.Name,
- Host: req.Host,
- Port: req.Port,
- TLS: req.Tls,
- BaseDN: req.BaseDn,
- UserObjectClass: req.UserObjectClass,
- UserUniqueAttribute: req.UserUniqueAttribute,
- Admin: req.Admin,
- Password: req.Password,
- LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
- IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
+ Name: req.Name,
+ Servers: req.Servers,
+ StartTLS: req.StartTls,
+ BaseDN: req.BaseDn,
+ BindDN: req.BindDn,
+ BindPassword: req.BindPassword,
+ UserBase: req.UserBase,
+ UserObjectClasses: req.UserObjectClasses,
+ UserFilters: req.UserFilters,
+ Timeout: req.Timeout.AsDuration(),
+ LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
+ IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func updateLDAPProviderToCommand(req *admin_pb.UpdateLDAPProviderRequest) command.LDAPProvider {
return command.LDAPProvider{
- Name: req.Name,
- Host: req.Host,
- Port: req.Port,
- TLS: req.Tls,
- BaseDN: req.BaseDn,
- UserObjectClass: req.UserObjectClass,
- UserUniqueAttribute: req.UserUniqueAttribute,
- Admin: req.Admin,
- Password: req.Password,
- LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
- IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
+ Name: req.Name,
+ Servers: req.Servers,
+ StartTLS: req.StartTls,
+ BaseDN: req.BaseDn,
+ BindDN: req.BindDn,
+ BindPassword: req.BindPassword,
+ UserBase: req.UserBase,
+ UserObjectClasses: req.UserObjectClasses,
+ UserFilters: req.UserFilters,
+ Timeout: req.Timeout.AsDuration(),
+ LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
+ IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
diff --git a/internal/api/grpc/idp/converter.go b/internal/api/grpc/idp/converter.go
index 4ff09dec94..c3980bd035 100644
--- a/internal/api/grpc/idp/converter.go
+++ b/internal/api/grpc/idp/converter.go
@@ -1,6 +1,8 @@
package idp
import (
+ "google.golang.org/protobuf/types/known/durationpb"
+
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/domain"
iam_model "github.com/zitadel/zitadel/internal/iam/model"
@@ -582,16 +584,21 @@ func googleConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.Goo
}
func ldapConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.LDAPIDPTemplate) {
+ var timeout *durationpb.Duration
+ if template.Timeout != 0 {
+ timeout = durationpb.New(template.Timeout)
+ }
providerConfig.Config = &idp_pb.ProviderConfig_Ldap{
Ldap: &idp_pb.LDAPConfig{
- Host: template.Host,
- Port: template.Port,
- Tls: template.TLS,
- BaseDn: template.BaseDN,
- UserObjectClass: template.UserObjectClass,
- UserUniqueAttribute: template.UserUniqueAttribute,
- Admin: template.Admin,
- Attributes: ldapAttributesToPb(template.LDAPAttributes),
+ Servers: template.Servers,
+ StartTls: template.StartTLS,
+ BaseDn: template.BaseDN,
+ BindDn: template.BindDN,
+ UserBase: template.UserBase,
+ UserObjectClasses: template.UserObjectClasses,
+ UserFilters: template.UserFilters,
+ Timeout: timeout,
+ Attributes: ldapAttributesToPb(template.LDAPAttributes),
},
}
}
diff --git a/internal/api/grpc/management/idp_converter.go b/internal/api/grpc/management/idp_converter.go
index ad78492668..70bcc5dd08 100644
--- a/internal/api/grpc/management/idp_converter.go
+++ b/internal/api/grpc/management/idp_converter.go
@@ -422,32 +422,34 @@ func updateGoogleProviderToCommand(req *mgmt_pb.UpdateGoogleProviderRequest) com
func addLDAPProviderToCommand(req *mgmt_pb.AddLDAPProviderRequest) command.LDAPProvider {
return command.LDAPProvider{
- Name: req.Name,
- Host: req.Host,
- Port: req.Port,
- TLS: req.Tls,
- BaseDN: req.BaseDn,
- UserObjectClass: req.UserObjectClass,
- UserUniqueAttribute: req.UserUniqueAttribute,
- Admin: req.Admin,
- Password: req.Password,
- LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
- IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
+ Name: req.Name,
+ Servers: req.Servers,
+ StartTLS: req.StartTls,
+ BaseDN: req.BaseDn,
+ BindDN: req.BindDn,
+ BindPassword: req.BindPassword,
+ UserBase: req.UserBase,
+ UserObjectClasses: req.UserObjectClasses,
+ UserFilters: req.UserFilters,
+ Timeout: req.Timeout.AsDuration(),
+ LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
+ IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func updateLDAPProviderToCommand(req *mgmt_pb.UpdateLDAPProviderRequest) command.LDAPProvider {
return command.LDAPProvider{
- Name: req.Name,
- Host: req.Host,
- Port: req.Port,
- TLS: req.Tls,
- BaseDN: req.BaseDn,
- UserObjectClass: req.UserObjectClass,
- UserUniqueAttribute: req.UserUniqueAttribute,
- Admin: req.Admin,
- Password: req.Password,
- LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
- IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
+ Name: req.Name,
+ Servers: req.Servers,
+ StartTLS: req.StartTls,
+ BaseDN: req.BaseDn,
+ BindDN: req.BindDn,
+ BindPassword: req.BindPassword,
+ UserBase: req.UserBase,
+ UserObjectClasses: req.UserObjectClasses,
+ UserFilters: req.UserFilters,
+ Timeout: req.Timeout.AsDuration(),
+ LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
+ IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
diff --git a/internal/api/ui/login/external_provider_handler.go b/internal/api/ui/login/external_provider_handler.go
index 3886c0f0ad..cac82900ab 100644
--- a/internal/api/ui/login/external_provider_handler.go
+++ b/internal/api/ui/login/external_provider_handler.go
@@ -23,6 +23,7 @@ import (
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
"github.com/zitadel/zitadel/internal/idp/providers/google"
"github.com/zitadel/zitadel/internal/idp/providers/jwt"
+ "github.com/zitadel/zitadel/internal/idp/providers/ldap"
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
"github.com/zitadel/zitadel/internal/query"
@@ -157,8 +158,9 @@ 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.IDPTypeLDAP,
- domain.IDPTypeUnspecified:
+ case domain.IDPTypeLDAP:
+ provider, err = l.ldapProvider(r.Context(), identityProvider)
+ case domain.IDPTypeUnspecified:
fallthrough
default:
l.renderLogin(w, r, authReq, errors.ThrowInvalidArgument(nil, "LOGIN-AShek", "Errors.ExternalIDP.IDPTypeNotImplemented"))
@@ -604,6 +606,69 @@ func (l *Login) updateExternalUser(ctx context.Context, authReq *domain.AuthRequ
return nil
}
+func (l *Login) ldapProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*ldap.Provider, error) {
+ password, err := crypto.DecryptString(identityProvider.LDAPIDPTemplate.BindPassword, l.idpConfigAlg)
+ if err != nil {
+ return nil, err
+ }
+ var opts []ldap.ProviderOpts
+ if !identityProvider.LDAPIDPTemplate.StartTLS {
+ opts = append(opts, ldap.WithoutStartTLS())
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.IDAttribute != "" {
+ opts = append(opts, ldap.WithCustomIDAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.IDAttribute))
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.FirstNameAttribute != "" {
+ opts = append(opts, ldap.WithFirstNameAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.FirstNameAttribute))
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.LastNameAttribute != "" {
+ opts = append(opts, ldap.WithLastNameAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.LastNameAttribute))
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.DisplayNameAttribute != "" {
+ opts = append(opts, ldap.WithDisplayNameAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.DisplayNameAttribute))
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.NickNameAttribute != "" {
+ opts = append(opts, ldap.WithNickNameAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.NickNameAttribute))
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.PreferredUsernameAttribute != "" {
+ opts = append(opts, ldap.WithPreferredUsernameAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.PreferredUsernameAttribute))
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.EmailAttribute != "" {
+ opts = append(opts, ldap.WithEmailAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.EmailAttribute))
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.EmailVerifiedAttribute != "" {
+ opts = append(opts, ldap.WithEmailVerifiedAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.EmailVerifiedAttribute))
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.PhoneAttribute != "" {
+ opts = append(opts, ldap.WithPhoneAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.PhoneAttribute))
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.PhoneVerifiedAttribute != "" {
+ opts = append(opts, ldap.WithPhoneVerifiedAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.PhoneVerifiedAttribute))
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.PreferredLanguageAttribute != "" {
+ opts = append(opts, ldap.WithPreferredLanguageAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.PreferredLanguageAttribute))
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.AvatarURLAttribute != "" {
+ opts = append(opts, ldap.WithAvatarURLAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.AvatarURLAttribute))
+ }
+ if identityProvider.LDAPIDPTemplate.LDAPAttributes.ProfileAttribute != "" {
+ opts = append(opts, ldap.WithProfileAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.ProfileAttribute))
+ }
+ return ldap.New(
+ identityProvider.Name,
+ identityProvider.Servers,
+ identityProvider.BaseDN,
+ identityProvider.BindDN,
+ password,
+ identityProvider.UserBase,
+ identityProvider.UserObjectClasses,
+ identityProvider.UserFilters,
+ identityProvider.Timeout,
+ l.baseURL(ctx)+EndpointLDAPLogin+"?"+QueryAuthRequestID+"=",
+ opts...,
+ ), nil
+}
+
func (l *Login) googleProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*google.Provider, error) {
errorHandler := func(w http.ResponseWriter, r *http.Request, errorType string, errorDesc string, state string) {
logging.Errorf("token exchanged failed: %s - %s (state: %s)", errorType, errorType, state)
diff --git a/internal/api/ui/login/ldap_handler.go b/internal/api/ui/login/ldap_handler.go
new file mode 100644
index 0000000000..1804a4884d
--- /dev/null
+++ b/internal/api/ui/login/ldap_handler.go
@@ -0,0 +1,83 @@
+package login
+
+import (
+ "net/http"
+
+ "github.com/zitadel/logging"
+
+ http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
+ "github.com/zitadel/zitadel/internal/domain"
+ "github.com/zitadel/zitadel/internal/idp/providers/ldap"
+)
+
+const (
+ tmplLDAPLogin = "ldap_login"
+)
+
+type ldapFormData struct {
+ Username string `schema:"ldapusername"`
+ Password string `schema:"ldappassword"`
+ ResetExternalIDP bool `schema:"resetexternalidp"`
+}
+
+func (l *Login) handleLDAP(w http.ResponseWriter, r *http.Request) {
+ authReq, err := l.getAuthRequest(r)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ l.renderLDAPLogin(w, r, authReq, nil)
+}
+
+func (l *Login) renderLDAPLogin(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) {
+ var errID, errMessage string
+ if err != nil {
+ errID, errMessage = l.getErrorMessage(r, err)
+ }
+ temp := l.renderer.Templates[tmplLDAPLogin]
+ data := l.getUserData(r, authReq, "Login.Title", "Login.Description", errID, errMessage)
+ l.renderer.RenderTemplate(w, r, l.getTranslator(r.Context(), authReq), temp, data, nil)
+}
+
+func (l *Login) handleLDAPCallback(w http.ResponseWriter, r *http.Request) {
+ data := new(ldapFormData)
+ authReq, err := l.getAuthRequestAndParseData(r, data)
+ if err != nil {
+ l.renderError(w, r, authReq, err)
+ return
+ }
+ if data.ResetExternalIDP {
+ userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
+ err := l.authRepo.ResetSelectedIDP(r.Context(), authReq.ID, userAgentID)
+ if err != nil {
+ l.renderLDAPLogin(w, r, authReq, err)
+ return
+ }
+
+ l.handleLoginName(w, r)
+ return
+ }
+
+ identityProvider, err := l.getIDPByID(r, authReq.SelectedIDPConfigID)
+ if err != nil {
+ l.renderLDAPLogin(w, r, authReq, err)
+ return
+ }
+
+ provider, err := l.ldapProvider(r.Context(), identityProvider)
+ if err != nil {
+ l.renderLDAPLogin(w, r, authReq, err)
+ return
+ }
+ session := &ldap.Session{Provider: provider, User: data.Username, Password: data.Password}
+
+ user, err := session.FetchUser(r.Context())
+ if err != nil {
+ if _, actionErr := l.runPostExternalAuthenticationActions(new(domain.ExternalUser), nil, authReq, r, nil, err); actionErr != nil {
+ logging.WithError(err).Error("both external user authentication and action post authentication failed")
+ }
+ l.renderLDAPLogin(w, r, authReq, err)
+ return
+ }
+ l.handleExternalUserAuthenticated(w, r, authReq, identityProvider, session, user, l.renderNextStep)
+}
diff --git a/internal/api/ui/login/renderer.go b/internal/api/ui/login/renderer.go
index e0d42aabd7..b08d452ac2 100644
--- a/internal/api/ui/login/renderer.go
+++ b/internal/api/ui/login/renderer.go
@@ -76,6 +76,7 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
tmplLinkUsersDone: "link_users_done.html",
tmplExternalNotFoundOption: "external_not_found_option.html",
tmplLoginSuccess: "login_success.html",
+ tmplLDAPLogin: "ldap_login.html",
}
funcs := map[string]interface{}{
"resourceUrl": func(file string) string {
@@ -219,6 +220,9 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
"idpProviderClass": func(idpType domain.IDPType) string {
return idpType.GetCSSClass()
},
+ "ldapUrl": func() string {
+ return path.Join(r.pathPrefix, EndpointLDAPCallback)
+ },
}
var err error
r.Renderer, err = renderer.NewRenderer(
diff --git a/internal/api/ui/login/router.go b/internal/api/ui/login/router.go
index e0f776f241..e723cad1ac 100644
--- a/internal/api/ui/login/router.go
+++ b/internal/api/ui/login/router.go
@@ -15,6 +15,8 @@ const (
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"
@@ -102,6 +104,8 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
router.HandleFunc(EndpointRegisterOrg, login.handleRegisterOrg).Methods(http.MethodGet)
router.HandleFunc(EndpointRegisterOrg, login.handleRegisterOrgCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointLoginSuccess, login.handleLoginSuccess).Methods(http.MethodGet)
+ router.HandleFunc(EndpointLDAPLogin, login.handleLDAP).Methods(http.MethodGet)
+ router.HandleFunc(EndpointLDAPCallback, login.handleLDAPCallback).Methods(http.MethodPost)
router.SkipClean(true).Handle("", http.RedirectHandler(HandlerPrefix+"/", http.StatusMovedPermanently))
return router
}
diff --git a/internal/api/ui/login/static/i18n/de.yaml b/internal/api/ui/login/static/i18n/de.yaml
index bb463266fb..be2f1e4c95 100644
--- a/internal/api/ui/login/static/i18n/de.yaml
+++ b/internal/api/ui/login/static/i18n/de.yaml
@@ -11,6 +11,13 @@ Login:
RegisterButtonText: registrieren
NextButtonText: weiter
+LDAP:
+ Title: Anmeldung
+ Description: Mit Konto anmelden.
+ LoginNameLabel: Loginname
+ PasswordLabel: Passwort
+ NextButtonText: weiter
+
SelectAccount:
Title: Account auswählen
Description: Wähle deinen Account aus.
diff --git a/internal/api/ui/login/static/i18n/en.yaml b/internal/api/ui/login/static/i18n/en.yaml
index bc980dc97b..2ca0b37d26 100644
--- a/internal/api/ui/login/static/i18n/en.yaml
+++ b/internal/api/ui/login/static/i18n/en.yaml
@@ -11,6 +11,13 @@ Login:
RegisterButtonText: register
NextButtonText: next
+LDAP:
+ Title: Login
+ Description: Enter your login data.
+ LoginNameLabel: Loginname
+ PasswordLabel: Password
+ NextButtonText: next
+
SelectAccount:
Title: Select account
Description: Use your ZITADEL-Account
diff --git a/internal/api/ui/login/static/i18n/fr.yaml b/internal/api/ui/login/static/i18n/fr.yaml
index 75a08ae808..f19a3e0b79 100644
--- a/internal/api/ui/login/static/i18n/fr.yaml
+++ b/internal/api/ui/login/static/i18n/fr.yaml
@@ -11,6 +11,13 @@ Login:
RegisterButtonText: s'inscrire
NextButtonText: suivant
+LDAP:
+ Title: Connexion
+ Description: Entrez vos données de connexion.
+ LoginNameLabel: Identifiant
+ PasswordLabel: Mot de passe
+ NextButtonText: suivant
+
SelectAccount:
Title: Sélectionner un compte
Description: Utilisez votre compte ZITADEL
diff --git a/internal/api/ui/login/static/i18n/it.yaml b/internal/api/ui/login/static/i18n/it.yaml
index 547c4fc8e2..0e72249d9b 100644
--- a/internal/api/ui/login/static/i18n/it.yaml
+++ b/internal/api/ui/login/static/i18n/it.yaml
@@ -11,6 +11,13 @@ Login:
RegisterButtonText: registrare
NextButtonText: Avanti
+LDAP:
+ Title: Accesso
+ Description: Inserisci i tuoi dati di accesso.
+ LoginNameLabel: Nome di accesso
+ PasswordLabel: Password
+ NextButtonText: Avanti
+
SelectAccount:
Title: Seleziona l'account
Description: Usa il tuo account ZITADEL
diff --git a/internal/api/ui/login/static/i18n/pl.yaml b/internal/api/ui/login/static/i18n/pl.yaml
index f6d6577028..e8910efe5f 100644
--- a/internal/api/ui/login/static/i18n/pl.yaml
+++ b/internal/api/ui/login/static/i18n/pl.yaml
@@ -11,6 +11,13 @@ Login:
RegisterButtonText: zarejestruj
NextButtonText: dalej
+LDAP:
+ Title: Rejestracja
+ Description: Wprowadź swoje dane logowania.
+ LoginNameLabel: Nazwa użytkownika
+ PasswordLabel: Hasło
+ NextButtonText: dalej
+
SelectAccount:
Title: Wybierz konto
Description: Użyj swojego konta ZITADEL
diff --git a/internal/api/ui/login/static/i18n/zh.yaml b/internal/api/ui/login/static/i18n/zh.yaml
index b395899127..112c99c2e8 100644
--- a/internal/api/ui/login/static/i18n/zh.yaml
+++ b/internal/api/ui/login/static/i18n/zh.yaml
@@ -11,6 +11,13 @@ Login:
RegisterButtonText: 注册
NextButtonText: 继续
+LDAP:
+ Title: 注册
+ Description: 输入您的登录数据。
+ LoginNameLabel: 登录名
+ PasswordLabel: 密码
+ NextButtonText: 继续
+
SelectAccount:
Title: 选择账户
Description: 使用您的 ZITADEL 帐户
diff --git a/internal/api/ui/login/static/templates/ldap_login.html b/internal/api/ui/login/static/templates/ldap_login.html
new file mode 100644
index 0000000000..850bf27e57
--- /dev/null
+++ b/internal/api/ui/login/static/templates/ldap_login.html
@@ -0,0 +1,40 @@
+{{template "main-top" .}}
+
+
+
{{t "LDAP.Title"}}
+
{{t "LDAP.Description"}}
+
+
+
+
+
+
+
+
+{{template "main-bottom" .}}
\ No newline at end of file
diff --git a/internal/auth/repository/auth_request.go b/internal/auth/repository/auth_request.go
index 028feb9973..17f8893655 100644
--- a/internal/auth/repository/auth_request.go
+++ b/internal/auth/repository/auth_request.go
@@ -34,4 +34,5 @@ type AuthRequestRepository interface {
LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *domain.BrowserInfo) error
AutoRegisterExternalUser(ctx context.Context, user *domain.Human, externalIDP *domain.UserIDPLink, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) error
ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error
+ ResetSelectedIDP(ctx context.Context, authReqID, userAgentID string) error
}
diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go
index 6d806ef2ab..7c77b61639 100644
--- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go
+++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go
@@ -461,6 +461,15 @@ func (repo *AuthRequestRepo) ResetLinkingUsers(ctx context.Context, authReqID, u
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
+func (repo *AuthRequestRepo) ResetSelectedIDP(ctx context.Context, authReqID, userAgentID string) error {
+ request, err := repo.getAuthRequest(ctx, authReqID, userAgentID)
+ if err != nil {
+ return err
+ }
+ request.SelectedIDPConfigID = ""
+ return repo.AuthRequests.UpdateAuthRequest(ctx, request)
+}
+
func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *domain.Human, externalIDP *domain.UserIDPLink, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) (err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
diff --git a/internal/command/idp.go b/internal/command/idp.go
index 7ae7cfd68a..a5605e8a3a 100644
--- a/internal/command/idp.go
+++ b/internal/command/idp.go
@@ -2,6 +2,7 @@ package command
import (
"context"
+ "time"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command/preparation"
@@ -94,17 +95,18 @@ type GoogleProvider struct {
}
type LDAPProvider struct {
- Name string
- Host string
- Port string
- TLS bool
- BaseDN string
- UserObjectClass string
- UserUniqueAttribute string
- Admin string
- Password string
- LDAPAttributes idp.LDAPAttributes
- IDPOptions idp.Options
+ Name string
+ Servers []string
+ StartTLS bool
+ BaseDN string
+ BindDN string
+ BindPassword string
+ UserBase string
+ UserObjectClasses []string
+ UserFilters []string
+ Timeout time.Duration
+ LDAPAttributes idp.LDAPAttributes
+ IDPOptions idp.Options
}
func ExistsIDP(ctx context.Context, filter preparation.FilterToQueryReducer, id, orgID string) (exists bool, err error) {
diff --git a/internal/command/idp_model.go b/internal/command/idp_model.go
index 452de29198..2e098fecfd 100644
--- a/internal/command/idp_model.go
+++ b/internal/command/idp_model.go
@@ -2,6 +2,7 @@ package command
import (
"reflect"
+ "time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
@@ -999,16 +1000,17 @@ func (wm *GoogleIDPWriteModel) NewChanges(
type LDAPIDPWriteModel struct {
eventstore.WriteModel
- ID string
- Name string
- Host string
- Port string
- TLS bool
- BaseDN string
- UserObjectClass string
- UserUniqueAttribute string
- Admin string
- Password *crypto.CryptoValue
+ ID string
+ Name string
+ Servers []string
+ StartTLS bool
+ BaseDN string
+ BindDN string
+ BindPassword *crypto.CryptoValue
+ UserBase string
+ UserObjectClasses []string
+ UserFilters []string
+ Timeout time.Duration
idp.LDAPAttributes
idp.Options
@@ -1040,14 +1042,15 @@ func (wm *LDAPIDPWriteModel) Reduce() error {
func (wm *LDAPIDPWriteModel) reduceAddedEvent(e *idp.LDAPIDPAddedEvent) {
wm.Name = e.Name
- wm.Host = e.Host
- wm.Port = e.Port
- wm.TLS = e.TLS
+ wm.Servers = e.Servers
+ wm.StartTLS = e.StartTLS
wm.BaseDN = e.BaseDN
- wm.UserObjectClass = e.UserObjectClass
- wm.UserUniqueAttribute = e.UserUniqueAttribute
- wm.Admin = e.Admin
- wm.Password = e.Password
+ wm.BindDN = e.BindDN
+ wm.BindPassword = e.BindPassword
+ wm.UserBase = e.UserBase
+ wm.UserObjectClasses = e.UserObjectClasses
+ wm.UserFilters = e.UserFilters
+ wm.Timeout = e.Timeout
wm.LDAPAttributes = e.LDAPAttributes
wm.Options = e.Options
wm.State = domain.IDPStateActive
@@ -1060,44 +1063,48 @@ func (wm *LDAPIDPWriteModel) reduceChangedEvent(e *idp.LDAPIDPChangedEvent) {
if e.Name != nil {
wm.Name = *e.Name
}
- if e.Host != nil {
- wm.Host = *e.Host
+ if e.Servers != nil {
+ wm.Servers = e.Servers
}
- if e.Port != nil {
- wm.Port = *e.Port
- }
- if e.TLS != nil {
- wm.TLS = *e.TLS
+ if e.StartTLS != nil {
+ wm.StartTLS = *e.StartTLS
}
if e.BaseDN != nil {
wm.BaseDN = *e.BaseDN
}
- if e.UserObjectClass != nil {
- wm.UserObjectClass = *e.UserObjectClass
+ if e.BindDN != nil {
+ wm.BindDN = *e.BindDN
}
- if e.UserUniqueAttribute != nil {
- wm.UserUniqueAttribute = *e.UserUniqueAttribute
+ if e.BindPassword != nil {
+ wm.BindPassword = e.BindPassword
}
- if e.Admin != nil {
- wm.Admin = *e.Admin
+ if e.UserBase != nil {
+ wm.UserBase = *e.UserBase
}
- if e.Password != nil {
- wm.Password = e.Password
+ if e.UserObjectClasses != nil {
+ wm.UserObjectClasses = e.UserObjectClasses
+ }
+ if e.UserFilters != nil {
+ wm.UserFilters = e.UserFilters
+ }
+ if e.Timeout != nil {
+ wm.Timeout = *e.Timeout
}
wm.LDAPAttributes.ReduceChanges(e.LDAPAttributeChanges)
wm.Options.ReduceChanges(e.OptionChanges)
}
func (wm *LDAPIDPWriteModel) NewChanges(
- name,
- host,
- port string,
- tls bool,
- baseDN,
- userObjectClass,
- userUniqueAttribute,
- admin string,
- password string,
+ name string,
+ servers []string,
+ startTLS bool,
+ baseDN string,
+ bindDN string,
+ bindPassword string,
+ userBase string,
+ userObjectClasses []string,
+ userFilters []string,
+ timeout time.Duration,
secretCrypto crypto.Crypto,
attributes idp.LDAPAttributes,
options idp.Options,
@@ -1105,36 +1112,39 @@ func (wm *LDAPIDPWriteModel) NewChanges(
changes := make([]idp.LDAPIDPChanges, 0)
var cryptedPassword *crypto.CryptoValue
var err error
- if password != "" {
- cryptedPassword, err = crypto.Crypt([]byte(password), secretCrypto)
+ if bindPassword != "" {
+ cryptedPassword, err = crypto.Crypt([]byte(bindPassword), secretCrypto)
if err != nil {
return nil, err
}
- changes = append(changes, idp.ChangeLDAPPassword(cryptedPassword))
+ changes = append(changes, idp.ChangeLDAPBindPassword(cryptedPassword))
}
if wm.Name != name {
changes = append(changes, idp.ChangeLDAPName(name))
}
- if wm.Host != host {
- changes = append(changes, idp.ChangeLDAPHost(host))
+ if !reflect.DeepEqual(wm.Servers, servers) {
+ changes = append(changes, idp.ChangeLDAPServers(servers))
}
- if wm.Port != port {
- changes = append(changes, idp.ChangeLDAPPort(port))
- }
- if wm.TLS != tls {
- changes = append(changes, idp.ChangeLDAPTLS(tls))
+ if wm.StartTLS != startTLS {
+ changes = append(changes, idp.ChangeLDAPStartTLS(startTLS))
}
if wm.BaseDN != baseDN {
changes = append(changes, idp.ChangeLDAPBaseDN(baseDN))
}
- if wm.UserObjectClass != userObjectClass {
- changes = append(changes, idp.ChangeLDAPUserObjectClass(userObjectClass))
+ if wm.BindDN != bindDN {
+ changes = append(changes, idp.ChangeLDAPBindDN(bindDN))
}
- if wm.UserUniqueAttribute != userUniqueAttribute {
- changes = append(changes, idp.ChangeLDAPUserUniqueAttribute(userUniqueAttribute))
+ if wm.UserBase != userBase {
+ changes = append(changes, idp.ChangeLDAPUserBase(userBase))
}
- if wm.Admin != admin {
- changes = append(changes, idp.ChangeLDAPAdmin(admin))
+ if !reflect.DeepEqual(wm.UserObjectClasses, userObjectClasses) {
+ changes = append(changes, idp.ChangeLDAPUserObjectClasses(userObjectClasses))
+ }
+ if !reflect.DeepEqual(wm.UserFilters, userFilters) {
+ changes = append(changes, idp.ChangeLDAPUserFilters(userFilters))
+ }
+ if wm.Timeout != timeout {
+ changes = append(changes, idp.ChangeLDAPTimeout(timeout))
}
attrs := wm.LDAPAttributes.Changes(attributes)
if !attrs.IsZero() {
diff --git a/internal/command/instance_idp.go b/internal/command/instance_idp.go
index e96f172bee..9d2533a12f 100644
--- a/internal/command/instance_idp.go
+++ b/internal/command/instance_idp.go
@@ -1278,23 +1278,26 @@ func (c *Commands) prepareAddInstanceLDAPProvider(a *instance.Aggregate, writeMo
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SAfdd", "Errors.Invalid.Argument")
}
- if provider.Host = strings.TrimSpace(provider.Host); provider.Host == "" {
- return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SDVg2", "Errors.Invalid.Argument")
- }
if provider.BaseDN = strings.TrimSpace(provider.BaseDN); provider.BaseDN == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-sv31s", "Errors.Invalid.Argument")
}
- if provider.UserObjectClass = strings.TrimSpace(provider.UserObjectClass); provider.UserObjectClass == "" {
+ if provider.BindDN = strings.TrimSpace(provider.BindDN); provider.BindDN == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-sdgf4", "Errors.Invalid.Argument")
}
- if provider.UserUniqueAttribute = strings.TrimSpace(provider.UserUniqueAttribute); provider.UserUniqueAttribute == "" {
+ if provider.BindPassword = strings.TrimSpace(provider.BindPassword); provider.BindPassword == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-AEG2w", "Errors.Invalid.Argument")
}
- if provider.Admin = strings.TrimSpace(provider.Admin); provider.Admin == "" {
+ if provider.UserBase = strings.TrimSpace(provider.UserBase); provider.UserBase == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SAD5n", "Errors.Invalid.Argument")
}
- if provider.Password = strings.TrimSpace(provider.Password); provider.Password == "" {
- return nil, caos_errs.ThrowInvalidArgument(nil, "INST-sdf5h", "Errors.Invalid.Argument")
+ if len(provider.Servers) == 0 {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SAx905n", "Errors.Invalid.Argument")
+ }
+ if len(provider.UserObjectClasses) == 0 {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "INST-S1x905n", "Errors.Invalid.Argument")
+ }
+ if len(provider.UserFilters) == 0 {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "INST-aAx905n", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
@@ -1305,7 +1308,7 @@ func (c *Commands) prepareAddInstanceLDAPProvider(a *instance.Aggregate, writeMo
if err = writeModel.Reduce(); err != nil {
return nil, err
}
- secret, err := crypto.Encrypt([]byte(provider.Password), c.idpConfigEncryption)
+ secret, err := crypto.Encrypt([]byte(provider.BindPassword), c.idpConfigEncryption)
if err != nil {
return nil, err
}
@@ -1315,14 +1318,15 @@ func (c *Commands) prepareAddInstanceLDAPProvider(a *instance.Aggregate, writeMo
&a.Aggregate,
writeModel.ID,
provider.Name,
- provider.Host,
- provider.Port,
- provider.TLS,
+ provider.Servers,
+ provider.StartTLS,
provider.BaseDN,
- provider.UserObjectClass,
- provider.UserUniqueAttribute,
- provider.Admin,
+ provider.BindDN,
secret,
+ provider.UserBase,
+ provider.UserObjectClasses,
+ provider.UserFilters,
+ provider.Timeout,
provider.LDAPAttributes,
provider.IDPOptions,
),
@@ -1339,21 +1343,24 @@ func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, writ
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Sffgd", "Errors.Invalid.Argument")
}
- if provider.Host = strings.TrimSpace(provider.Host); provider.Host == "" {
- return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Dz62d", "Errors.Invalid.Argument")
- }
if provider.BaseDN = strings.TrimSpace(provider.BaseDN); provider.BaseDN == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-vb3ss", "Errors.Invalid.Argument")
}
- if provider.UserObjectClass = strings.TrimSpace(provider.UserObjectClass); provider.UserObjectClass == "" {
+ if provider.BindDN = strings.TrimSpace(provider.BindDN); provider.BindDN == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-hbere", "Errors.Invalid.Argument")
}
- if provider.UserUniqueAttribute = strings.TrimSpace(provider.UserUniqueAttribute); provider.UserUniqueAttribute == "" {
- return nil, caos_errs.ThrowInvalidArgument(nil, "INST-ASFt6", "Errors.Invalid.Argument")
- }
- if provider.Admin = strings.TrimSpace(provider.Admin); provider.Admin == "" {
+ if provider.UserBase = strings.TrimSpace(provider.UserBase); provider.UserBase == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-DG45z", "Errors.Invalid.Argument")
}
+ if len(provider.Servers) == 0 {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SAx945n", "Errors.Invalid.Argument")
+ }
+ if len(provider.UserObjectClasses) == 0 {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "INST-S1x605n", "Errors.Invalid.Argument")
+ }
+ if len(provider.UserFilters) == 0 {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "INST-aAx901n", "Errors.Invalid.Argument")
+ }
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
if err != nil {
@@ -1370,16 +1377,16 @@ func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, writ
ctx,
&a.Aggregate,
writeModel.ID,
- writeModel.Name,
provider.Name,
- provider.Host,
- provider.Port,
- provider.TLS,
+ provider.Servers,
+ provider.StartTLS,
provider.BaseDN,
- provider.UserObjectClass,
- provider.UserUniqueAttribute,
- provider.Admin,
- provider.Password,
+ provider.BindDN,
+ provider.BindPassword,
+ provider.UserBase,
+ provider.UserObjectClasses,
+ provider.UserFilters,
+ provider.Timeout,
c.idpConfigEncryption,
provider.LDAPAttributes,
provider.IDPOptions,
diff --git a/internal/command/instance_idp_model.go b/internal/command/instance_idp_model.go
index f2363e54f1..41c971cba3 100644
--- a/internal/command/instance_idp_model.go
+++ b/internal/command/instance_idp_model.go
@@ -2,6 +2,7 @@ package command
import (
"context"
+ "time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -744,16 +745,16 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
- oldName,
- name,
- host,
- port string,
- tls bool,
- baseDN,
- userObjectClass,
- userUniqueAttribute,
- admin string,
- password string,
+ name string,
+ servers []string,
+ startTLS bool,
+ baseDN string,
+ bindDN string,
+ bindPassword string,
+ userBase string,
+ userObjectClasses []string,
+ userFilters []string,
+ timeout time.Duration,
secretCrypto crypto.Crypto,
attributes idp.LDAPAttributes,
options idp.Options,
@@ -761,14 +762,15 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
changes, err := wm.LDAPIDPWriteModel.NewChanges(
name,
- host,
- port,
- tls,
+ servers,
+ startTLS,
baseDN,
- userObjectClass,
- userUniqueAttribute,
- admin,
- password,
+ bindDN,
+ bindPassword,
+ userBase,
+ userObjectClasses,
+ userFilters,
+ timeout,
secretCrypto,
attributes,
options,
@@ -776,7 +778,7 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
if err != nil || len(changes) == 0 {
return nil, err
}
- return instance.NewLDAPIDPChangedEvent(ctx, aggregate, id, oldName, changes)
+ return instance.NewLDAPIDPChangedEvent(ctx, aggregate, id, changes)
}
type InstanceIDPRemoveWriteModel struct {
diff --git a/internal/command/instance_idp_test.go b/internal/command/instance_idp_test.go
index e4adfee60e..ed2b5fcaf8 100644
--- a/internal/command/instance_idp_test.go
+++ b/internal/command/instance_idp_test.go
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"testing"
+ "time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
@@ -18,7 +19,6 @@ import (
"github.com/zitadel/zitadel/internal/id"
id_mock "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/idp"
- "github.com/zitadel/zitadel/internal/repository/idpconfig"
"github.com/zitadel/zitadel/internal/repository/instance"
)
@@ -3677,24 +3677,6 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
},
},
},
- {
- "invalid host",
- fields{
- eventstore: eventstoreExpect(t),
- idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
- },
- args{
- ctx: authz.WithInstanceID(context.Background(), "instance1"),
- provider: LDAPProvider{
- Name: "name",
- },
- },
- res{
- err: func(err error) bool {
- return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-SDVg2", ""))
- },
- },
- },
{
"invalid baseDN",
fields{
@@ -3705,7 +3687,6 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{
Name: "name",
- Host: "host",
},
},
res{
@@ -3715,7 +3696,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
},
},
{
- "invalid userObjectClass",
+ "invalid bindDN",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@@ -3724,7 +3705,6 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{
Name: "name",
- Host: "host",
BaseDN: "baseDN",
},
},
@@ -3735,7 +3715,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
},
},
{
- "invalid userUniqueAttribute",
+ "invalid bindPassword",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@@ -3743,10 +3723,9 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
+ Name: "name",
+ BindDN: "binddn",
+ BaseDN: "baseDN",
},
},
res{
@@ -3756,7 +3735,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
},
},
{
- "invalid admin",
+ "invalid userBase",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@@ -3764,11 +3743,10 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
+ Name: "name",
+ BindDN: "binddn",
+ BaseDN: "baseDN",
+ BindPassword: "password",
},
},
res{
@@ -3778,7 +3756,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
},
},
{
- "invalid password",
+ "invalid servers",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@@ -3786,17 +3764,63 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
- Admin: "admin",
+ Name: "name",
+ BindDN: "binddn",
+ BaseDN: "baseDN",
+ BindPassword: "password",
+ UserBase: "user",
},
},
res{
err: func(err error) bool {
- return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-sdf5h", ""))
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-SAx905n", ""))
+ },
+ },
+ },
+ {
+ "invalid userObjectClasses",
+ fields{
+ eventstore: eventstoreExpect(t),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ },
+ args{
+ ctx: authz.WithInstanceID(context.Background(), "instance1"),
+ provider: LDAPProvider{
+ Name: "name",
+ Servers: []string{"server"},
+ BindDN: "binddn",
+ BaseDN: "baseDN",
+ BindPassword: "password",
+ UserBase: "user",
+ },
+ },
+ res{
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-S1x905n", ""))
+ },
+ },
+ },
+ {
+ "invalid userFilters",
+ fields{
+ eventstore: eventstoreExpect(t),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ },
+ args{
+ ctx: authz.WithInstanceID(context.Background(), "instance1"),
+ provider: LDAPProvider{
+ Name: "name",
+ Servers: []string{"server"},
+ BindDN: "binddn",
+ BaseDN: "baseDN",
+ BindPassword: "password",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ },
+ },
+ res{
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-aAx905n", ""))
},
},
},
@@ -3812,24 +3836,24 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
instance.NewLDAPIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
- "host",
- "",
+ []string{"server"},
false,
"baseDN",
- "userObjectClass",
- "userUniqueAttribute",
- "admin",
+ "dn",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
+ "user",
+ []string{"object"},
+ []string{"filter"},
+ time.Second*30,
idp.LDAPAttributes{},
idp.Options{},
)),
},
- uniqueConstraintsFromEventConstraintWithInstanceID("instance1", idpconfig.NewAddIDPConfigNameUniqueConstraint("name", "instance1")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@@ -3838,13 +3862,16 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
- Admin: "admin",
- Password: "password",
+ Name: "name",
+ Servers: []string{"server"},
+ StartTLS: false,
+ BaseDN: "baseDN",
+ BindDN: "dn",
+ BindPassword: "password",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ UserFilters: []string{"filter"},
+ Timeout: time.Second * 30,
},
},
res: res{
@@ -3864,19 +3891,20 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
instance.NewLDAPIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
- "host",
- "port",
- true,
+ []string{"server"},
+ false,
"baseDN",
- "userObjectClass",
- "userUniqueAttribute",
- "admin",
+ "dn",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
+ "user",
+ []string{"object"},
+ []string{"filter"},
+ time.Second*30,
idp.LDAPAttributes{
IDAttribute: "id",
FirstNameAttribute: "firstName",
@@ -3900,7 +3928,6 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
},
)),
},
- uniqueConstraintsFromEventConstraintWithInstanceID("instance1", idpconfig.NewAddIDPConfigNameUniqueConstraint("name", "instance1")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@@ -3909,15 +3936,16 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- Port: "port",
- TLS: true,
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
- Admin: "admin",
- Password: "password",
+ Name: "name",
+ Servers: []string{"server"},
+ StartTLS: false,
+ BaseDN: "baseDN",
+ BindDN: "dn",
+ BindPassword: "password",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ UserFilters: []string{"filter"},
+ Timeout: time.Second * 30,
LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id",
FirstNameAttribute: "firstName",
@@ -4020,24 +4048,6 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
},
},
},
- {
- "invalid host",
- fields{
- eventstore: eventstoreExpect(t),
- },
- args{
- ctx: authz.WithInstanceID(context.Background(), "instance1"),
- id: "id1",
- provider: LDAPProvider{
- Name: "name",
- },
- },
- res{
- err: func(err error) bool {
- return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Dz62d", ""))
- },
- },
- },
{
"invalid baseDN",
fields{
@@ -4048,7 +4058,6 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
id: "id1",
provider: LDAPProvider{
Name: "name",
- Host: "host",
},
},
res{
@@ -4058,7 +4067,7 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
},
},
{
- "invalid userObjectClass",
+ "invalid bindDN",
fields{
eventstore: eventstoreExpect(t),
},
@@ -4067,7 +4076,6 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
id: "id1",
provider: LDAPProvider{
Name: "name",
- Host: "host",
BaseDN: "baseDN",
},
},
@@ -4078,7 +4086,7 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
},
},
{
- "invalid userUniqueAttribute",
+ "invalid userbase",
fields{
eventstore: eventstoreExpect(t),
},
@@ -4086,32 +4094,9 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- },
- },
- res{
- err: func(err error) bool {
- return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-ASFt6", ""))
- },
- },
- },
- {
- "invalid admin",
- fields{
- eventstore: eventstoreExpect(t),
- },
- args{
- ctx: authz.WithInstanceID(context.Background(), "instance1"),
- id: "id1",
- provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
+ Name: "name",
+ BaseDN: "baseDN",
+ BindDN: "bindDN",
},
},
res{
@@ -4120,6 +4105,72 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
},
},
},
+ {
+ "invalid servers",
+ fields{
+ eventstore: eventstoreExpect(t),
+ },
+ args{
+ ctx: authz.WithInstanceID(context.Background(), "instance1"),
+ id: "id1",
+ provider: LDAPProvider{
+ Name: "name",
+ BaseDN: "baseDN",
+ BindDN: "bindDN",
+ UserBase: "user",
+ },
+ },
+ res{
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-SAx945n", ""))
+ },
+ },
+ },
+ {
+ "invalid userObjectClasses",
+ fields{
+ eventstore: eventstoreExpect(t),
+ },
+ args{
+ ctx: authz.WithInstanceID(context.Background(), "instance1"),
+ id: "id1",
+ provider: LDAPProvider{
+ Name: "name",
+ Servers: []string{"server"},
+ BaseDN: "baseDN",
+ BindDN: "bindDN",
+ UserBase: "user",
+ },
+ },
+ res{
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-S1x605n", ""))
+ },
+ },
+ },
+ {
+ "invalid userFilters",
+ fields{
+ eventstore: eventstoreExpect(t),
+ },
+ args{
+ ctx: authz.WithInstanceID(context.Background(), "instance1"),
+ id: "id1",
+ provider: LDAPProvider{
+ Name: "name",
+ Servers: []string{"server"},
+ BaseDN: "baseDN",
+ BindDN: "bindDN",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ },
+ },
+ res{
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-aAx901n", ""))
+ },
+ },
+ },
{
name: "not found",
fields: fields{
@@ -4131,16 +4182,20 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
- Admin: "admin",
+ Name: "name",
+ Servers: []string{"server"},
+ BaseDN: "baseDN",
+ BindDN: "binddn",
+ BindPassword: "password",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ UserFilters: []string{"filter"},
},
},
res: res{
- err: caos_errors.IsNotFound,
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowNotFound(nil, "INST-ASF3F", ""))
+ },
},
},
{
@@ -4152,19 +4207,20 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
instance.NewLDAPIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
- "host",
- "",
+ []string{"server"},
false,
- "baseDN",
- "userObjectClass",
- "userUniqueAttribute",
- "admin",
+ "basedn",
+ "binddn",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
+ "user",
+ []string{"object"},
+ []string{"filter"},
+ time.Second*30,
idp.LDAPAttributes{},
idp.Options{},
)),
@@ -4175,12 +4231,15 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
- Admin: "admin",
+ Name: "name",
+ Servers: []string{"server"},
+ StartTLS: false,
+ BaseDN: "basedn",
+ BindDN: "binddn",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ UserFilters: []string{"filter"},
+ Timeout: time.Second * 30,
},
},
res: res{
@@ -4196,19 +4255,20 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
instance.NewLDAPIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
- "host",
- "port",
+ []string{"server"},
false,
- "baseDN",
- "userObjectClass",
- "userUniqueAttribute",
- "admin",
+ "basedn",
+ "binddn",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
+ "user",
+ []string{"object"},
+ []string{"filter"},
+ time.Second*30,
idp.LDAPAttributes{},
idp.Options{},
)),
@@ -4221,22 +4281,22 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
t := true
event, _ := instance.NewLDAPIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
- "name",
[]idp.LDAPIDPChanges{
idp.ChangeLDAPName("new name"),
- idp.ChangeLDAPHost("new host"),
- idp.ChangeLDAPPort("new port"),
- idp.ChangeLDAPTLS(true),
- idp.ChangeLDAPBaseDN("new baseDN"),
- idp.ChangeLDAPUserObjectClass("new userObjectClass"),
- idp.ChangeLDAPUserUniqueAttribute("new userUniqueAttribute"),
- idp.ChangeLDAPAdmin("new admin"),
- idp.ChangeLDAPPassword(&crypto.CryptoValue{
+ idp.ChangeLDAPServers([]string{"new server"}),
+ idp.ChangeLDAPStartTLS(true),
+ idp.ChangeLDAPBaseDN("new basedn"),
+ idp.ChangeLDAPBindDN("new binddn"),
+ idp.ChangeLDAPBindPassword(&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("new password"),
}),
+ idp.ChangeLDAPUserBase("new user"),
+ idp.ChangeLDAPUserObjectClasses([]string{"new object"}),
+ idp.ChangeLDAPUserFilters([]string{"new filter"}),
+ idp.ChangeLDAPTimeout(time.Second * 20),
idp.ChangeLDAPAttributes(idp.LDAPAttributeChanges{
IDAttribute: stringPointer("new id"),
FirstNameAttribute: stringPointer("new firstName"),
@@ -4264,8 +4324,6 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
}(),
),
},
- uniqueConstraintsFromEventConstraintWithInstanceID("instance1", idpconfig.NewRemoveIDPConfigNameUniqueConstraint("name", "instance1")),
- uniqueConstraintsFromEventConstraintWithInstanceID("instance1", idpconfig.NewAddIDPConfigNameUniqueConstraint("new name", "instance1")),
),
),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
@@ -4274,15 +4332,16 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: LDAPProvider{
- Name: "new name",
- Host: "new host",
- Port: "new port",
- TLS: true,
- BaseDN: "new baseDN",
- UserObjectClass: "new userObjectClass",
- UserUniqueAttribute: "new userUniqueAttribute",
- Admin: "new admin",
- Password: "new password",
+ Name: "new name",
+ Servers: []string{"new server"},
+ StartTLS: true,
+ BaseDN: "new basedn",
+ BindDN: "new binddn",
+ BindPassword: "new password",
+ UserBase: "new user",
+ UserObjectClasses: []string{"new object"},
+ UserFilters: []string{"new filter"},
+ Timeout: time.Second * 20,
LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "new id",
FirstNameAttribute: "new firstName",
diff --git a/internal/command/org_idp.go b/internal/command/org_idp.go
index d9872be198..e6e22c4473 100644
--- a/internal/command/org_idp.go
+++ b/internal/command/org_idp.go
@@ -1268,23 +1268,26 @@ func (c *Commands) prepareAddOrgLDAPProvider(a *org.Aggregate, writeModel *OrgLD
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SAfdd", "Errors.Invalid.Argument")
}
- if provider.Host = strings.TrimSpace(provider.Host); provider.Host == "" {
- return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SDVg2", "Errors.Invalid.Argument")
- }
if provider.BaseDN = strings.TrimSpace(provider.BaseDN); provider.BaseDN == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-sv31s", "Errors.Invalid.Argument")
}
- if provider.UserObjectClass = strings.TrimSpace(provider.UserObjectClass); provider.UserObjectClass == "" {
+ if provider.BindDN = strings.TrimSpace(provider.BindDN); provider.BindDN == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-sdgf4", "Errors.Invalid.Argument")
}
- if provider.UserUniqueAttribute = strings.TrimSpace(provider.UserUniqueAttribute); provider.UserUniqueAttribute == "" {
+ if provider.BindPassword = strings.TrimSpace(provider.BindPassword); provider.BindPassword == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-AEG2w", "Errors.Invalid.Argument")
}
- if provider.Admin = strings.TrimSpace(provider.Admin); provider.Admin == "" {
+ if provider.UserBase = strings.TrimSpace(provider.UserBase); provider.UserBase == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SAD5n", "Errors.Invalid.Argument")
}
- if provider.Password = strings.TrimSpace(provider.Password); provider.Password == "" {
- return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-sdf5h", "Errors.Invalid.Argument")
+ if len(provider.Servers) == 0 {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SAy945n", "Errors.Invalid.Argument")
+ }
+ if len(provider.UserObjectClasses) == 0 {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-S1x705n", "Errors.Invalid.Argument")
+ }
+ if len(provider.UserFilters) == 0 {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-aAx9x1n", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
@@ -1295,7 +1298,7 @@ func (c *Commands) prepareAddOrgLDAPProvider(a *org.Aggregate, writeModel *OrgLD
if err = writeModel.Reduce(); err != nil {
return nil, err
}
- secret, err := crypto.Encrypt([]byte(provider.Password), c.idpConfigEncryption)
+ secret, err := crypto.Encrypt([]byte(provider.BindPassword), c.idpConfigEncryption)
if err != nil {
return nil, err
}
@@ -1305,14 +1308,15 @@ func (c *Commands) prepareAddOrgLDAPProvider(a *org.Aggregate, writeModel *OrgLD
&a.Aggregate,
writeModel.ID,
provider.Name,
- provider.Host,
- provider.Port,
- provider.TLS,
+ provider.Servers,
+ provider.StartTLS,
provider.BaseDN,
- provider.UserObjectClass,
- provider.UserUniqueAttribute,
- provider.Admin,
+ provider.BindDN,
secret,
+ provider.UserBase,
+ provider.UserObjectClasses,
+ provider.UserFilters,
+ provider.Timeout,
provider.LDAPAttributes,
provider.IDPOptions,
),
@@ -1329,21 +1333,24 @@ func (c *Commands) prepareUpdateOrgLDAPProvider(a *org.Aggregate, writeModel *Or
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Sffgd", "Errors.Invalid.Argument")
}
- if provider.Host = strings.TrimSpace(provider.Host); provider.Host == "" {
- return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Dz62d", "Errors.Invalid.Argument")
- }
if provider.BaseDN = strings.TrimSpace(provider.BaseDN); provider.BaseDN == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-vb3ss", "Errors.Invalid.Argument")
}
- if provider.UserObjectClass = strings.TrimSpace(provider.UserObjectClass); provider.UserObjectClass == "" {
+ if provider.BindDN = strings.TrimSpace(provider.BindDN); provider.BindDN == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-hbere", "Errors.Invalid.Argument")
}
- if provider.UserUniqueAttribute = strings.TrimSpace(provider.UserUniqueAttribute); provider.UserUniqueAttribute == "" {
- return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-ASFt6", "Errors.Invalid.Argument")
- }
- if provider.Admin = strings.TrimSpace(provider.Admin); provider.Admin == "" {
+ if provider.UserBase = strings.TrimSpace(provider.UserBase); provider.UserBase == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-DG45z", "Errors.Invalid.Argument")
}
+ if len(provider.Servers) == 0 {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Sxx945n", "Errors.Invalid.Argument")
+ }
+ if len(provider.UserObjectClasses) == 0 {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-S1p605n", "Errors.Invalid.Argument")
+ }
+ if len(provider.UserFilters) == 0 {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-aBx901n", "Errors.Invalid.Argument")
+ }
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
if err != nil {
@@ -1360,16 +1367,16 @@ func (c *Commands) prepareUpdateOrgLDAPProvider(a *org.Aggregate, writeModel *Or
ctx,
&a.Aggregate,
writeModel.ID,
- writeModel.Name,
provider.Name,
- provider.Host,
- provider.Port,
- provider.TLS,
+ provider.Servers,
+ provider.StartTLS,
provider.BaseDN,
- provider.UserObjectClass,
- provider.UserUniqueAttribute,
- provider.Admin,
- provider.Password,
+ provider.BindDN,
+ provider.BindPassword,
+ provider.UserBase,
+ provider.UserObjectClasses,
+ provider.UserFilters,
+ provider.Timeout,
c.idpConfigEncryption,
provider.LDAPAttributes,
provider.IDPOptions,
diff --git a/internal/command/org_idp_model.go b/internal/command/org_idp_model.go
index 379a7eac66..ca8011121c 100644
--- a/internal/command/org_idp_model.go
+++ b/internal/command/org_idp_model.go
@@ -2,6 +2,7 @@ package command
import (
"context"
+ "time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -754,16 +755,16 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
- oldName,
- name,
- host,
- port string,
- tls bool,
- baseDN,
- userObjectClass,
- userUniqueAttribute,
- admin string,
- password string,
+ name string,
+ servers []string,
+ startTLS bool,
+ baseDN string,
+ bindDN string,
+ bindPassword string,
+ userBase string,
+ userObjectClasses []string,
+ userFilters []string,
+ timeout time.Duration,
secretCrypto crypto.Crypto,
attributes idp.LDAPAttributes,
options idp.Options,
@@ -771,14 +772,15 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
changes, err := wm.LDAPIDPWriteModel.NewChanges(
name,
- host,
- port,
- tls,
+ servers,
+ startTLS,
baseDN,
- userObjectClass,
- userUniqueAttribute,
- admin,
- password,
+ bindDN,
+ bindPassword,
+ userBase,
+ userObjectClasses,
+ userFilters,
+ timeout,
secretCrypto,
attributes,
options,
@@ -786,7 +788,7 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
if err != nil || len(changes) == 0 {
return nil, err
}
- return org.NewLDAPIDPChangedEvent(ctx, aggregate, id, oldName, changes)
+ return org.NewLDAPIDPChangedEvent(ctx, aggregate, id, changes)
}
type OrgIDPRemoveWriteModel struct {
diff --git a/internal/command/org_idp_test.go b/internal/command/org_idp_test.go
index 83d95025a4..7110cdd1dd 100644
--- a/internal/command/org_idp_test.go
+++ b/internal/command/org_idp_test.go
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"testing"
+ "time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
@@ -17,7 +18,6 @@ import (
"github.com/zitadel/zitadel/internal/id"
id_mock "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/idp"
- "github.com/zitadel/zitadel/internal/repository/idpconfig"
"github.com/zitadel/zitadel/internal/repository/org"
)
@@ -3734,25 +3734,6 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
},
},
},
- {
- "invalid host",
- fields{
- eventstore: eventstoreExpect(t),
- idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
- },
- args{
- ctx: context.Background(),
- resourceOwner: "org1",
- provider: LDAPProvider{
- Name: "name",
- },
- },
- res{
- err: func(err error) bool {
- return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-SDVg2", ""))
- },
- },
- },
{
"invalid baseDN",
fields{
@@ -3764,7 +3745,6 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
resourceOwner: "org1",
provider: LDAPProvider{
Name: "name",
- Host: "host",
},
},
res{
@@ -3774,7 +3754,7 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
},
},
{
- "invalid userObjectClass",
+ "invalid binddn",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@@ -3784,7 +3764,6 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
resourceOwner: "org1",
provider: LDAPProvider{
Name: "name",
- Host: "host",
BaseDN: "baseDN",
},
},
@@ -3794,51 +3773,6 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
},
},
},
- {
- "invalid userUniqueAttribute",
- fields{
- eventstore: eventstoreExpect(t),
- idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
- },
- args{
- ctx: context.Background(),
- resourceOwner: "org1",
- provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- },
- },
- res{
- err: func(err error) bool {
- return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-AEG2w", ""))
- },
- },
- },
- {
- "invalid admin",
- fields{
- eventstore: eventstoreExpect(t),
- idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
- },
- args{
- ctx: context.Background(),
- resourceOwner: "org1",
- provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
- },
- },
- res{
- err: func(err error) bool {
- return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-SAD5n", ""))
- },
- },
- },
{
"invalid password",
fields{
@@ -3849,17 +3783,108 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
ctx: context.Background(),
resourceOwner: "org1",
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
- Admin: "admin",
+ Name: "name",
+ BindDN: "binddn",
+ BaseDN: "baseDN",
},
},
res{
err: func(err error) bool {
- return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-sdf5h", ""))
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-AEG2w", ""))
+ },
+ },
+ },
+ {
+ "invalid userbase",
+ fields{
+ eventstore: eventstoreExpect(t),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ },
+ args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ provider: LDAPProvider{
+ Name: "name",
+ BindDN: "binddn",
+ BaseDN: "baseDN",
+ BindPassword: "password",
+ },
+ },
+ res{
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-SAD5n", ""))
+ },
+ },
+ },
+ {
+ "invalid servers",
+ fields{
+ eventstore: eventstoreExpect(t),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ },
+ args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ provider: LDAPProvider{
+ Name: "name",
+ BindDN: "binddn",
+ BaseDN: "baseDN",
+ BindPassword: "password",
+ UserBase: "user",
+ },
+ },
+ res{
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-SAy945n", ""))
+ },
+ },
+ },
+ {
+ "invalid userObjectClasses",
+ fields{
+ eventstore: eventstoreExpect(t),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ },
+ args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ provider: LDAPProvider{
+ Name: "name",
+ Servers: []string{"server"},
+ BindDN: "binddn",
+ BaseDN: "baseDN",
+ BindPassword: "password",
+ UserBase: "user",
+ },
+ },
+ res{
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-S1x705n", ""))
+ },
+ },
+ },
+ {
+ "invalid userFilters",
+ fields{
+ eventstore: eventstoreExpect(t),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ },
+ args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ provider: LDAPProvider{
+ Name: "name",
+ Servers: []string{"server"},
+ BindDN: "binddn",
+ BaseDN: "baseDN",
+ BindPassword: "password",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ },
+ },
+ res{
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-aAx9x1n", ""))
},
},
},
@@ -3873,23 +3898,23 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
org.NewLDAPIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
- "host",
- "",
+ []string{"server"},
false,
"baseDN",
- "userObjectClass",
- "userUniqueAttribute",
- "admin",
+ "dn",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
+ "user",
+ []string{"object"},
+ []string{"filter"},
+ time.Second*30,
idp.LDAPAttributes{},
idp.Options{},
)),
- uniqueConstraintsFromEventConstraint(idpconfig.NewAddIDPConfigNameUniqueConstraint("name", "org1")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@@ -3899,13 +3924,16 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
ctx: context.Background(),
resourceOwner: "org1",
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
- Admin: "admin",
- Password: "password",
+ Name: "name",
+ Servers: []string{"server"},
+ StartTLS: false,
+ BaseDN: "baseDN",
+ BindDN: "dn",
+ BindPassword: "password",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ UserFilters: []string{"filter"},
+ Timeout: time.Second * 30,
},
},
res: res{
@@ -3923,19 +3951,20 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
org.NewLDAPIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
- "host",
- "port",
- true,
+ []string{"server"},
+ false,
"baseDN",
- "userObjectClass",
- "userUniqueAttribute",
- "admin",
+ "dn",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
+ "user",
+ []string{"object"},
+ []string{"filter"},
+ time.Second*30,
idp.LDAPAttributes{
IDAttribute: "id",
FirstNameAttribute: "firstName",
@@ -3958,7 +3987,6 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
IsAutoUpdate: true,
},
)),
- uniqueConstraintsFromEventConstraint(idpconfig.NewAddIDPConfigNameUniqueConstraint("name", "org1")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@@ -3968,15 +3996,16 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
ctx: context.Background(),
resourceOwner: "org1",
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- Port: "port",
- TLS: true,
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
- Admin: "admin",
- Password: "password",
+ Name: "name",
+ Servers: []string{"server"},
+ StartTLS: false,
+ BaseDN: "baseDN",
+ BindDN: "dn",
+ BindPassword: "password",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ UserFilters: []string{"filter"},
+ Timeout: time.Second * 30,
LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id",
FirstNameAttribute: "firstName",
@@ -4082,25 +4111,6 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
},
},
},
- {
- "invalid host",
- fields{
- eventstore: eventstoreExpect(t),
- },
- args{
- ctx: context.Background(),
- resourceOwner: "org1",
- id: "id1",
- provider: LDAPProvider{
- Name: "name",
- },
- },
- res{
- err: func(err error) bool {
- return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-Dz62d", ""))
- },
- },
- },
{
"invalid baseDN",
fields{
@@ -4112,7 +4122,6 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
id: "id1",
provider: LDAPProvider{
Name: "name",
- Host: "host",
},
},
res{
@@ -4122,7 +4131,7 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
},
},
{
- "invalid userObjectClass",
+ "invalid binddn",
fields{
eventstore: eventstoreExpect(t),
},
@@ -4132,7 +4141,6 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
id: "id1",
provider: LDAPProvider{
Name: "name",
- Host: "host",
BaseDN: "baseDN",
},
},
@@ -4143,7 +4151,7 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
},
},
{
- "invalid userUniqueAttribute",
+ "invalid userbase",
fields{
eventstore: eventstoreExpect(t),
},
@@ -4152,33 +4160,9 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
resourceOwner: "org1",
id: "id1",
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- },
- },
- res{
- err: func(err error) bool {
- return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-ASFt6", ""))
- },
- },
- },
- {
- "invalid admin",
- fields{
- eventstore: eventstoreExpect(t),
- },
- args{
- ctx: context.Background(),
- resourceOwner: "org1",
- id: "id1",
- provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
+ Name: "name",
+ BaseDN: "baseDN",
+ BindDN: "bindDN",
},
},
res{
@@ -4187,6 +4171,75 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
},
},
},
+ {
+ "invalid servers",
+ fields{
+ eventstore: eventstoreExpect(t),
+ },
+ args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ id: "id1",
+ provider: LDAPProvider{
+ Name: "name",
+ BaseDN: "baseDN",
+ BindDN: "bindDN",
+ UserBase: "user",
+ },
+ },
+ res{
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-Sxx945n", ""))
+ },
+ },
+ },
+ {
+ "invalid userObjectClasses",
+ fields{
+ eventstore: eventstoreExpect(t),
+ },
+ args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ id: "id1",
+ provider: LDAPProvider{
+ Name: "name",
+ Servers: []string{"server"},
+ BaseDN: "baseDN",
+ BindDN: "bindDN",
+ UserBase: "user",
+ },
+ },
+ res{
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-S1p605n", ""))
+ },
+ },
+ },
+ {
+ "invalid userFilters",
+ fields{
+ eventstore: eventstoreExpect(t),
+ },
+ args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ id: "id1",
+ provider: LDAPProvider{
+ Name: "name",
+ Servers: []string{"server"},
+ BaseDN: "baseDN",
+ BindDN: "bindDN",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ },
+ },
+ res{
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-aBx901n", ""))
+ },
+ },
+ },
{
name: "not found",
fields: fields{
@@ -4199,16 +4252,20 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
resourceOwner: "org1",
id: "id1",
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
- Admin: "admin",
+ Name: "name",
+ Servers: []string{"server"},
+ BaseDN: "baseDN",
+ BindDN: "binddn",
+ BindPassword: "password",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ UserFilters: []string{"filter"},
},
},
res: res{
- err: caos_errors.IsNotFound,
+ err: func(err error) bool {
+ return errors.Is(err, caos_errors.ThrowNotFound(nil, "ORG-ASF3F", ""))
+ },
},
},
{
@@ -4220,19 +4277,20 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
org.NewLDAPIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
- "host",
- "",
+ []string{"server"},
false,
- "baseDN",
- "userObjectClass",
- "userUniqueAttribute",
- "admin",
+ "basedn",
+ "binddn",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
+ "user",
+ []string{"object"},
+ []string{"filter"},
+ time.Second*30,
idp.LDAPAttributes{},
idp.Options{},
)),
@@ -4244,12 +4302,14 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
resourceOwner: "org1",
id: "id1",
provider: LDAPProvider{
- Name: "name",
- Host: "host",
- BaseDN: "baseDN",
- UserObjectClass: "userObjectClass",
- UserUniqueAttribute: "userUniqueAttribute",
- Admin: "admin",
+ Name: "name",
+ Servers: []string{"server"},
+ BaseDN: "basedn",
+ BindDN: "binddn",
+ UserObjectClasses: []string{"object"},
+ UserFilters: []string{"filter"},
+ UserBase: "user",
+ Timeout: time.Second * 30,
},
},
res: res{
@@ -4265,19 +4325,20 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
org.NewLDAPIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
- "host",
- "port",
+ []string{"server"},
false,
- "baseDN",
- "userObjectClass",
- "userUniqueAttribute",
- "admin",
+ "basedn",
+ "binddn",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
+ "user",
+ []string{"object"},
+ []string{"filter"},
+ time.Second*30,
idp.LDAPAttributes{},
idp.Options{},
)),
@@ -4288,22 +4349,22 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
t := true
event, _ := org.NewLDAPIDPChangedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
- "name",
[]idp.LDAPIDPChanges{
idp.ChangeLDAPName("new name"),
- idp.ChangeLDAPHost("new host"),
- idp.ChangeLDAPPort("new port"),
- idp.ChangeLDAPTLS(true),
- idp.ChangeLDAPBaseDN("new baseDN"),
- idp.ChangeLDAPUserObjectClass("new userObjectClass"),
- idp.ChangeLDAPUserUniqueAttribute("new userUniqueAttribute"),
- idp.ChangeLDAPAdmin("new admin"),
- idp.ChangeLDAPPassword(&crypto.CryptoValue{
+ idp.ChangeLDAPServers([]string{"new server"}),
+ idp.ChangeLDAPStartTLS(true),
+ idp.ChangeLDAPBaseDN("new basedn"),
+ idp.ChangeLDAPBindDN("new binddn"),
+ idp.ChangeLDAPBindPassword(&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("new password"),
}),
+ idp.ChangeLDAPUserBase("new user"),
+ idp.ChangeLDAPUserObjectClasses([]string{"new object"}),
+ idp.ChangeLDAPUserFilters([]string{"new filter"}),
+ idp.ChangeLDAPTimeout(time.Second * 20),
idp.ChangeLDAPAttributes(idp.LDAPAttributeChanges{
IDAttribute: stringPointer("new id"),
FirstNameAttribute: stringPointer("new firstName"),
@@ -4330,8 +4391,6 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
return event
}(),
),
- uniqueConstraintsFromEventConstraint(idpconfig.NewRemoveIDPConfigNameUniqueConstraint("name", "org1")),
- uniqueConstraintsFromEventConstraint(idpconfig.NewAddIDPConfigNameUniqueConstraint("new name", "org1")),
),
),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
@@ -4341,15 +4400,16 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
resourceOwner: "org1",
id: "id1",
provider: LDAPProvider{
- Name: "new name",
- Host: "new host",
- Port: "new port",
- TLS: true,
- BaseDN: "new baseDN",
- UserObjectClass: "new userObjectClass",
- UserUniqueAttribute: "new userUniqueAttribute",
- Admin: "new admin",
- Password: "new password",
+ Name: "new name",
+ Servers: []string{"new server"},
+ StartTLS: true,
+ BaseDN: "new basedn",
+ BindDN: "new binddn",
+ BindPassword: "new password",
+ UserBase: "new user",
+ UserObjectClasses: []string{"new object"},
+ UserFilters: []string{"new filter"},
+ Timeout: time.Second * 20,
LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "new id",
FirstNameAttribute: "new firstName",
diff --git a/internal/idp/providers/ldap/ldap.go b/internal/idp/providers/ldap/ldap.go
index 5fc14f60d3..d2c950b29b 100644
--- a/internal/idp/providers/ldap/ldap.go
+++ b/internal/idp/providers/ldap/ldap.go
@@ -2,6 +2,7 @@ package ldap
import (
"context"
+ "time"
"github.com/zitadel/zitadel/internal/idp"
)
@@ -12,16 +13,18 @@ var _ idp.Provider = (*Provider)(nil)
// Provider is the [idp.Provider] implementation for a generic LDAP provider
type Provider struct {
- name string
- host string
- port string
- tls bool
- baseDN string
- userObjectClass string
- userUniqueAttribute string
- admin string
- password string
- loginUrl string
+ name string
+ servers []string
+ startTLS bool
+ baseDN string
+ bindDN string
+ bindPassword string
+ userBase string
+ userObjectClasses []string
+ userFilters []string
+ timeout time.Duration
+
+ loginUrl string
isLinkingAllowed bool
isCreationAllowed bool
@@ -74,17 +77,10 @@ func WithAutoUpdate() ProviderOpts {
}
}
-// WithCustomPort configures a custom port used for the communication instead of :389 as per default
-func WithCustomPort(port string) ProviderOpts {
+// WithoutStartTLS configures to communication insecure with the LDAP server without startTLS
+func WithoutStartTLS() ProviderOpts {
return func(p *Provider) {
- p.port = port
- }
-}
-
-// Insecure configures to communication insecure with the LDAP server without TLS
-func Insecure() ProviderOpts {
- return func(p *Provider) {
- p.tls = false
+ p.startTLS = false
}
}
@@ -181,27 +177,29 @@ func WithProfileAttribute(name string) ProviderOpts {
func New(
name string,
- host string,
+ servers []string,
baseDN string,
- userObjectClass string,
- userUniqueAttribute string,
- admin string,
- password string,
+ bindDN string,
+ bindPassword string,
+ userBase string,
+ userObjectClasses []string,
+ userFilters []string,
+ timeout time.Duration,
loginUrl string,
options ...ProviderOpts,
) *Provider {
provider := &Provider{
- name: name,
- host: host,
- port: DefaultPort,
- tls: true,
- baseDN: baseDN,
- userObjectClass: userObjectClass,
- userUniqueAttribute: userUniqueAttribute,
- admin: admin,
- password: password,
- loginUrl: loginUrl,
- idAttribute: userUniqueAttribute,
+ name: name,
+ servers: servers,
+ startTLS: true,
+ baseDN: baseDN,
+ bindDN: bindDN,
+ bindPassword: bindPassword,
+ userBase: userBase,
+ userObjectClasses: userObjectClasses,
+ userFilters: userFilters,
+ timeout: timeout,
+ loginUrl: loginUrl,
}
for _, option := range options {
option(provider)
@@ -216,7 +214,7 @@ func (p *Provider) Name() string {
func (p *Provider) BeginAuth(ctx context.Context, state string, params ...any) (idp.Session, error) {
return &Session{
Provider: p,
- loginUrl: p.loginUrl + "?state=" + state,
+ loginUrl: p.loginUrl + state,
}, nil
}
@@ -235,3 +233,47 @@ func (p *Provider) IsAutoCreation() bool {
func (p *Provider) IsAutoUpdate() bool {
return p.isAutoUpdate
}
+
+func (p *Provider) getNecessaryAttributes() []string {
+ attributes := []string{p.userBase}
+ if p.idAttribute != "" {
+ attributes = append(attributes, p.idAttribute)
+ }
+ if p.firstNameAttribute != "" {
+ attributes = append(attributes, p.firstNameAttribute)
+ }
+ if p.lastNameAttribute != "" {
+ attributes = append(attributes, p.lastNameAttribute)
+ }
+ if p.displayNameAttribute != "" {
+ attributes = append(attributes, p.displayNameAttribute)
+ }
+ if p.nickNameAttribute != "" {
+ attributes = append(attributes, p.nickNameAttribute)
+ }
+ if p.preferredUsernameAttribute != "" {
+ attributes = append(attributes, p.preferredUsernameAttribute)
+ }
+ if p.emailAttribute != "" {
+ attributes = append(attributes, p.emailAttribute)
+ }
+ if p.emailVerifiedAttribute != "" {
+ attributes = append(attributes, p.emailVerifiedAttribute)
+ }
+ if p.phoneAttribute != "" {
+ attributes = append(attributes, p.phoneAttribute)
+ }
+ if p.phoneVerifiedAttribute != "" {
+ attributes = append(attributes, p.phoneVerifiedAttribute)
+ }
+ if p.preferredLanguageAttribute != "" {
+ attributes = append(attributes, p.preferredLanguageAttribute)
+ }
+ if p.avatarURLAttribute != "" {
+ attributes = append(attributes, p.avatarURLAttribute)
+ }
+ if p.profileAttribute != "" {
+ attributes = append(attributes, p.profileAttribute)
+ }
+ return attributes
+}
diff --git a/internal/idp/providers/ldap/ldap_test.go b/internal/idp/providers/ldap/ldap_test.go
index 6ec2790c96..d00680da1a 100644
--- a/internal/idp/providers/ldap/ldap_test.go
+++ b/internal/idp/providers/ldap/ldap_test.go
@@ -2,26 +2,28 @@ package ldap
import (
"testing"
+ "time"
"github.com/stretchr/testify/assert"
)
func TestProvider_Options(t *testing.T) {
type fields struct {
- name string
- host string
- baseDN string
- userObjectClass string
- userUniqueAttribute string
- admin string
- password string
- loginUrl string
- opts []ProviderOpts
+ name string
+ servers []string
+ baseDN string
+ bindDN string
+ bindPassword string
+ userBase string
+ userObjectClasses []string
+ userFilters []string
+ timeout time.Duration
+ loginUrl string
+ opts []ProviderOpts
}
type want struct {
name string
- port string
- tls bool
+ startTls bool
linkingAllowed bool
creationAllowed bool
autoCreation bool
@@ -48,39 +50,43 @@ func TestProvider_Options(t *testing.T) {
{
name: "default",
fields: fields{
- name: "ldap",
- host: "host",
- baseDN: "base",
- userObjectClass: "class",
- userUniqueAttribute: "attr",
- admin: "admin",
- password: "password",
- loginUrl: "url",
- opts: nil,
+ name: "ldap",
+ servers: []string{"server"},
+ baseDN: "base",
+ bindDN: "binddn",
+ bindPassword: "password",
+ userBase: "user",
+ userObjectClasses: []string{"object"},
+ userFilters: []string{"filter"},
+ timeout: 30 * time.Second,
+ loginUrl: "url",
+ opts: nil,
},
want: want{
name: "ldap",
- port: DefaultPort,
- tls: true,
+ startTls: true,
linkingAllowed: false,
creationAllowed: false,
autoCreation: false,
autoUpdate: false,
- idAttribute: "attr",
+ idAttribute: "",
},
},
{
name: "all true",
fields: fields{
- name: "ldap",
- host: "host",
- baseDN: "base",
- userObjectClass: "class",
- userUniqueAttribute: "attr",
- admin: "admin",
- password: "password",
- loginUrl: "url",
+ name: "ldap",
+ servers: []string{"server"},
+ baseDN: "base",
+ bindDN: "binddn",
+ bindPassword: "password",
+ userBase: "user",
+ userObjectClasses: []string{"object"},
+ userFilters: []string{"filter"},
+ timeout: 30 * time.Second,
+ loginUrl: "url",
opts: []ProviderOpts{
+ WithoutStartTLS(),
WithLinkingAllowed(),
WithCreationAllowed(),
WithAutoCreation(),
@@ -89,28 +95,28 @@ func TestProvider_Options(t *testing.T) {
},
want: want{
name: "ldap",
- port: DefaultPort,
- tls: true,
+ startTls: false,
linkingAllowed: true,
creationAllowed: true,
autoCreation: true,
autoUpdate: true,
- idAttribute: "attr",
+ idAttribute: "",
},
}, {
name: "all true, attributes set",
fields: fields{
- name: "ldap",
- host: "host",
- baseDN: "base",
- userObjectClass: "class",
- userUniqueAttribute: "attr",
- admin: "admin",
- password: "password",
- loginUrl: "url",
+ name: "ldap",
+ servers: []string{"server"},
+ baseDN: "base",
+ bindDN: "binddn",
+ bindPassword: "password",
+ userBase: "user",
+ userObjectClasses: []string{"object"},
+ userFilters: []string{"filter"},
+ timeout: 30 * time.Second,
+ loginUrl: "url",
opts: []ProviderOpts{
- Insecure(),
- WithCustomPort("port"),
+ WithoutStartTLS(),
WithLinkingAllowed(),
WithCreationAllowed(),
WithAutoCreation(),
@@ -132,8 +138,7 @@ func TestProvider_Options(t *testing.T) {
},
want: want{
name: "ldap",
- port: "port",
- tls: false,
+ startTls: false,
linkingAllowed: true,
creationAllowed: true,
autoCreation: true,
@@ -157,11 +162,22 @@ func TestProvider_Options(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := assert.New(t)
- provider := New(tt.fields.name, tt.fields.host, tt.fields.baseDN, tt.fields.userObjectClass, tt.fields.userUniqueAttribute, tt.fields.admin, tt.fields.password, tt.fields.loginUrl, tt.fields.opts...)
+ provider := New(
+ tt.fields.name,
+ tt.fields.servers,
+ tt.fields.baseDN,
+ tt.fields.bindDN,
+ tt.fields.bindPassword,
+ tt.fields.userBase,
+ tt.fields.userObjectClasses,
+ tt.fields.userFilters,
+ tt.fields.timeout,
+ tt.fields.loginUrl,
+ tt.fields.opts...,
+ )
a.Equal(tt.want.name, provider.Name())
- a.Equal(tt.want.port, provider.port)
- a.Equal(tt.want.tls, provider.tls)
+ a.Equal(tt.want.startTls, provider.startTLS)
a.Equal(tt.want.linkingAllowed, provider.IsLinkingAllowed())
a.Equal(tt.want.creationAllowed, provider.IsCreationAllowed())
a.Equal(tt.want.autoCreation, provider.IsAutoCreation())
diff --git a/internal/idp/providers/ldap/session.go b/internal/idp/providers/ldap/session.go
index 638f4f46e4..46bc06573e 100644
--- a/internal/idp/providers/ldap/session.go
+++ b/internal/idp/providers/ldap/session.go
@@ -4,8 +4,10 @@ import (
"context"
"crypto/tls"
"errors"
- "fmt"
+ "net"
+ "net/url"
"strconv"
+ "time"
"github.com/go-ldap/ldap/v3"
"golang.org/x/text/language"
@@ -15,49 +17,154 @@ import (
)
var ErrNoSingleUser = errors.New("user does not exist or too many entries returned")
+var ErrFailedLogin = errors.New("user failed to login")
var _ idp.Session = (*Session)(nil)
type Session struct {
Provider *Provider
loginUrl string
- user string
- password string
+ User string
+ Password string
}
func (s *Session) GetAuthURL() string {
return s.loginUrl
}
-func (s *Session) FetchUser(_ context.Context) (idp.User, error) {
- l, err := ldap.DialURL("ldap://" + s.Provider.host + ":" + s.Provider.port)
+
+func (s *Session) FetchUser(_ context.Context) (_ idp.User, err error) {
+ var user *ldap.Entry
+ for _, server := range s.Provider.servers {
+ user, err = tryBind(server,
+ s.Provider.startTLS,
+ s.Provider.bindDN,
+ s.Provider.bindPassword,
+ s.Provider.baseDN,
+ s.Provider.getNecessaryAttributes(),
+ s.Provider.userObjectClasses,
+ s.Provider.userFilters,
+ s.User,
+ s.Password, s.Provider.timeout)
+ // If there were invalid credentials or multiple users with the credentials cancel process
+ if err != nil && (errors.Is(err, ErrFailedLogin) || errors.Is(err, ErrNoSingleUser)) {
+ return nil, err
+ }
+ // If a user bind was successful and user is filled continue with login, otherwise try next server
+ if err == nil && user != nil {
+ break
+ }
+ }
if err != nil {
return nil, err
}
- defer l.Close()
- if s.Provider.tls {
- err = l.StartTLS(&tls.Config{ServerName: s.Provider.host})
+ return mapLDAPEntryToUser(
+ user,
+ s.Provider.idAttribute,
+ s.Provider.firstNameAttribute,
+ s.Provider.lastNameAttribute,
+ s.Provider.displayNameAttribute,
+ s.Provider.nickNameAttribute,
+ s.Provider.preferredUsernameAttribute,
+ s.Provider.emailAttribute,
+ s.Provider.emailVerifiedAttribute,
+ s.Provider.phoneAttribute,
+ s.Provider.phoneVerifiedAttribute,
+ s.Provider.preferredLanguageAttribute,
+ s.Provider.avatarURLAttribute,
+ s.Provider.profileAttribute,
+ )
+}
+
+func tryBind(
+ server string,
+ startTLS bool,
+ bindDN string,
+ bindPassword string,
+ baseDN string,
+ attributes []string,
+ objectClasses []string,
+ userFilters []string,
+ username string,
+ password string,
+ timeout time.Duration,
+) (*ldap.Entry, error) {
+ conn, err := getConnection(server, startTLS, timeout)
+ if err != nil {
+ return nil, err
+ }
+ defer conn.Close()
+
+ if err := conn.Bind(bindDN, bindPassword); err != nil {
+ return nil, err
+ }
+
+ return trySearchAndUserBind(
+ conn,
+ baseDN,
+ attributes,
+ objectClasses,
+ userFilters,
+ username,
+ password,
+ timeout,
+ )
+}
+
+func getConnection(
+ server string,
+ startTLS bool,
+ timeout time.Duration,
+) (*ldap.Conn, error) {
+ if timeout == 0 {
+ timeout = ldap.DefaultTimeout
+ }
+
+ conn, err := ldap.DialURL(server, ldap.DialWithDialer(&net.Dialer{Timeout: timeout}))
+ if err != nil {
+ return nil, err
+ }
+
+ u, err := url.Parse(server)
+ if err != nil {
+ return nil, err
+ }
+ if u.Scheme == "ldaps" && startTLS {
+ err = conn.StartTLS(&tls.Config{ServerName: u.Host})
if err != nil {
return nil, err
}
}
+ return conn, nil
+}
- // Bind as the admin to search for user
- err = l.Bind("cn="+s.Provider.admin+","+s.Provider.baseDN, s.Provider.password)
- if err != nil {
- return nil, err
- }
+func trySearchAndUserBind(
+ conn *ldap.Conn,
+ baseDN string,
+ attributes []string,
+ objectClasses []string,
+ userFilters []string,
+ username string,
+ password string,
+ timeout time.Duration,
+) (*ldap.Entry, error) {
+ searchQuery := queriesAndToSearchQuery(
+ objectClassesToSearchQuery(objectClasses),
+ queriesOrToSearchQuery(
+ userFiltersToSearchQuery(userFilters, username),
+ ),
+ )
// Search for user with the unique attribute for the userDN
searchRequest := ldap.NewSearchRequest(
- s.Provider.baseDN,
- ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
- fmt.Sprintf("(&(objectClass="+s.Provider.userObjectClass+")("+s.Provider.userUniqueAttribute+"=%s))", ldap.EscapeFilter(s.user)),
- []string{"dn"},
+ baseDN,
+ ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, int(timeout.Seconds()), false,
+ searchQuery,
+ attributes,
nil,
)
- sr, err := l.Search(searchRequest)
+ sr, err := conn.Search(searchRequest)
if err != nil {
return nil, err
}
@@ -67,33 +174,100 @@ func (s *Session) FetchUser(_ context.Context) (idp.User, error) {
user := sr.Entries[0]
// Bind as the user to verify their password
- err = l.Bind(user.DN, s.password)
- if err != nil {
- return nil, err
+ if err = conn.Bind(user.DN, password); err != nil {
+ return nil, ErrFailedLogin
}
+ return user, nil
+}
- emailVerified, err := strconv.ParseBool(user.GetAttributeValue(s.Provider.emailVerifiedAttribute))
- if err != nil {
- return nil, err
+func queriesAndToSearchQuery(queries ...string) string {
+ if len(queries) == 0 {
+ return ""
}
- phoneVerified, err := strconv.ParseBool(user.GetAttributeValue(s.Provider.phoneVerifiedAttribute))
- if err != nil {
- return nil, err
+ if len(queries) == 1 {
+ return queries[0]
+ }
+ joinQueries := "(&"
+ for _, s := range queries {
+ joinQueries += s
+ }
+ return joinQueries + ")"
+}
+
+func queriesOrToSearchQuery(queries ...string) string {
+ if len(queries) == 0 {
+ return ""
+ }
+ if len(queries) == 1 {
+ return queries[0]
+ }
+ joinQueries := "(|"
+ for _, s := range queries {
+ joinQueries += s
+ }
+ return joinQueries + ")"
+}
+
+func objectClassesToSearchQuery(classes []string) string {
+ searchQuery := ""
+ for _, class := range classes {
+ searchQuery += "(objectClass=" + class + ")"
+ }
+ return searchQuery
+}
+
+func userFiltersToSearchQuery(filters []string, username string) string {
+ searchQuery := ""
+ for _, filter := range filters {
+ searchQuery += "(" + filter + "=" + ldap.EscapeFilter(username) + ")"
+ }
+ return searchQuery
+}
+
+func mapLDAPEntryToUser(
+ user *ldap.Entry,
+ idAttribute,
+ firstNameAttribute,
+ lastNameAttribute,
+ displayNameAttribute,
+ nickNameAttribute,
+ preferredUsernameAttribute,
+ emailAttribute,
+ emailVerifiedAttribute,
+ phoneAttribute,
+ phoneVerifiedAttribute,
+ preferredLanguageAttribute,
+ avatarURLAttribute,
+ profileAttribute string,
+) (_ *User, err error) {
+ var emailVerified bool
+ if v := user.GetAttributeValue(emailVerifiedAttribute); v != "" {
+ emailVerified, err = strconv.ParseBool(v)
+ if err != nil {
+ return nil, err
+ }
+ }
+ var phoneVerified bool
+ if v := user.GetAttributeValue(phoneVerifiedAttribute); v != "" {
+ phoneVerified, err = strconv.ParseBool(v)
+ if err != nil {
+ return nil, err
+ }
}
return NewUser(
- user.GetAttributeValue(s.Provider.idAttribute),
- user.GetAttributeValue(s.Provider.firstNameAttribute),
- user.GetAttributeValue(s.Provider.lastNameAttribute),
- user.GetAttributeValue(s.Provider.displayNameAttribute),
- user.GetAttributeValue(s.Provider.nickNameAttribute),
- user.GetAttributeValue(s.Provider.preferredUsernameAttribute),
- domain.EmailAddress(user.GetAttributeValue(s.Provider.emailAttribute)),
+ user.GetAttributeValue(idAttribute),
+ user.GetAttributeValue(firstNameAttribute),
+ user.GetAttributeValue(lastNameAttribute),
+ user.GetAttributeValue(displayNameAttribute),
+ user.GetAttributeValue(nickNameAttribute),
+ user.GetAttributeValue(preferredUsernameAttribute),
+ domain.EmailAddress(user.GetAttributeValue(emailAttribute)),
emailVerified,
- domain.PhoneNumber(user.GetAttributeValue(s.Provider.phoneAttribute)),
+ domain.PhoneNumber(user.GetAttributeValue(phoneAttribute)),
phoneVerified,
- language.Make(user.GetAttributeValue(s.Provider.preferredLanguageAttribute)),
- user.GetAttributeValue(s.Provider.avatarURLAttribute),
- user.GetAttributeValue(s.Provider.profileAttribute),
+ language.Make(user.GetAttributeValue(preferredLanguageAttribute)),
+ user.GetAttributeValue(avatarURLAttribute),
+ user.GetAttributeValue(profileAttribute),
), nil
}
diff --git a/internal/idp/providers/ldap/session_test.go b/internal/idp/providers/ldap/session_test.go
new file mode 100644
index 0000000000..f6bcee6544
--- /dev/null
+++ b/internal/idp/providers/ldap/session_test.go
@@ -0,0 +1,400 @@
+package ldap
+
+import (
+ "testing"
+
+ "github.com/go-ldap/ldap/v3"
+ "github.com/stretchr/testify/assert"
+ "golang.org/x/text/language"
+)
+
+func TestProvider_objectClassesToSearchQuery(t *testing.T) {
+ tests := []struct {
+ name string
+ fields []string
+ want string
+ }{
+ {
+ name: "zero",
+ fields: []string{},
+ want: "",
+ },
+ {
+ name: "one",
+ fields: []string{"test"},
+ want: "(objectClass=test)",
+ },
+ {
+ name: "three",
+ fields: []string{"test1", "test2", "test3"},
+ want: "(objectClass=test1)(objectClass=test2)(objectClass=test3)",
+ },
+ {
+ name: "five",
+ fields: []string{"test1", "test2", "test3", "test4", "test5"},
+ want: "(objectClass=test1)(objectClass=test2)(objectClass=test3)(objectClass=test4)(objectClass=test5)",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ a := assert.New(t)
+
+ a.Equal(tt.want, objectClassesToSearchQuery(tt.fields))
+ })
+ }
+}
+
+func TestProvider_userFiltersToSearchQuery(t *testing.T) {
+ tests := []struct {
+ name string
+ fields []string
+ username string
+ want string
+ }{
+ {
+ name: "zero",
+ fields: []string{},
+ username: "user",
+ want: "",
+ },
+ {
+ name: "one",
+ fields: []string{"test"},
+ username: "user",
+ want: "(test=user)",
+ },
+ {
+ name: "three",
+ fields: []string{"test1", "test2", "test3"},
+ username: "user",
+ want: "(test1=user)(test2=user)(test3=user)",
+ },
+ {
+ name: "five",
+ fields: []string{"test1", "test2", "test3", "test4", "test5"},
+ username: "user",
+ want: "(test1=user)(test2=user)(test3=user)(test4=user)(test5=user)",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ a := assert.New(t)
+
+ a.Equal(tt.want, userFiltersToSearchQuery(tt.fields, tt.username))
+ })
+ }
+}
+
+func TestProvider_queriesAndToSearchQuery(t *testing.T) {
+ tests := []struct {
+ name string
+ fields []string
+ want string
+ }{
+ {
+ name: "zero",
+ fields: []string{},
+ want: "",
+ },
+ {
+ name: "one",
+ fields: []string{"(test)"},
+ want: "(test)",
+ },
+ {
+ name: "three",
+ fields: []string{"(test1)", "(test2)", "(test3)"},
+ want: "(&(test1)(test2)(test3))",
+ },
+ {
+ name: "five",
+ fields: []string{"(test1)", "(test2)", "(test3)", "(test4)", "(test5)"},
+ want: "(&(test1)(test2)(test3)(test4)(test5))",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ a := assert.New(t)
+
+ a.Equal(tt.want, queriesAndToSearchQuery(tt.fields...))
+ })
+ }
+}
+
+func TestProvider_queriesOrToSearchQuery(t *testing.T) {
+ tests := []struct {
+ name string
+ fields []string
+ want string
+ }{
+ {
+ name: "zero",
+ fields: []string{},
+ want: "",
+ },
+ {
+ name: "one",
+ fields: []string{"(test)"},
+ want: "(test)",
+ },
+ {
+ name: "three",
+ fields: []string{"(test1)", "(test2)", "(test3)"},
+ want: "(|(test1)(test2)(test3))",
+ },
+ {
+ name: "five",
+ fields: []string{"(test1)", "(test2)", "(test3)", "(test4)", "(test5)"},
+ want: "(|(test1)(test2)(test3)(test4)(test5))",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ a := assert.New(t)
+
+ a.Equal(tt.want, queriesOrToSearchQuery(tt.fields...))
+ })
+ }
+}
+
+func TestProvider_mapLDAPEntryToUser(t *testing.T) {
+ type fields struct {
+ user *ldap.Entry
+ idAttribute string
+ firstNameAttribute string
+ lastNameAttribute string
+ displayNameAttribute string
+ nickNameAttribute string
+ preferredUsernameAttribute string
+ emailAttribute string
+ emailVerifiedAttribute string
+ phoneAttribute string
+ phoneVerifiedAttribute string
+ preferredLanguageAttribute string
+ avatarURLAttribute string
+ profileAttribute string
+ }
+ type want struct {
+ user *User
+ err func(error) bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want want
+ }{
+ {
+ name: "empty",
+ fields: fields{
+ user: &ldap.Entry{
+ Attributes: []*ldap.EntryAttribute{
+ {Name: "id", Values: []string{"id"}},
+ {Name: "first", Values: []string{"first"}},
+ {Name: "last", Values: []string{"last"}},
+ {Name: "display", Values: []string{"display"}},
+ {Name: "nick", Values: []string{"nick"}},
+ {Name: "preferred", Values: []string{"preferred"}},
+ {Name: "email", Values: []string{"email"}},
+ {Name: "emailVerified", Values: []string{"false"}},
+ {Name: "phone", Values: []string{"phone"}},
+ {Name: "phoneVerified", Values: []string{"false"}},
+ {Name: "lang", Values: []string{"und"}},
+ {Name: "avatar", Values: []string{"avatar"}},
+ {Name: "profile", Values: []string{"profile"}},
+ },
+ },
+ idAttribute: "",
+ firstNameAttribute: "",
+ lastNameAttribute: "",
+ displayNameAttribute: "",
+ nickNameAttribute: "",
+ preferredUsernameAttribute: "",
+ emailAttribute: "",
+ emailVerifiedAttribute: "",
+ phoneAttribute: "",
+ phoneVerifiedAttribute: "",
+ preferredLanguageAttribute: "",
+ avatarURLAttribute: "",
+ profileAttribute: "",
+ },
+ want: want{
+ user: &User{
+ id: "",
+ firstName: "",
+ lastName: "",
+ displayName: "",
+ nickName: "",
+ preferredUsername: "",
+ email: "",
+ emailVerified: false,
+ phone: "",
+ phoneVerified: false,
+ preferredLanguage: language.Tag{},
+ avatarURL: "",
+ profile: "",
+ },
+ },
+ },
+ {
+ name: "failed parse emailVerified",
+ fields: fields{
+ user: &ldap.Entry{
+ Attributes: []*ldap.EntryAttribute{
+ {Name: "id", Values: []string{"id"}},
+ {Name: "first", Values: []string{"first"}},
+ {Name: "last", Values: []string{"last"}},
+ {Name: "display", Values: []string{"display"}},
+ {Name: "nick", Values: []string{"nick"}},
+ {Name: "preferred", Values: []string{"preferred"}},
+ {Name: "email", Values: []string{"email"}},
+ {Name: "emailVerified", Values: []string{"failure"}},
+ {Name: "phone", Values: []string{"phone"}},
+ {Name: "phoneVerified", Values: []string{"false"}},
+ {Name: "lang", Values: []string{"und"}},
+ {Name: "avatar", Values: []string{"avatar"}},
+ {Name: "profile", Values: []string{"profile"}},
+ },
+ },
+ idAttribute: "id",
+ firstNameAttribute: "first",
+ lastNameAttribute: "last",
+ displayNameAttribute: "display",
+ nickNameAttribute: "nick",
+ preferredUsernameAttribute: "preferred",
+ emailAttribute: "email",
+ emailVerifiedAttribute: "emailVerified",
+ phoneAttribute: "phone",
+ phoneVerifiedAttribute: "phoneVerified",
+ preferredLanguageAttribute: "lang",
+ avatarURLAttribute: "avatar",
+ profileAttribute: "profile",
+ },
+ want: want{
+ err: func(err error) bool {
+ return err != nil
+ },
+ },
+ },
+ {
+ name: "failed parse phoneVerified",
+ fields: fields{
+ user: &ldap.Entry{
+ Attributes: []*ldap.EntryAttribute{
+ {Name: "id", Values: []string{"id"}},
+ {Name: "first", Values: []string{"first"}},
+ {Name: "last", Values: []string{"last"}},
+ {Name: "display", Values: []string{"display"}},
+ {Name: "nick", Values: []string{"nick"}},
+ {Name: "preferred", Values: []string{"preferred"}},
+ {Name: "email", Values: []string{"email"}},
+ {Name: "emailVerified", Values: []string{"false"}},
+ {Name: "phone", Values: []string{"phone"}},
+ {Name: "phoneVerified", Values: []string{"failure"}},
+ {Name: "lang", Values: []string{"und"}},
+ {Name: "avatar", Values: []string{"avatar"}},
+ {Name: "profile", Values: []string{"profile"}},
+ },
+ },
+ idAttribute: "id",
+ firstNameAttribute: "first",
+ lastNameAttribute: "last",
+ displayNameAttribute: "display",
+ nickNameAttribute: "nick",
+ preferredUsernameAttribute: "preferred",
+ emailAttribute: "email",
+ emailVerifiedAttribute: "emailVerified",
+ phoneAttribute: "phone",
+ phoneVerifiedAttribute: "phoneVerified",
+ preferredLanguageAttribute: "lang",
+ avatarURLAttribute: "avatar",
+ profileAttribute: "profile",
+ },
+ want: want{
+ err: func(err error) bool {
+ return err != nil
+ },
+ },
+ },
+ {
+ name: "full user",
+ fields: fields{
+ user: &ldap.Entry{
+ Attributes: []*ldap.EntryAttribute{
+ {Name: "id", Values: []string{"id"}},
+ {Name: "first", Values: []string{"first"}},
+ {Name: "last", Values: []string{"last"}},
+ {Name: "display", Values: []string{"display"}},
+ {Name: "nick", Values: []string{"nick"}},
+ {Name: "preferred", Values: []string{"preferred"}},
+ {Name: "email", Values: []string{"email"}},
+ {Name: "emailVerified", Values: []string{"false"}},
+ {Name: "phone", Values: []string{"phone"}},
+ {Name: "phoneVerified", Values: []string{"false"}},
+ {Name: "lang", Values: []string{"und"}},
+ {Name: "avatar", Values: []string{"avatar"}},
+ {Name: "profile", Values: []string{"profile"}},
+ },
+ },
+ idAttribute: "id",
+ firstNameAttribute: "first",
+ lastNameAttribute: "last",
+ displayNameAttribute: "display",
+ nickNameAttribute: "nick",
+ preferredUsernameAttribute: "preferred",
+ emailAttribute: "email",
+ emailVerifiedAttribute: "emailVerified",
+ phoneAttribute: "phone",
+ phoneVerifiedAttribute: "phoneVerified",
+ preferredLanguageAttribute: "lang",
+ avatarURLAttribute: "avatar",
+ profileAttribute: "profile",
+ },
+ want: want{
+ user: &User{
+ id: "id",
+ firstName: "first",
+ lastName: "last",
+ displayName: "display",
+ nickName: "nick",
+ preferredUsername: "preferred",
+ email: "email",
+ emailVerified: false,
+ phone: "phone",
+ phoneVerified: false,
+ preferredLanguage: language.Make("und"),
+ avatarURL: "avatar",
+ profile: "profile",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := mapLDAPEntryToUser(
+ tt.fields.user,
+ tt.fields.idAttribute,
+ tt.fields.firstNameAttribute,
+ tt.fields.lastNameAttribute,
+ tt.fields.displayNameAttribute,
+ tt.fields.nickNameAttribute,
+ tt.fields.preferredUsernameAttribute,
+ tt.fields.emailAttribute,
+ tt.fields.emailVerifiedAttribute,
+ tt.fields.phoneAttribute,
+ tt.fields.phoneVerifiedAttribute,
+ tt.fields.preferredLanguageAttribute,
+ tt.fields.avatarURLAttribute,
+ tt.fields.profileAttribute,
+ )
+ if tt.want.err == nil {
+ assert.NoError(t, err)
+ }
+ if tt.want.err != nil && !tt.want.err(err) {
+ t.Errorf("got wrong err: %v ", err)
+ }
+ if tt.want.err == nil {
+ assert.Equal(t, tt.want.user, got)
+ }
+ })
+ }
+}
diff --git a/internal/query/idp_template.go b/internal/query/idp_template.go
index 53190297f1..2639691a74 100644
--- a/internal/query/idp_template.go
+++ b/internal/query/idp_template.go
@@ -128,15 +128,16 @@ type GoogleIDPTemplate struct {
}
type LDAPIDPTemplate struct {
- IDPID string
- Host string
- Port string
- TLS bool
- BaseDN string
- UserObjectClass string
- UserUniqueAttribute string
- Admin string
- Password *crypto.CryptoValue
+ IDPID string
+ Servers []string
+ StartTLS bool
+ BaseDN string
+ BindDN string
+ BindPassword *crypto.CryptoValue
+ UserBase string
+ UserObjectClasses []string
+ UserFilters []string
+ Timeout time.Duration
idp.LDAPAttributes
}
@@ -515,36 +516,40 @@ var (
name: projection.LDAPInstanceIDCol,
table: ldapIdpTemplateTable,
}
- LDAPHostCol = Column{
- name: projection.LDAPHostCol,
+ LDAPServersCol = Column{
+ name: projection.LDAPServersCol,
table: ldapIdpTemplateTable,
}
- LDAPPortCol = Column{
- name: projection.LDAPPortCol,
- table: ldapIdpTemplateTable,
- }
- LDAPTlsCol = Column{
- name: projection.LDAPTlsCol,
+ LDAPStartTLSCol = Column{
+ name: projection.LDAPStartTLSCol,
table: ldapIdpTemplateTable,
}
LDAPBaseDNCol = Column{
name: projection.LDAPBaseDNCol,
table: ldapIdpTemplateTable,
}
- LDAPUserObjectClassCol = Column{
- name: projection.LDAPUserObjectClassCol,
+ LDAPBindDNCol = Column{
+ name: projection.LDAPBindDNCol,
table: ldapIdpTemplateTable,
}
- LDAPUserUniqueAttributeCol = Column{
- name: projection.LDAPUserUniqueAttributeCol,
+ LDAPBindPasswordCol = Column{
+ name: projection.LDAPBindPasswordCol,
table: ldapIdpTemplateTable,
}
- LDAPAdminCol = Column{
- name: projection.LDAPAdminCol,
+ LDAPUserBaseCol = Column{
+ name: projection.LDAPUserBaseCol,
table: ldapIdpTemplateTable,
}
- LDAPPasswordCol = Column{
- name: projection.LDAPPasswordCol,
+ LDAPUserObjectClassesCol = Column{
+ name: projection.LDAPUserObjectClassesCol,
+ table: ldapIdpTemplateTable,
+ }
+ LDAPUserFiltersCol = Column{
+ name: projection.LDAPUserFiltersCol,
+ table: ldapIdpTemplateTable,
+ }
+ LDAPTimeoutCol = Column{
+ name: projection.LDAPTimeoutCol,
table: ldapIdpTemplateTable,
}
LDAPIDAttributeCol = Column{
@@ -772,14 +777,15 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
GoogleScopesCol.identifier(),
// ldap
LDAPIDCol.identifier(),
- LDAPHostCol.identifier(),
- LDAPPortCol.identifier(),
- LDAPTlsCol.identifier(),
+ LDAPServersCol.identifier(),
+ LDAPStartTLSCol.identifier(),
LDAPBaseDNCol.identifier(),
- LDAPUserObjectClassCol.identifier(),
- LDAPUserUniqueAttributeCol.identifier(),
- LDAPAdminCol.identifier(),
- LDAPPasswordCol.identifier(),
+ LDAPBindDNCol.identifier(),
+ LDAPBindPasswordCol.identifier(),
+ LDAPUserBaseCol.identifier(),
+ LDAPUserObjectClassesCol.identifier(),
+ LDAPUserFiltersCol.identifier(),
+ LDAPTimeoutCol.identifier(),
LDAPIDAttributeCol.identifier(),
LDAPFirstNameAttributeCol.identifier(),
LDAPLastNameAttributeCol.identifier(),
@@ -869,14 +875,15 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
googleScopes := database.StringArray{}
ldapID := sql.NullString{}
- ldapHost := sql.NullString{}
- ldapPort := sql.NullString{}
- ldapTls := sql.NullBool{}
+ ldapServers := database.StringArray{}
+ ldapStartTls := sql.NullBool{}
ldapBaseDN := sql.NullString{}
- ldapUserObjectClass := sql.NullString{}
- ldapUserUniqueAttribute := sql.NullString{}
- ldapAdmin := sql.NullString{}
- ldapPassword := new(crypto.CryptoValue)
+ ldapBindDN := sql.NullString{}
+ ldapBindPassword := new(crypto.CryptoValue)
+ ldapUserBase := sql.NullString{}
+ ldapUserObjectClasses := database.StringArray{}
+ ldapUserFilters := database.StringArray{}
+ ldapTimeout := sql.NullInt64{}
ldapIDAttribute := sql.NullString{}
ldapFirstNameAttribute := sql.NullString{}
ldapLastNameAttribute := sql.NullString{}
@@ -965,14 +972,15 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
&googleScopes,
// ldap
&ldapID,
- &ldapHost,
- &ldapPort,
- &ldapTls,
+ &ldapServers,
+ &ldapStartTls,
&ldapBaseDN,
- &ldapUserObjectClass,
- &ldapUserUniqueAttribute,
- &ldapAdmin,
- &ldapPassword,
+ &ldapBindDN,
+ &ldapBindPassword,
+ &ldapUserBase,
+ &ldapUserObjectClasses,
+ &ldapUserFilters,
+ &ldapTimeout,
&ldapIDAttribute,
&ldapFirstNameAttribute,
&ldapLastNameAttribute,
@@ -1083,15 +1091,16 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
}
if ldapID.Valid {
idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{
- IDPID: ldapID.String,
- Host: ldapHost.String,
- Port: ldapPort.String,
- TLS: ldapTls.Bool,
- BaseDN: ldapBaseDN.String,
- UserObjectClass: ldapUserObjectClass.String,
- UserUniqueAttribute: ldapUserUniqueAttribute.String,
- Admin: ldapAdmin.String,
- Password: ldapPassword,
+ IDPID: ldapID.String,
+ Servers: ldapServers,
+ StartTLS: ldapStartTls.Bool,
+ BaseDN: ldapBaseDN.String,
+ BindDN: ldapBindDN.String,
+ BindPassword: ldapBindPassword,
+ UserBase: ldapUserBase.String,
+ UserObjectClasses: ldapUserObjectClasses,
+ UserFilters: ldapUserFilters,
+ Timeout: time.Duration(ldapTimeout.Int64),
LDAPAttributes: idp.LDAPAttributes{
IDAttribute: ldapIDAttribute.String,
FirstNameAttribute: ldapFirstNameAttribute.String,
@@ -1189,14 +1198,15 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
GoogleScopesCol.identifier(),
// ldap
LDAPIDCol.identifier(),
- LDAPHostCol.identifier(),
- LDAPPortCol.identifier(),
- LDAPTlsCol.identifier(),
+ LDAPServersCol.identifier(),
+ LDAPStartTLSCol.identifier(),
LDAPBaseDNCol.identifier(),
- LDAPUserObjectClassCol.identifier(),
- LDAPUserUniqueAttributeCol.identifier(),
- LDAPAdminCol.identifier(),
- LDAPPasswordCol.identifier(),
+ LDAPBindDNCol.identifier(),
+ LDAPBindPasswordCol.identifier(),
+ LDAPUserBaseCol.identifier(),
+ LDAPUserObjectClassesCol.identifier(),
+ LDAPUserFiltersCol.identifier(),
+ LDAPTimeoutCol.identifier(),
LDAPIDAttributeCol.identifier(),
LDAPFirstNameAttributeCol.identifier(),
LDAPLastNameAttributeCol.identifier(),
@@ -1290,14 +1300,15 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
googleScopes := database.StringArray{}
ldapID := sql.NullString{}
- ldapHost := sql.NullString{}
- ldapPort := sql.NullString{}
- ldapTls := sql.NullBool{}
+ ldapServers := database.StringArray{}
+ ldapStartTls := sql.NullBool{}
ldapBaseDN := sql.NullString{}
- ldapUserObjectClass := sql.NullString{}
- ldapUserUniqueAttribute := sql.NullString{}
- ldapAdmin := sql.NullString{}
- ldapPassword := new(crypto.CryptoValue)
+ ldapBindDN := sql.NullString{}
+ ldapBindPassword := new(crypto.CryptoValue)
+ ldapUserBase := sql.NullString{}
+ ldapUserObjectClasses := database.StringArray{}
+ ldapUserFilters := database.StringArray{}
+ ldapTimeout := sql.NullInt64{}
ldapIDAttribute := sql.NullString{}
ldapFirstNameAttribute := sql.NullString{}
ldapLastNameAttribute := sql.NullString{}
@@ -1386,14 +1397,15 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
&googleScopes,
// ldap
&ldapID,
- &ldapHost,
- &ldapPort,
- &ldapTls,
+ &ldapServers,
+ &ldapStartTls,
&ldapBaseDN,
- &ldapUserObjectClass,
- &ldapUserUniqueAttribute,
- &ldapAdmin,
- &ldapPassword,
+ &ldapBindDN,
+ &ldapBindPassword,
+ &ldapUserBase,
+ &ldapUserObjectClasses,
+ &ldapUserFilters,
+ &ldapTimeout,
&ldapIDAttribute,
&ldapFirstNameAttribute,
&ldapLastNameAttribute,
@@ -1503,15 +1515,16 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
}
if ldapID.Valid {
idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{
- IDPID: ldapID.String,
- Host: ldapHost.String,
- Port: ldapPort.String,
- TLS: ldapTls.Bool,
- BaseDN: ldapBaseDN.String,
- UserObjectClass: ldapUserObjectClass.String,
- UserUniqueAttribute: ldapUserUniqueAttribute.String,
- Admin: ldapAdmin.String,
- Password: ldapPassword,
+ IDPID: ldapID.String,
+ Servers: ldapServers,
+ StartTLS: ldapStartTls.Bool,
+ BaseDN: ldapBaseDN.String,
+ BindDN: ldapBindDN.String,
+ BindPassword: ldapBindPassword,
+ UserBase: ldapUserBase.String,
+ UserObjectClasses: ldapUserObjectClasses,
+ UserFilters: ldapUserFilters,
+ Timeout: time.Duration(ldapTimeout.Int64),
LDAPAttributes: idp.LDAPAttributes{
IDAttribute: ldapIDAttribute.String,
FirstNameAttribute: ldapFirstNameAttribute.String,
diff --git a/internal/query/idp_template_test.go b/internal/query/idp_template_test.go
index e3147116fe..3d39eacc21 100644
--- a/internal/query/idp_template_test.go
+++ b/internal/query/idp_template_test.go
@@ -7,6 +7,7 @@ import (
"fmt"
"regexp"
"testing"
+ "time"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
@@ -87,28 +88,29 @@ var (
` projections.idp_templates4_google.client_secret,` +
` projections.idp_templates4_google.scopes,` +
// ldap
- ` projections.idp_templates4_ldap.idp_id,` +
- ` projections.idp_templates4_ldap.host,` +
- ` projections.idp_templates4_ldap.port,` +
- ` projections.idp_templates4_ldap.tls,` +
- ` projections.idp_templates4_ldap.base_dn,` +
- ` projections.idp_templates4_ldap.user_object_class,` +
- ` projections.idp_templates4_ldap.user_unique_attribute,` +
- ` projections.idp_templates4_ldap.admin,` +
- ` projections.idp_templates4_ldap.password,` +
- ` projections.idp_templates4_ldap.id_attribute,` +
- ` projections.idp_templates4_ldap.first_name_attribute,` +
- ` projections.idp_templates4_ldap.last_name_attribute,` +
- ` projections.idp_templates4_ldap.display_name_attribute,` +
- ` projections.idp_templates4_ldap.nick_name_attribute,` +
- ` projections.idp_templates4_ldap.preferred_username_attribute,` +
- ` projections.idp_templates4_ldap.email_attribute,` +
- ` projections.idp_templates4_ldap.email_verified,` +
- ` projections.idp_templates4_ldap.phone_attribute,` +
- ` projections.idp_templates4_ldap.phone_verified_attribute,` +
- ` projections.idp_templates4_ldap.preferred_language_attribute,` +
- ` projections.idp_templates4_ldap.avatar_url_attribute,` +
- ` projections.idp_templates4_ldap.profile_attribute` +
+ ` projections.idp_templates4_ldap2.idp_id,` +
+ ` projections.idp_templates4_ldap2.servers,` +
+ ` projections.idp_templates4_ldap2.start_tls,` +
+ ` projections.idp_templates4_ldap2.base_dn,` +
+ ` projections.idp_templates4_ldap2.bind_dn,` +
+ ` projections.idp_templates4_ldap2.bind_password,` +
+ ` projections.idp_templates4_ldap2.user_base,` +
+ ` projections.idp_templates4_ldap2.user_object_classes,` +
+ ` projections.idp_templates4_ldap2.user_filters,` +
+ ` projections.idp_templates4_ldap2.timeout,` +
+ ` projections.idp_templates4_ldap2.id_attribute,` +
+ ` projections.idp_templates4_ldap2.first_name_attribute,` +
+ ` projections.idp_templates4_ldap2.last_name_attribute,` +
+ ` projections.idp_templates4_ldap2.display_name_attribute,` +
+ ` projections.idp_templates4_ldap2.nick_name_attribute,` +
+ ` projections.idp_templates4_ldap2.preferred_username_attribute,` +
+ ` projections.idp_templates4_ldap2.email_attribute,` +
+ ` projections.idp_templates4_ldap2.email_verified,` +
+ ` projections.idp_templates4_ldap2.phone_attribute,` +
+ ` projections.idp_templates4_ldap2.phone_verified_attribute,` +
+ ` projections.idp_templates4_ldap2.preferred_language_attribute,` +
+ ` projections.idp_templates4_ldap2.avatar_url_attribute,` +
+ ` projections.idp_templates4_ldap2.profile_attribute` +
` FROM projections.idp_templates4` +
` LEFT JOIN projections.idp_templates4_oauth2 ON projections.idp_templates4.id = projections.idp_templates4_oauth2.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_oauth2.instance_id` +
` LEFT JOIN projections.idp_templates4_oidc ON projections.idp_templates4.id = projections.idp_templates4_oidc.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_oidc.instance_id` +
@@ -119,7 +121,7 @@ var (
` LEFT JOIN projections.idp_templates4_gitlab ON projections.idp_templates4.id = projections.idp_templates4_gitlab.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_gitlab.instance_id` +
` LEFT JOIN projections.idp_templates4_gitlab_self_hosted ON projections.idp_templates4.id = projections.idp_templates4_gitlab_self_hosted.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_gitlab_self_hosted.instance_id` +
` LEFT JOIN projections.idp_templates4_google ON projections.idp_templates4.id = projections.idp_templates4_google.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_google.instance_id` +
- ` LEFT JOIN projections.idp_templates4_ldap ON projections.idp_templates4.id = projections.idp_templates4_ldap.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_ldap.instance_id` +
+ ` LEFT JOIN projections.idp_templates4_ldap2 ON projections.idp_templates4.id = projections.idp_templates4_ldap2.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_ldap2.instance_id` +
` AS OF SYSTEM TIME '-1 ms'`
idpTemplateCols = []string{
"id",
@@ -195,14 +197,15 @@ var (
"scopes",
// ldap config
"idp_id",
- "host",
- "port",
- "tls",
+ "servers",
+ "start_tls",
"base_dn",
- "user_object_class",
- "user_unique_attribute",
- "admin",
- "password",
+ "bind_dn",
+ "bind_password",
+ "user_base",
+ "user_object_classes",
+ "user_filters",
+ "timeout",
"id_attribute",
"first_name_attribute",
"last_name_attribute",
@@ -289,28 +292,29 @@ var (
` projections.idp_templates4_google.client_secret,` +
` projections.idp_templates4_google.scopes,` +
// ldap
- ` projections.idp_templates4_ldap.idp_id,` +
- ` projections.idp_templates4_ldap.host,` +
- ` projections.idp_templates4_ldap.port,` +
- ` projections.idp_templates4_ldap.tls,` +
- ` projections.idp_templates4_ldap.base_dn,` +
- ` projections.idp_templates4_ldap.user_object_class,` +
- ` projections.idp_templates4_ldap.user_unique_attribute,` +
- ` projections.idp_templates4_ldap.admin,` +
- ` projections.idp_templates4_ldap.password,` +
- ` projections.idp_templates4_ldap.id_attribute,` +
- ` projections.idp_templates4_ldap.first_name_attribute,` +
- ` projections.idp_templates4_ldap.last_name_attribute,` +
- ` projections.idp_templates4_ldap.display_name_attribute,` +
- ` projections.idp_templates4_ldap.nick_name_attribute,` +
- ` projections.idp_templates4_ldap.preferred_username_attribute,` +
- ` projections.idp_templates4_ldap.email_attribute,` +
- ` projections.idp_templates4_ldap.email_verified,` +
- ` projections.idp_templates4_ldap.phone_attribute,` +
- ` projections.idp_templates4_ldap.phone_verified_attribute,` +
- ` projections.idp_templates4_ldap.preferred_language_attribute,` +
- ` projections.idp_templates4_ldap.avatar_url_attribute,` +
- ` projections.idp_templates4_ldap.profile_attribute,` +
+ ` projections.idp_templates4_ldap2.idp_id,` +
+ ` projections.idp_templates4_ldap2.servers,` +
+ ` projections.idp_templates4_ldap2.start_tls,` +
+ ` projections.idp_templates4_ldap2.base_dn,` +
+ ` projections.idp_templates4_ldap2.bind_dn,` +
+ ` projections.idp_templates4_ldap2.bind_password,` +
+ ` projections.idp_templates4_ldap2.user_base,` +
+ ` projections.idp_templates4_ldap2.user_object_classes,` +
+ ` projections.idp_templates4_ldap2.user_filters,` +
+ ` projections.idp_templates4_ldap2.timeout,` +
+ ` projections.idp_templates4_ldap2.id_attribute,` +
+ ` projections.idp_templates4_ldap2.first_name_attribute,` +
+ ` projections.idp_templates4_ldap2.last_name_attribute,` +
+ ` projections.idp_templates4_ldap2.display_name_attribute,` +
+ ` projections.idp_templates4_ldap2.nick_name_attribute,` +
+ ` projections.idp_templates4_ldap2.preferred_username_attribute,` +
+ ` projections.idp_templates4_ldap2.email_attribute,` +
+ ` projections.idp_templates4_ldap2.email_verified,` +
+ ` projections.idp_templates4_ldap2.phone_attribute,` +
+ ` projections.idp_templates4_ldap2.phone_verified_attribute,` +
+ ` projections.idp_templates4_ldap2.preferred_language_attribute,` +
+ ` projections.idp_templates4_ldap2.avatar_url_attribute,` +
+ ` projections.idp_templates4_ldap2.profile_attribute,` +
` COUNT(*) OVER ()` +
` FROM projections.idp_templates4` +
` LEFT JOIN projections.idp_templates4_oauth2 ON projections.idp_templates4.id = projections.idp_templates4_oauth2.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_oauth2.instance_id` +
@@ -322,7 +326,7 @@ var (
` LEFT JOIN projections.idp_templates4_gitlab ON projections.idp_templates4.id = projections.idp_templates4_gitlab.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_gitlab.instance_id` +
` LEFT JOIN projections.idp_templates4_gitlab_self_hosted ON projections.idp_templates4.id = projections.idp_templates4_gitlab_self_hosted.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_gitlab_self_hosted.instance_id` +
` LEFT JOIN projections.idp_templates4_google ON projections.idp_templates4.id = projections.idp_templates4_google.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_google.instance_id` +
- ` LEFT JOIN projections.idp_templates4_ldap ON projections.idp_templates4.id = projections.idp_templates4_ldap.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_ldap.instance_id` +
+ ` LEFT JOIN projections.idp_templates4_ldap2 ON projections.idp_templates4.id = projections.idp_templates4_ldap2.idp_id AND projections.idp_templates4.instance_id = projections.idp_templates4_ldap2.instance_id` +
` AS OF SYSTEM TIME '-1 ms'`
idpTemplatesCols = []string{
"id",
@@ -398,14 +402,15 @@ var (
"scopes",
// ldap config
"idp_id",
- "host",
- "port",
- "tls",
+ "servers",
+ "start_tls",
"base_dn",
- "user_object_class",
- "user_unique_attribute",
- "admin",
- "password",
+ "bind_dn",
+ "bind_password",
+ "user_base",
+ "user_object_classes",
+ "user_filters",
+ "timeout",
"id_attribute",
"first_name_attribute",
"last_name_attribute",
@@ -554,6 +559,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
),
},
@@ -685,6 +691,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
),
},
@@ -814,6 +821,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
),
},
@@ -942,6 +950,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
),
},
@@ -1069,6 +1078,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
),
},
@@ -1196,6 +1206,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
),
},
@@ -1324,6 +1335,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
),
},
@@ -1430,14 +1442,15 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
// ldap config
"idp-id",
- "host",
- "port",
+ database.StringArray{"server"},
true,
"base",
- "user",
- "uid",
- "admin",
+ "dn",
nil,
+ "user",
+ database.StringArray{"object"},
+ database.StringArray{"filter"},
+ time.Duration(30000000000),
"id",
"first",
"last",
@@ -1469,14 +1482,15 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
IsAutoCreation: true,
IsAutoUpdate: true,
LDAPIDPTemplate: &LDAPIDPTemplate{
- IDPID: "idp-id",
- Host: "host",
- Port: "port",
- TLS: true,
- BaseDN: "base",
- UserObjectClass: "user",
- UserUniqueAttribute: "uid",
- Admin: "admin",
+ IDPID: "idp-id",
+ Servers: []string{"server"},
+ StartTLS: true,
+ BaseDN: "base",
+ BindDN: "dn",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ UserFilters: []string{"filter"},
+ Timeout: time.Duration(30000000000),
LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id",
FirstNameAttribute: "first",
@@ -1597,6 +1611,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
),
},
@@ -1733,14 +1748,15 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
// ldap config
"idp-id",
- "host",
- "port",
+ database.StringArray{"server"},
true,
"base",
- "user",
- "uid",
- "admin",
+ "dn",
nil,
+ "user",
+ database.StringArray{"object"},
+ database.StringArray{"filter"},
+ time.Duration(30000000000),
"id",
"first",
"last",
@@ -1778,14 +1794,15 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
IsAutoCreation: true,
IsAutoUpdate: true,
LDAPIDPTemplate: &LDAPIDPTemplate{
- IDPID: "idp-id",
- Host: "host",
- Port: "port",
- TLS: true,
- BaseDN: "base",
- UserObjectClass: "user",
- UserUniqueAttribute: "uid",
- Admin: "admin",
+ IDPID: "idp-id",
+ Servers: []string{"server"},
+ StartTLS: true,
+ BaseDN: "base",
+ BindDN: "dn",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ UserFilters: []string{"filter"},
+ Timeout: time.Duration(30000000000),
LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id",
FirstNameAttribute: "first",
@@ -1909,6 +1926,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
},
),
@@ -2018,14 +2036,15 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
// ldap config
"idp-id-ldap",
- "host",
- "port",
+ database.StringArray{"server"},
true,
"base",
- "user",
- "uid",
- "admin",
+ "dn",
nil,
+ "user",
+ database.StringArray{"object"},
+ database.StringArray{"filter"},
+ time.Duration(30000000000),
"id",
"first",
"last",
@@ -2135,6 +2154,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
{
"idp-id-oauth",
@@ -2231,6 +2251,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
{
"idp-id-oidc",
@@ -2327,6 +2348,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
{
"idp-id-jwt",
@@ -2423,6 +2445,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
},
},
),
@@ -2447,14 +2470,15 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
IsAutoCreation: true,
IsAutoUpdate: true,
LDAPIDPTemplate: &LDAPIDPTemplate{
- IDPID: "idp-id-ldap",
- Host: "host",
- Port: "port",
- TLS: true,
- BaseDN: "base",
- UserObjectClass: "user",
- UserUniqueAttribute: "uid",
- Admin: "admin",
+ IDPID: "idp-id-ldap",
+ Servers: []string{"server"},
+ StartTLS: true,
+ BaseDN: "base",
+ BindDN: "dn",
+ UserBase: "user",
+ UserObjectClasses: []string{"object"},
+ UserFilters: []string{"filter"},
+ Timeout: time.Duration(30000000000),
LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id",
FirstNameAttribute: "first",
diff --git a/internal/query/projection/idp_template.go b/internal/query/projection/idp_template.go
index 53afb832ac..2a1f9a9cc0 100644
--- a/internal/query/projection/idp_template.go
+++ b/internal/query/projection/idp_template.go
@@ -38,7 +38,7 @@ const (
IDPTemplateGitLabSuffix = "gitlab"
IDPTemplateGitLabSelfHostedSuffix = "gitlab_self_hosted"
IDPTemplateGoogleSuffix = "google"
- IDPTemplateLDAPSuffix = "ldap"
+ IDPTemplateLDAPSuffix = "ldap2"
IDPTemplateIDCol = "id"
IDPTemplateCreationDateCol = "creation_date"
@@ -125,14 +125,15 @@ const (
LDAPIDCol = "idp_id"
LDAPInstanceIDCol = "instance_id"
- LDAPHostCol = "host"
- LDAPPortCol = "port"
- LDAPTlsCol = "tls"
+ LDAPServersCol = "servers"
+ LDAPStartTLSCol = "start_tls"
LDAPBaseDNCol = "base_dn"
- LDAPUserObjectClassCol = "user_object_class"
- LDAPUserUniqueAttributeCol = "user_unique_attribute"
- LDAPAdminCol = "admin"
- LDAPPasswordCol = "password"
+ LDAPBindDNCol = "bind_dn"
+ LDAPBindPasswordCol = "bind_password"
+ LDAPUserBaseCol = "user_base"
+ LDAPUserObjectClassesCol = "user_object_classes"
+ LDAPUserFiltersCol = "user_filters"
+ LDAPTimeoutCol = "timeout"
LDAPIDAttributeCol = "id_attribute"
LDAPFirstNameAttributeCol = "first_name_attribute"
LDAPLastNameAttributeCol = "last_name_attribute"
@@ -293,14 +294,15 @@ func newIDPTemplateProjection(ctx context.Context, config crdb.StatementHandlerC
crdb.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(LDAPIDCol, crdb.ColumnTypeText),
crdb.NewColumn(LDAPInstanceIDCol, crdb.ColumnTypeText),
- crdb.NewColumn(LDAPHostCol, crdb.ColumnTypeText, crdb.Nullable()),
- crdb.NewColumn(LDAPPortCol, crdb.ColumnTypeText, crdb.Nullable()),
- crdb.NewColumn(LDAPTlsCol, crdb.ColumnTypeBool, crdb.Nullable()),
- crdb.NewColumn(LDAPBaseDNCol, crdb.ColumnTypeText, crdb.Nullable()),
- crdb.NewColumn(LDAPUserObjectClassCol, crdb.ColumnTypeText, crdb.Nullable()),
- crdb.NewColumn(LDAPUserUniqueAttributeCol, crdb.ColumnTypeText, crdb.Nullable()),
- crdb.NewColumn(LDAPAdminCol, crdb.ColumnTypeText, crdb.Nullable()),
- crdb.NewColumn(LDAPPasswordCol, crdb.ColumnTypeJSONB, crdb.Nullable()),
+ crdb.NewColumn(LDAPServersCol, crdb.ColumnTypeTextArray),
+ crdb.NewColumn(LDAPStartTLSCol, crdb.ColumnTypeBool),
+ crdb.NewColumn(LDAPBaseDNCol, crdb.ColumnTypeText),
+ crdb.NewColumn(LDAPBindDNCol, crdb.ColumnTypeText),
+ crdb.NewColumn(LDAPBindPasswordCol, crdb.ColumnTypeJSONB),
+ crdb.NewColumn(LDAPUserBaseCol, crdb.ColumnTypeText),
+ crdb.NewColumn(LDAPUserObjectClassesCol, crdb.ColumnTypeTextArray),
+ crdb.NewColumn(LDAPUserFiltersCol, crdb.ColumnTypeTextArray),
+ crdb.NewColumn(LDAPTimeoutCol, crdb.ColumnTypeInt64),
crdb.NewColumn(LDAPIDAttributeCol, crdb.ColumnTypeText, crdb.Nullable()),
crdb.NewColumn(LDAPFirstNameAttributeCol, crdb.ColumnTypeText, crdb.Nullable()),
crdb.NewColumn(LDAPLastNameAttributeCol, crdb.ColumnTypeText, crdb.Nullable()),
@@ -1663,14 +1665,15 @@ func (p *idpTemplateProjection) reduceLDAPIDPAdded(event eventstore.Event) (*han
[]handler.Column{
handler.NewCol(LDAPIDCol, idpEvent.ID),
handler.NewCol(LDAPInstanceIDCol, idpEvent.Aggregate().InstanceID),
- handler.NewCol(LDAPHostCol, idpEvent.Host),
- handler.NewCol(LDAPPortCol, idpEvent.Port),
- handler.NewCol(LDAPTlsCol, idpEvent.TLS),
+ handler.NewCol(LDAPServersCol, database.StringArray(idpEvent.Servers)),
+ handler.NewCol(LDAPStartTLSCol, idpEvent.StartTLS),
handler.NewCol(LDAPBaseDNCol, idpEvent.BaseDN),
- handler.NewCol(LDAPUserObjectClassCol, idpEvent.UserObjectClass),
- handler.NewCol(LDAPUserUniqueAttributeCol, idpEvent.UserUniqueAttribute),
- handler.NewCol(LDAPAdminCol, idpEvent.Admin),
- handler.NewCol(LDAPPasswordCol, idpEvent.Password),
+ handler.NewCol(LDAPBindDNCol, idpEvent.BindDN),
+ handler.NewCol(LDAPBindPasswordCol, idpEvent.BindPassword),
+ handler.NewCol(LDAPUserBaseCol, idpEvent.UserBase),
+ handler.NewCol(LDAPUserObjectClassesCol, database.StringArray(idpEvent.UserObjectClasses)),
+ handler.NewCol(LDAPUserFiltersCol, database.StringArray(idpEvent.UserFilters)),
+ handler.NewCol(LDAPTimeoutCol, idpEvent.Timeout),
handler.NewCol(LDAPIDAttributeCol, idpEvent.IDAttribute),
handler.NewCol(LDAPFirstNameAttributeCol, idpEvent.FirstNameAttribute),
handler.NewCol(LDAPLastNameAttributeCol, idpEvent.LastNameAttribute),
@@ -1962,29 +1965,32 @@ func reduceGoogleIDPChangedColumns(idpEvent idp.GoogleIDPChangedEvent) []handler
func reduceLDAPIDPChangedColumns(idpEvent idp.LDAPIDPChangedEvent) []handler.Column {
ldapCols := make([]handler.Column, 0, 4)
- if idpEvent.Host != nil {
- ldapCols = append(ldapCols, handler.NewCol(LDAPHostCol, *idpEvent.Host))
+ if idpEvent.Servers != nil {
+ ldapCols = append(ldapCols, handler.NewCol(LDAPServersCol, database.StringArray(idpEvent.Servers)))
}
- if idpEvent.Port != nil {
- ldapCols = append(ldapCols, handler.NewCol(LDAPPortCol, *idpEvent.Port))
- }
- if idpEvent.TLS != nil {
- ldapCols = append(ldapCols, handler.NewCol(LDAPTlsCol, *idpEvent.TLS))
+ if idpEvent.StartTLS != nil {
+ ldapCols = append(ldapCols, handler.NewCol(LDAPStartTLSCol, *idpEvent.StartTLS))
}
if idpEvent.BaseDN != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPBaseDNCol, *idpEvent.BaseDN))
}
- if idpEvent.UserObjectClass != nil {
- ldapCols = append(ldapCols, handler.NewCol(LDAPUserObjectClassCol, *idpEvent.UserObjectClass))
+ if idpEvent.BindDN != nil {
+ ldapCols = append(ldapCols, handler.NewCol(LDAPBindDNCol, *idpEvent.BindDN))
}
- if idpEvent.UserUniqueAttribute != nil {
- ldapCols = append(ldapCols, handler.NewCol(LDAPUserUniqueAttributeCol, *idpEvent.UserUniqueAttribute))
+ if idpEvent.BindPassword != nil {
+ ldapCols = append(ldapCols, handler.NewCol(LDAPBindPasswordCol, idpEvent.BindPassword))
}
- if idpEvent.Admin != nil {
- ldapCols = append(ldapCols, handler.NewCol(LDAPAdminCol, *idpEvent.Admin))
+ if idpEvent.UserBase != nil {
+ ldapCols = append(ldapCols, handler.NewCol(LDAPUserBaseCol, *idpEvent.UserBase))
}
- if idpEvent.Password != nil {
- ldapCols = append(ldapCols, handler.NewCol(LDAPPasswordCol, *idpEvent.Password))
+ if idpEvent.UserObjectClasses != nil {
+ ldapCols = append(ldapCols, handler.NewCol(LDAPUserObjectClassesCol, database.StringArray(idpEvent.UserObjectClasses)))
+ }
+ if idpEvent.UserFilters != nil {
+ ldapCols = append(ldapCols, handler.NewCol(LDAPUserFiltersCol, database.StringArray(idpEvent.UserFilters)))
+ }
+ if idpEvent.Timeout != nil {
+ ldapCols = append(ldapCols, handler.NewCol(LDAPTimeoutCol, *idpEvent.Timeout))
}
if idpEvent.IDAttribute != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPIDAttributeCol, *idpEvent.IDAttribute))
diff --git a/internal/query/projection/idp_template_test.go b/internal/query/projection/idp_template_test.go
index 6e58a76edc..0b26770a8a 100644
--- a/internal/query/projection/idp_template_test.go
+++ b/internal/query/projection/idp_template_test.go
@@ -2,6 +2,7 @@ package projection
import (
"testing"
+ "time"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
@@ -2033,18 +2034,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
[]byte(`{
"id": "idp-id",
"name": "custom-zitadel-instance",
- "host": "host",
- "port": "port",
- "tls": true,
- "baseDN": "base",
- "userObjectClass": "user",
- "userUniqueAttribute": "uid",
- "admin": "admin",
- "password": {
+ "servers": ["server"],
+ "startTls": false,
+ "baseDN": "basedn",
+ "bindDN": "binddn",
+ "bindPassword": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
+ "userBase": "user",
+ "userObjectClasses": ["object"],
+ "userFilters": ["filter"],
+ "timeout": 30000000000,
"idAttribute": "id",
"firstNameAttribute": "first",
"lastNameAttribute": "last",
@@ -2092,18 +2094,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
},
},
{
- expectedStmt: "INSERT INTO projections.idp_templates4_ldap (idp_id, instance_id, host, port, tls, base_dn, user_object_class, user_unique_attribute, admin, password, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23)",
+ expectedStmt: "INSERT INTO projections.idp_templates4_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)",
expectedArgs: []interface{}{
"idp-id",
"instance-id",
- "host",
- "port",
- true,
- "base",
- "user",
- "uid",
- "admin",
+ database.StringArray{"server"},
+ false,
+ "basedn",
+ "binddn",
anyArg{},
+ "user",
+ database.StringArray{"object"},
+ database.StringArray{"filter"},
+ time.Duration(30000000000),
"id",
"first",
"last",
@@ -2132,18 +2135,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
[]byte(`{
"id": "idp-id",
"name": "custom-zitadel-instance",
- "host": "host",
- "port": "port",
- "tls": true,
- "baseDN": "base",
- "userObjectClass": "user",
- "userUniqueAttribute": "uid",
- "admin": "admin",
- "password": {
+ "servers": ["server"],
+ "startTls": false,
+ "baseDN": "basedn",
+ "bindDN": "binddn",
+ "bindPassword": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
+ "userBase": "user",
+ "userObjectClasses": ["object"],
+ "userFilters": ["filter"],
+ "timeout": 30000000000,
"idAttribute": "id",
"firstNameAttribute": "first",
"lastNameAttribute": "last",
@@ -2191,18 +2195,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
},
},
{
- expectedStmt: "INSERT INTO projections.idp_templates4_ldap (idp_id, instance_id, host, port, tls, base_dn, user_object_class, user_unique_attribute, admin, password, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23)",
+ expectedStmt: "INSERT INTO projections.idp_templates4_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)",
expectedArgs: []interface{}{
"idp-id",
"instance-id",
- "host",
- "port",
- true,
- "base",
- "user",
- "uid",
- "admin",
+ database.StringArray{"server"},
+ false,
+ "basedn",
+ "binddn",
anyArg{},
+ "user",
+ database.StringArray{"object"},
+ database.StringArray{"filter"},
+ time.Duration(30000000000),
"id",
"first",
"last",
@@ -2231,7 +2236,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
[]byte(`{
"id": "idp-id",
"name": "custom-zitadel-instance",
- "host": "host"
+ "baseDN": "basedn"
}`),
), instance.LDAPIDPChangedEventMapper),
},
@@ -2253,9 +2258,9 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
},
},
{
- expectedStmt: "UPDATE projections.idp_templates4_ldap SET host = $1 WHERE (idp_id = $2) AND (instance_id = $3)",
+ expectedStmt: "UPDATE projections.idp_templates4_ldap2 SET base_dn = $1 WHERE (idp_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
- "host",
+ "basedn",
"idp-id",
"instance-id",
},
@@ -2273,18 +2278,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
[]byte(`{
"id": "idp-id",
"name": "custom-zitadel-instance",
- "host": "host",
- "port": "port",
- "tls": true,
- "baseDN": "base",
- "userObjectClass": "user",
- "userUniqueAttribute": "uid",
- "admin": "admin",
- "password": {
+ "servers": ["server"],
+ "startTls": false,
+ "baseDN": "basedn",
+ "bindDN": "binddn",
+ "bindPassword": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
+ "userBase": "user",
+ "userObjectClasses": ["object"],
+ "userFilters": ["filter"],
+ "timeout": 30000000000,
"idAttribute": "id",
"firstNameAttribute": "first",
"lastNameAttribute": "last",
@@ -2327,16 +2333,17 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
},
},
{
- expectedStmt: "UPDATE projections.idp_templates4_ldap SET (host, port, tls, base_dn, user_object_class, user_unique_attribute, admin, password, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) WHERE (idp_id = $22) AND (instance_id = $23)",
+ expectedStmt: "UPDATE projections.idp_templates4_ldap2 SET (servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) WHERE (idp_id = $23) AND (instance_id = $24)",
expectedArgs: []interface{}{
- "host",
- "port",
- true,
- "base",
- "user",
- "uid",
- "admin",
+ database.StringArray{"server"},
+ false,
+ "basedn",
+ "binddn",
anyArg{},
+ "user",
+ database.StringArray{"object"},
+ database.StringArray{"filter"},
+ time.Duration(30000000000),
"id",
"first",
"last",
diff --git a/internal/repository/idp/ldap.go b/internal/repository/idp/ldap.go
index 99e8bd1ff2..5115bc46ae 100644
--- a/internal/repository/idp/ldap.go
+++ b/internal/repository/idp/ldap.go
@@ -2,27 +2,28 @@ package idp
import (
"encoding/json"
+ "time"
"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"
- "github.com/zitadel/zitadel/internal/repository/idpconfig"
)
type LDAPIDPAddedEvent struct {
eventstore.BaseEvent `json:"-"`
- ID string `json:"id"`
- Name string `json:"name"`
- Host string `json:"host"`
- Port string `json:"port,omitempty"`
- TLS bool `json:"tls"`
- BaseDN string `json:"baseDN"`
- UserObjectClass string `json:"userObjectClass"`
- UserUniqueAttribute string `json:"userUniqueAttribute"`
- Admin string `json:"admin"`
- Password *crypto.CryptoValue `json:"password"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Servers []string `json:"servers"`
+ StartTLS bool `json:"startTLS"`
+ BaseDN string `json:"baseDN"`
+ BindDN string `json:"bindDN"`
+ BindPassword *crypto.CryptoValue `json:"bindPassword"`
+ UserBase string `json:"userBase"`
+ UserObjectClasses []string `json:"userObjectClasses"`
+ UserFilters []string `json:"userFilters"`
+ Timeout time.Duration `json:"timeout"`
LDAPAttributes
Options
@@ -132,33 +133,35 @@ func (o *LDAPAttributes) ReduceChanges(changes LDAPAttributeChanges) {
func NewLDAPIDPAddedEvent(
base *eventstore.BaseEvent,
- id,
- name,
- host,
- port string,
- tls bool,
- baseDN,
- userObjectClass,
- userUniqueAttribute,
- admin string,
- password *crypto.CryptoValue,
+ id string,
+ name string,
+ servers []string,
+ startTLS bool,
+ baseDN string,
+ bindDN string,
+ bindPassword *crypto.CryptoValue,
+ userBase string,
+ userObjectClasses []string,
+ userFilters []string,
+ timeout time.Duration,
attributes LDAPAttributes,
options Options,
) *LDAPIDPAddedEvent {
return &LDAPIDPAddedEvent{
- BaseEvent: *base,
- ID: id,
- Name: name,
- Host: host,
- Port: port,
- TLS: tls,
- BaseDN: baseDN,
- UserObjectClass: userObjectClass,
- UserUniqueAttribute: userUniqueAttribute,
- Admin: admin,
- Password: password,
- LDAPAttributes: attributes,
- Options: options,
+ BaseEvent: *base,
+ ID: id,
+ Name: name,
+ Servers: servers,
+ StartTLS: startTLS,
+ BaseDN: baseDN,
+ BindDN: bindDN,
+ BindPassword: bindPassword,
+ UserBase: userBase,
+ UserObjectClasses: userObjectClasses,
+ UserFilters: userFilters,
+ Timeout: timeout,
+ LDAPAttributes: attributes,
+ Options: options,
}
}
@@ -167,7 +170,7 @@ func (e *LDAPIDPAddedEvent) Data() interface{} {
}
func (e *LDAPIDPAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
- return []*eventstore.EventUniqueConstraint{idpconfig.NewAddIDPConfigNameUniqueConstraint(e.Name, e.Aggregate().ResourceOwner)}
+ return nil
}
func LDAPIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
@@ -186,18 +189,17 @@ func LDAPIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error)
type LDAPIDPChangedEvent struct {
eventstore.BaseEvent `json:"-"`
- oldName string
-
- ID string `json:"id"`
- Name *string `json:"name,omitempty"`
- Host *string `json:"host,omitempty"`
- Port *string `json:"port,omitempty"`
- TLS *bool `json:"tls,omitempty"`
- BaseDN *string `json:"baseDN,omitempty"`
- UserObjectClass *string `json:"userObjectClass,omitempty"`
- UserUniqueAttribute *string `json:"userUniqueAttribute,omitempty"`
- Admin *string `json:"admin,omitempty"`
- Password *crypto.CryptoValue `json:"password,omitempty"`
+ ID string `json:"id"`
+ Name *string `json:"name,omitempty"`
+ Servers []string `json:"servers,omitempty"`
+ StartTLS *bool `json:"startTLS,omitempty"`
+ BaseDN *string `json:"baseDN,omitempty"`
+ BindDN *string `json:"bindDN,omitempty"`
+ BindPassword *crypto.CryptoValue `json:"bindPassword,omitempty"`
+ UserBase *string `json:"userBase,omitempty"`
+ UserObjectClasses []string `json:"userObjectClasses,omitempty"`
+ UserFilters []string `json:"userFilters,omitempty"`
+ Timeout *time.Duration `json:"timeout,omitempty"`
LDAPAttributeChanges
OptionChanges
@@ -238,7 +240,6 @@ func (o LDAPAttributeChanges) IsZero() bool {
func NewLDAPIDPChangedEvent(
base *eventstore.BaseEvent,
id string,
- oldName string,
changes []LDAPIDPChanges,
) (*LDAPIDPChangedEvent, error) {
if len(changes) == 0 {
@@ -247,7 +248,6 @@ func NewLDAPIDPChangedEvent(
changedEvent := &LDAPIDPChangedEvent{
BaseEvent: *base,
ID: id,
- oldName: oldName,
}
for _, change := range changes {
change(changedEvent)
@@ -263,51 +263,57 @@ func ChangeLDAPName(name string) func(*LDAPIDPChangedEvent) {
}
}
-func ChangeLDAPHost(host string) func(*LDAPIDPChangedEvent) {
+func ChangeLDAPServers(servers []string) func(*LDAPIDPChangedEvent) {
return func(e *LDAPIDPChangedEvent) {
- e.Host = &host
+ e.Servers = servers
}
}
-func ChangeLDAPPort(port string) func(*LDAPIDPChangedEvent) {
+func ChangeLDAPStartTLS(startTls bool) func(*LDAPIDPChangedEvent) {
return func(e *LDAPIDPChangedEvent) {
- e.Port = &port
+ e.StartTLS = &startTls
}
}
-func ChangeLDAPTLS(tls bool) func(*LDAPIDPChangedEvent) {
+func ChangeLDAPBaseDN(baseDN string) func(*LDAPIDPChangedEvent) {
return func(e *LDAPIDPChangedEvent) {
- e.TLS = &tls
+ e.BaseDN = &baseDN
}
}
-func ChangeLDAPBaseDN(basDN string) func(*LDAPIDPChangedEvent) {
+func ChangeLDAPBindDN(bindDN string) func(*LDAPIDPChangedEvent) {
return func(e *LDAPIDPChangedEvent) {
- e.BaseDN = &basDN
+ e.BindDN = &bindDN
}
}
-func ChangeLDAPUserObjectClass(userObjectClass string) func(*LDAPIDPChangedEvent) {
+func ChangeLDAPBindPassword(password *crypto.CryptoValue) func(*LDAPIDPChangedEvent) {
return func(e *LDAPIDPChangedEvent) {
- e.UserObjectClass = &userObjectClass
+ e.BindPassword = password
}
}
-func ChangeLDAPUserUniqueAttribute(userUniqueAttribute string) func(*LDAPIDPChangedEvent) {
+func ChangeLDAPUserBase(userBase string) func(*LDAPIDPChangedEvent) {
return func(e *LDAPIDPChangedEvent) {
- e.UserUniqueAttribute = &userUniqueAttribute
+ e.UserBase = &userBase
}
}
-func ChangeLDAPAdmin(admin string) func(*LDAPIDPChangedEvent) {
+func ChangeLDAPUserObjectClasses(objectClasses []string) func(*LDAPIDPChangedEvent) {
return func(e *LDAPIDPChangedEvent) {
- e.Admin = &admin
+ e.UserObjectClasses = objectClasses
}
}
-func ChangeLDAPPassword(password *crypto.CryptoValue) func(*LDAPIDPChangedEvent) {
+func ChangeLDAPUserFilters(userFilters []string) func(*LDAPIDPChangedEvent) {
return func(e *LDAPIDPChangedEvent) {
- e.Password = password
+ e.UserFilters = userFilters
+ }
+}
+
+func ChangeLDAPTimeout(timeout time.Duration) func(*LDAPIDPChangedEvent) {
+ return func(e *LDAPIDPChangedEvent) {
+ e.Timeout = &timeout
}
}
@@ -328,13 +334,7 @@ func (e *LDAPIDPChangedEvent) Data() interface{} {
}
func (e *LDAPIDPChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
- if e.Name == nil || e.oldName == *e.Name { // TODO: nil check should be enough?
- return nil
- }
- return []*eventstore.EventUniqueConstraint{
- idpconfig.NewRemoveIDPConfigNameUniqueConstraint(e.oldName, e.Aggregate().ResourceOwner),
- idpconfig.NewAddIDPConfigNameUniqueConstraint(*e.Name, e.Aggregate().ResourceOwner),
- }
+ return nil
}
func LDAPIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
diff --git a/internal/repository/instance/idp.go b/internal/repository/instance/idp.go
index bf34360e7b..612933f0ee 100644
--- a/internal/repository/instance/idp.go
+++ b/internal/repository/instance/idp.go
@@ -2,6 +2,7 @@ package instance
import (
"context"
+ "time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -28,8 +29,8 @@ const (
GitLabSelfHostedIDPChangedEventType eventstore.EventType = "instance.idp.gitlab_self_hosted.changed"
GoogleIDPAddedEventType eventstore.EventType = "instance.idp.google.added"
GoogleIDPChangedEventType eventstore.EventType = "instance.idp.google.changed"
- LDAPIDPAddedEventType eventstore.EventType = "instance.idp.ldap.added"
- LDAPIDPChangedEventType eventstore.EventType = "instance.idp.ldap.changed"
+ LDAPIDPAddedEventType eventstore.EventType = "instance.idp.ldap.v2.added"
+ LDAPIDPChangedEventType eventstore.EventType = "instance.idp.ldap.v2.changed"
IDPRemovedEventType eventstore.EventType = "instance.idp.removed"
)
@@ -751,15 +752,16 @@ func NewLDAPIDPAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
- name,
- host,
- port string,
- tls bool,
- baseDN,
- userObjectClass,
- userUniqueAttribute,
- admin string,
- password *crypto.CryptoValue,
+ name string,
+ servers []string,
+ startTLS bool,
+ baseDN string,
+ bindDN string,
+ bindPassword *crypto.CryptoValue,
+ userBase string,
+ userObjectClasses []string,
+ userFilters []string,
+ timeout time.Duration,
attributes idp.LDAPAttributes,
options idp.Options,
) *LDAPIDPAddedEvent {
@@ -773,14 +775,15 @@ func NewLDAPIDPAddedEvent(
),
id,
name,
- host,
- port,
- tls,
+ servers,
+ startTLS,
baseDN,
- userObjectClass,
- userUniqueAttribute,
- admin,
- password,
+ bindDN,
+ bindPassword,
+ userBase,
+ userObjectClasses,
+ userFilters,
+ timeout,
attributes,
options,
),
@@ -803,8 +806,7 @@ type LDAPIDPChangedEvent struct {
func NewLDAPIDPChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
- id,
- oldName string,
+ id string,
changes []idp.LDAPIDPChanges,
) (*LDAPIDPChangedEvent, error) {
@@ -815,7 +817,6 @@ func NewLDAPIDPChangedEvent(
LDAPIDPChangedEventType,
),
id,
- oldName,
changes,
)
if err != nil {
diff --git a/internal/repository/org/idp.go b/internal/repository/org/idp.go
index a5a0204b1a..97b0ebffc1 100644
--- a/internal/repository/org/idp.go
+++ b/internal/repository/org/idp.go
@@ -2,6 +2,7 @@ package org
import (
"context"
+ "time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -751,15 +752,16 @@ func NewLDAPIDPAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
- name,
- host,
- port string,
- tls bool,
- baseDN,
- userObjectClass,
- userUniqueAttribute,
- admin string,
- password *crypto.CryptoValue,
+ name string,
+ servers []string,
+ startTLS bool,
+ baseDN string,
+ bindDN string,
+ bindPassword *crypto.CryptoValue,
+ userBase string,
+ userObjectClasses []string,
+ userFilters []string,
+ timeout time.Duration,
attributes idp.LDAPAttributes,
options idp.Options,
) *LDAPIDPAddedEvent {
@@ -773,14 +775,15 @@ func NewLDAPIDPAddedEvent(
),
id,
name,
- host,
- port,
- tls,
+ servers,
+ startTLS,
baseDN,
- userObjectClass,
- userUniqueAttribute,
- admin,
- password,
+ bindDN,
+ bindPassword,
+ userBase,
+ userObjectClasses,
+ userFilters,
+ timeout,
attributes,
options,
),
@@ -803,8 +806,7 @@ type LDAPIDPChangedEvent struct {
func NewLDAPIDPChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
- id,
- oldName string,
+ id string,
changes []idp.LDAPIDPChanges,
) (*LDAPIDPChangedEvent, error) {
@@ -815,7 +817,6 @@ func NewLDAPIDPChangedEvent(
LDAPIDPChangedEventType,
),
id,
- oldName,
changes,
)
if err != nil {
diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto
index fd0cab5ee7..beae4750ec 100644
--- a/proto/zitadel/admin.proto
+++ b/proto/zitadel/admin.proto
@@ -4732,16 +4732,17 @@ message UpdateGoogleProviderResponse {
message AddLDAPProviderRequest {
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string host = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string port = 3 [(validate.rules).string = {max_len: 5}];
- bool tls = 4;
- string base_dn = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string user_object_class = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string user_unique_attribute = 7 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string admin = 8 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string password = 9 [(validate.rules).string = {min_len: 1, max_len: 200}];
- zitadel.idp.v1.LDAPAttributes attributes = 10;
- zitadel.idp.v1.Options provider_options = 11;
+ repeated string servers = 2 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
+ bool start_tls = 3;
+ string base_dn = 4 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string bind_dn = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string bind_password = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string user_base = 7 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ repeated string user_object_classes = 8 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
+ repeated string user_filters = 9 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
+ google.protobuf.Duration timeout = 10;
+ zitadel.idp.v1.LDAPAttributes attributes = 11;
+ zitadel.idp.v1.Options provider_options = 12;
}
message AddLDAPProviderResponse {
@@ -4752,16 +4753,17 @@ message AddLDAPProviderResponse {
message UpdateLDAPProviderRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string host = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string port = 4 [(validate.rules).string = {max_len: 5}];
- bool tls = 5;
- string base_dn = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string user_object_class = 7 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string user_unique_attribute = 8 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string admin = 9 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string password = 10 [(validate.rules).string = {max_len: 200}];
- zitadel.idp.v1.LDAPAttributes attributes = 11;
- zitadel.idp.v1.Options provider_options = 12;
+ repeated string servers = 3 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
+ bool start_tls = 4;
+ string base_dn = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string bind_dn = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string bind_password = 7 [(validate.rules).string = {max_len: 200}];
+ string user_base = 8 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ repeated string user_object_classes = 9 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
+ repeated string user_filters = 10 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
+ google.protobuf.Duration timeout = 11;
+ zitadel.idp.v1.LDAPAttributes attributes = 12;
+ zitadel.idp.v1.Options provider_options = 13;
}
message UpdateLDAPProviderResponse {
diff --git a/proto/zitadel/idp.proto b/proto/zitadel/idp.proto
index 9cf248c7c2..fb9c154391 100644
--- a/proto/zitadel/idp.proto
+++ b/proto/zitadel/idp.proto
@@ -3,6 +3,7 @@ syntax = "proto3";
import "zitadel/object.proto";
import "validate/validate.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
+import "google/protobuf/duration.proto";
package zitadel.idp.v1;
@@ -321,15 +322,15 @@ message GitLabSelfHostedConfig {
}
message LDAPConfig {
- string host = 1;
- string port = 2;
- bool tls = 3;
- string base_dn = 4;
- string user_object_class = 5;
- string user_unique_attribute = 6;
- string admin = 7;
- LDAPAttributes attributes = 8;
- Options provider_options = 9;
+ repeated string servers = 1;
+ bool start_tls = 2;
+ string base_dn = 3;
+ string bind_dn = 4;
+ string user_base = 5;
+ repeated string user_object_classes = 6;
+ repeated string user_filters = 7;
+ google.protobuf.Duration timeout = 8;
+ LDAPAttributes attributes = 9;
}
message AzureADConfig {
diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto
index 788340a69f..73fe75eb4a 100644
--- a/proto/zitadel/management.proto
+++ b/proto/zitadel/management.proto
@@ -11406,16 +11406,17 @@ message UpdateGoogleProviderResponse {
message AddLDAPProviderRequest {
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string host = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string port = 3 [(validate.rules).string = {max_len: 5}];
- bool tls = 4;
- string base_dn = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string user_object_class = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string user_unique_attribute = 7 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string admin = 8 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string password = 9 [(validate.rules).string = {min_len: 1, max_len: 200}];
- zitadel.idp.v1.LDAPAttributes attributes = 10;
- zitadel.idp.v1.Options provider_options = 11;
+ repeated string servers = 2 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
+ bool start_tls = 3;
+ string base_dn = 4 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string bind_dn = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string bind_password = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string user_base = 7 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ repeated string user_object_classes = 8 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
+ repeated string user_filters = 9 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
+ google.protobuf.Duration timeout = 10;
+ zitadel.idp.v1.LDAPAttributes attributes = 11;
+ zitadel.idp.v1.Options provider_options = 12;
}
message AddLDAPProviderResponse {
@@ -11426,16 +11427,17 @@ message AddLDAPProviderResponse {
message UpdateLDAPProviderRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string host = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string port = 4 [(validate.rules).string = {max_len: 5}];
- bool tls = 5;
- string base_dn = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string user_object_class = 7 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string user_unique_attribute = 8 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string admin = 9 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string password = 10 [(validate.rules).string = {max_len: 200}];
- zitadel.idp.v1.LDAPAttributes attributes = 11;
- zitadel.idp.v1.Options provider_options = 12;
+ repeated string servers = 3 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
+ bool start_tls = 4;
+ string base_dn = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string bind_dn = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string bind_password = 7 [(validate.rules).string = {max_len: 200}];
+ string user_base = 8 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ repeated string user_object_classes = 9 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
+ repeated string user_filters = 10 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
+ google.protobuf.Duration timeout = 11;
+ zitadel.idp.v1.LDAPAttributes attributes = 12;
+ zitadel.idp.v1.Options provider_options = 13;
}
message UpdateLDAPProviderResponse {