feat: ldap provider login (#5448)

Add the logic to configure and use LDAP provider as an external IDP with a dedicated login GUI.
This commit is contained in:
Stefan Benz 2023-03-24 16:18:56 +01:00 committed by GitHub
parent a8bfcc166e
commit 41ff0bbc63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 2240 additions and 1142 deletions

2
go.mod
View File

@ -53,7 +53,7 @@ require (
github.com/sony/sonyflake v1.1.0 github.com/sony/sonyflake v1.1.0
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.15.0 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/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203
github.com/ttacon/libphonenumber v1.2.1 github.com/ttacon/libphonenumber v1.2.1
github.com/zitadel/logging v0.3.4 github.com/zitadel/logging v0.3.4

3
go.sum
View File

@ -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.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.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.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.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.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 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=

View File

@ -407,32 +407,34 @@ func updateGoogleProviderToCommand(req *admin_pb.UpdateGoogleProviderRequest) co
func addLDAPProviderToCommand(req *admin_pb.AddLDAPProviderRequest) command.LDAPProvider { func addLDAPProviderToCommand(req *admin_pb.AddLDAPProviderRequest) command.LDAPProvider {
return command.LDAPProvider{ return command.LDAPProvider{
Name: req.Name, Name: req.Name,
Host: req.Host, Servers: req.Servers,
Port: req.Port, StartTLS: req.StartTls,
TLS: req.Tls, BaseDN: req.BaseDn,
BaseDN: req.BaseDn, BindDN: req.BindDn,
UserObjectClass: req.UserObjectClass, BindPassword: req.BindPassword,
UserUniqueAttribute: req.UserUniqueAttribute, UserBase: req.UserBase,
Admin: req.Admin, UserObjectClasses: req.UserObjectClasses,
Password: req.Password, UserFilters: req.UserFilters,
LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes), Timeout: req.Timeout.AsDuration(),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
} }
} }
func updateLDAPProviderToCommand(req *admin_pb.UpdateLDAPProviderRequest) command.LDAPProvider { func updateLDAPProviderToCommand(req *admin_pb.UpdateLDAPProviderRequest) command.LDAPProvider {
return command.LDAPProvider{ return command.LDAPProvider{
Name: req.Name, Name: req.Name,
Host: req.Host, Servers: req.Servers,
Port: req.Port, StartTLS: req.StartTls,
TLS: req.Tls, BaseDN: req.BaseDn,
BaseDN: req.BaseDn, BindDN: req.BindDn,
UserObjectClass: req.UserObjectClass, BindPassword: req.BindPassword,
UserUniqueAttribute: req.UserUniqueAttribute, UserBase: req.UserBase,
Admin: req.Admin, UserObjectClasses: req.UserObjectClasses,
Password: req.Password, UserFilters: req.UserFilters,
LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes), Timeout: req.Timeout.AsDuration(),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
} }
} }

View File

@ -1,6 +1,8 @@
package idp package idp
import ( import (
"google.golang.org/protobuf/types/known/durationpb"
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object" obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
iam_model "github.com/zitadel/zitadel/internal/iam/model" 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) { 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{ providerConfig.Config = &idp_pb.ProviderConfig_Ldap{
Ldap: &idp_pb.LDAPConfig{ Ldap: &idp_pb.LDAPConfig{
Host: template.Host, Servers: template.Servers,
Port: template.Port, StartTls: template.StartTLS,
Tls: template.TLS, BaseDn: template.BaseDN,
BaseDn: template.BaseDN, BindDn: template.BindDN,
UserObjectClass: template.UserObjectClass, UserBase: template.UserBase,
UserUniqueAttribute: template.UserUniqueAttribute, UserObjectClasses: template.UserObjectClasses,
Admin: template.Admin, UserFilters: template.UserFilters,
Attributes: ldapAttributesToPb(template.LDAPAttributes), Timeout: timeout,
Attributes: ldapAttributesToPb(template.LDAPAttributes),
}, },
} }
} }

View File

@ -422,32 +422,34 @@ func updateGoogleProviderToCommand(req *mgmt_pb.UpdateGoogleProviderRequest) com
func addLDAPProviderToCommand(req *mgmt_pb.AddLDAPProviderRequest) command.LDAPProvider { func addLDAPProviderToCommand(req *mgmt_pb.AddLDAPProviderRequest) command.LDAPProvider {
return command.LDAPProvider{ return command.LDAPProvider{
Name: req.Name, Name: req.Name,
Host: req.Host, Servers: req.Servers,
Port: req.Port, StartTLS: req.StartTls,
TLS: req.Tls, BaseDN: req.BaseDn,
BaseDN: req.BaseDn, BindDN: req.BindDn,
UserObjectClass: req.UserObjectClass, BindPassword: req.BindPassword,
UserUniqueAttribute: req.UserUniqueAttribute, UserBase: req.UserBase,
Admin: req.Admin, UserObjectClasses: req.UserObjectClasses,
Password: req.Password, UserFilters: req.UserFilters,
LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes), Timeout: req.Timeout.AsDuration(),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
} }
} }
func updateLDAPProviderToCommand(req *mgmt_pb.UpdateLDAPProviderRequest) command.LDAPProvider { func updateLDAPProviderToCommand(req *mgmt_pb.UpdateLDAPProviderRequest) command.LDAPProvider {
return command.LDAPProvider{ return command.LDAPProvider{
Name: req.Name, Name: req.Name,
Host: req.Host, Servers: req.Servers,
Port: req.Port, StartTLS: req.StartTls,
TLS: req.Tls, BaseDN: req.BaseDn,
BaseDN: req.BaseDn, BindDN: req.BindDn,
UserObjectClass: req.UserObjectClass, BindPassword: req.BindPassword,
UserUniqueAttribute: req.UserUniqueAttribute, UserBase: req.UserBase,
Admin: req.Admin, UserObjectClasses: req.UserObjectClasses,
Password: req.Password, UserFilters: req.UserFilters,
LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes), Timeout: req.Timeout.AsDuration(),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
} }
} }

View File

@ -23,6 +23,7 @@ import (
"github.com/zitadel/zitadel/internal/idp/providers/gitlab" "github.com/zitadel/zitadel/internal/idp/providers/gitlab"
"github.com/zitadel/zitadel/internal/idp/providers/google" "github.com/zitadel/zitadel/internal/idp/providers/google"
"github.com/zitadel/zitadel/internal/idp/providers/jwt" "github.com/zitadel/zitadel/internal/idp/providers/jwt"
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
"github.com/zitadel/zitadel/internal/idp/providers/oauth" "github.com/zitadel/zitadel/internal/idp/providers/oauth"
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc" openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
"github.com/zitadel/zitadel/internal/query" "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) provider, err = l.gitlabSelfHostedProvider(r.Context(), identityProvider)
case domain.IDPTypeGoogle: case domain.IDPTypeGoogle:
provider, err = l.googleProvider(r.Context(), identityProvider) provider, err = l.googleProvider(r.Context(), identityProvider)
case domain.IDPTypeLDAP, case domain.IDPTypeLDAP:
domain.IDPTypeUnspecified: provider, err = l.ldapProvider(r.Context(), identityProvider)
case domain.IDPTypeUnspecified:
fallthrough fallthrough
default: default:
l.renderLogin(w, r, authReq, errors.ThrowInvalidArgument(nil, "LOGIN-AShek", "Errors.ExternalIDP.IDPTypeNotImplemented")) 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 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) { 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) { 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) logging.Errorf("token exchanged failed: %s - %s (state: %s)", errorType, errorType, state)

View File

@ -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)
}

View File

@ -76,6 +76,7 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
tmplLinkUsersDone: "link_users_done.html", tmplLinkUsersDone: "link_users_done.html",
tmplExternalNotFoundOption: "external_not_found_option.html", tmplExternalNotFoundOption: "external_not_found_option.html",
tmplLoginSuccess: "login_success.html", tmplLoginSuccess: "login_success.html",
tmplLDAPLogin: "ldap_login.html",
} }
funcs := map[string]interface{}{ funcs := map[string]interface{}{
"resourceUrl": func(file string) string { "resourceUrl": func(file string) string {
@ -219,6 +220,9 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
"idpProviderClass": func(idpType domain.IDPType) string { "idpProviderClass": func(idpType domain.IDPType) string {
return idpType.GetCSSClass() return idpType.GetCSSClass()
}, },
"ldapUrl": func() string {
return path.Join(r.pathPrefix, EndpointLDAPCallback)
},
} }
var err error var err error
r.Renderer, err = renderer.NewRenderer( r.Renderer, err = renderer.NewRenderer(

View File

@ -15,6 +15,8 @@ const (
EndpointExternalLoginCallback = "/login/externalidp/callback" EndpointExternalLoginCallback = "/login/externalidp/callback"
EndpointJWTAuthorize = "/login/jwt/authorize" EndpointJWTAuthorize = "/login/jwt/authorize"
EndpointJWTCallback = "/login/jwt/callback" EndpointJWTCallback = "/login/jwt/callback"
EndpointLDAPLogin = "/login/ldap"
EndpointLDAPCallback = "/login/ldap/callback"
EndpointPasswordlessLogin = "/login/passwordless" EndpointPasswordlessLogin = "/login/passwordless"
EndpointPasswordlessRegistration = "/login/passwordless/init" EndpointPasswordlessRegistration = "/login/passwordless/init"
EndpointPasswordlessPrompt = "/login/passwordless/prompt" 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.handleRegisterOrg).Methods(http.MethodGet)
router.HandleFunc(EndpointRegisterOrg, login.handleRegisterOrgCheck).Methods(http.MethodPost) router.HandleFunc(EndpointRegisterOrg, login.handleRegisterOrgCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointLoginSuccess, login.handleLoginSuccess).Methods(http.MethodGet) 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)) router.SkipClean(true).Handle("", http.RedirectHandler(HandlerPrefix+"/", http.StatusMovedPermanently))
return router return router
} }

View File

@ -11,6 +11,13 @@ Login:
RegisterButtonText: registrieren RegisterButtonText: registrieren
NextButtonText: weiter NextButtonText: weiter
LDAP:
Title: Anmeldung
Description: Mit Konto anmelden.
LoginNameLabel: Loginname
PasswordLabel: Passwort
NextButtonText: weiter
SelectAccount: SelectAccount:
Title: Account auswählen Title: Account auswählen
Description: Wähle deinen Account aus. Description: Wähle deinen Account aus.

View File

@ -11,6 +11,13 @@ Login:
RegisterButtonText: register RegisterButtonText: register
NextButtonText: next NextButtonText: next
LDAP:
Title: Login
Description: Enter your login data.
LoginNameLabel: Loginname
PasswordLabel: Password
NextButtonText: next
SelectAccount: SelectAccount:
Title: Select account Title: Select account
Description: Use your ZITADEL-Account Description: Use your ZITADEL-Account

View File

@ -11,6 +11,13 @@ Login:
RegisterButtonText: s'inscrire RegisterButtonText: s'inscrire
NextButtonText: suivant NextButtonText: suivant
LDAP:
Title: Connexion
Description: Entrez vos données de connexion.
LoginNameLabel: Identifiant
PasswordLabel: Mot de passe
NextButtonText: suivant
SelectAccount: SelectAccount:
Title: Sélectionner un compte Title: Sélectionner un compte
Description: Utilisez votre compte ZITADEL Description: Utilisez votre compte ZITADEL

View File

@ -11,6 +11,13 @@ Login:
RegisterButtonText: registrare RegisterButtonText: registrare
NextButtonText: Avanti NextButtonText: Avanti
LDAP:
Title: Accesso
Description: Inserisci i tuoi dati di accesso.
LoginNameLabel: Nome di accesso
PasswordLabel: Password
NextButtonText: Avanti
SelectAccount: SelectAccount:
Title: Seleziona l'account Title: Seleziona l'account
Description: Usa il tuo account ZITADEL Description: Usa il tuo account ZITADEL

View File

@ -11,6 +11,13 @@ Login:
RegisterButtonText: zarejestruj RegisterButtonText: zarejestruj
NextButtonText: dalej NextButtonText: dalej
LDAP:
Title: Rejestracja
Description: Wprowadź swoje dane logowania.
LoginNameLabel: Nazwa użytkownika
PasswordLabel: Hasło
NextButtonText: dalej
SelectAccount: SelectAccount:
Title: Wybierz konto Title: Wybierz konto
Description: Użyj swojego konta ZITADEL Description: Użyj swojego konta ZITADEL

View File

@ -11,6 +11,13 @@ Login:
RegisterButtonText: 注册 RegisterButtonText: 注册
NextButtonText: 继续 NextButtonText: 继续
LDAP:
Title: 注册
Description: 输入您的登录数据。
LoginNameLabel: 登录名
PasswordLabel: 密码
NextButtonText: 继续
SelectAccount: SelectAccount:
Title: 选择账户 Title: 选择账户
Description: 使用您的 ZITADEL 帐户 Description: 使用您的 ZITADEL 帐户

View File

@ -0,0 +1,40 @@
{{template "main-top" .}}
<div class="lgn-head">
<h1>{{t "LDAP.Title"}}</h1>
<p>{{t "LDAP.Description"}}</p>
</div>
<form action="{{ ldapUrl }}" method="POST">
{{ .CSRF }}
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}"/>
<div class="fields">
<label class="lgn-label" for="ldapusername">{{t "LDAP.LoginNameLabel"}}</label>
<input class="lgn-input" type="text" id="ldapusername" name="ldapusername" autocomplete="username" autofocus required>
</div>
<div class="fields">
<label class="lgn-label" for="ldappassword">{{t "LDAP.PasswordLabel"}}</label>
<input class="lgn-input" type="password" id="ldappassword" name="ldappassword" autocomplete="current-password" required>
</div>
{{template "error-message" .}}
<div class="lgn-actions lgn-reverse-order">
<button class="lgn-raised-button lgn-primary lgn-initial-focus" id="submit-button" type="submit">
{{t "LDAP.NextButtonText"}}
</button>
<span class="fill-space"></span>
<button class="lgn-icon-button lgn-left-action" name="resetexternalidp" value="true" formnovalidate>
<i class="lgn-icon-arrow-left-solid"></i>
</button>
</div>
</form>
<script src="{{ resourceUrl " scripts/form_submit.js" }}"></script>
<script src="{{ resourceUrl " scripts/default_form_validation.js" }}"></script>
{{template "main-bottom" .}}

View File

@ -34,4 +34,5 @@ type AuthRequestRepository interface {
LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *domain.BrowserInfo) error 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 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 ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error
ResetSelectedIDP(ctx context.Context, authReqID, userAgentID string) error
} }

View File

@ -461,6 +461,15 @@ func (repo *AuthRequestRepo) ResetLinkingUsers(ctx context.Context, authReqID, u
return repo.AuthRequests.UpdateAuthRequest(ctx, request) 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) { 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) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()

View File

@ -2,6 +2,7 @@ package command
import ( import (
"context" "context"
"time"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/command/preparation"
@ -94,17 +95,18 @@ type GoogleProvider struct {
} }
type LDAPProvider struct { type LDAPProvider struct {
Name string Name string
Host string Servers []string
Port string StartTLS bool
TLS bool BaseDN string
BaseDN string BindDN string
UserObjectClass string BindPassword string
UserUniqueAttribute string UserBase string
Admin string UserObjectClasses []string
Password string UserFilters []string
LDAPAttributes idp.LDAPAttributes Timeout time.Duration
IDPOptions idp.Options LDAPAttributes idp.LDAPAttributes
IDPOptions idp.Options
} }
func ExistsIDP(ctx context.Context, filter preparation.FilterToQueryReducer, id, orgID string) (exists bool, err error) { func ExistsIDP(ctx context.Context, filter preparation.FilterToQueryReducer, id, orgID string) (exists bool, err error) {

View File

@ -2,6 +2,7 @@ package command
import ( import (
"reflect" "reflect"
"time"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
@ -999,16 +1000,17 @@ func (wm *GoogleIDPWriteModel) NewChanges(
type LDAPIDPWriteModel struct { type LDAPIDPWriteModel struct {
eventstore.WriteModel eventstore.WriteModel
ID string ID string
Name string Name string
Host string Servers []string
Port string StartTLS bool
TLS bool BaseDN string
BaseDN string BindDN string
UserObjectClass string BindPassword *crypto.CryptoValue
UserUniqueAttribute string UserBase string
Admin string UserObjectClasses []string
Password *crypto.CryptoValue UserFilters []string
Timeout time.Duration
idp.LDAPAttributes idp.LDAPAttributes
idp.Options idp.Options
@ -1040,14 +1042,15 @@ func (wm *LDAPIDPWriteModel) Reduce() error {
func (wm *LDAPIDPWriteModel) reduceAddedEvent(e *idp.LDAPIDPAddedEvent) { func (wm *LDAPIDPWriteModel) reduceAddedEvent(e *idp.LDAPIDPAddedEvent) {
wm.Name = e.Name wm.Name = e.Name
wm.Host = e.Host wm.Servers = e.Servers
wm.Port = e.Port wm.StartTLS = e.StartTLS
wm.TLS = e.TLS
wm.BaseDN = e.BaseDN wm.BaseDN = e.BaseDN
wm.UserObjectClass = e.UserObjectClass wm.BindDN = e.BindDN
wm.UserUniqueAttribute = e.UserUniqueAttribute wm.BindPassword = e.BindPassword
wm.Admin = e.Admin wm.UserBase = e.UserBase
wm.Password = e.Password wm.UserObjectClasses = e.UserObjectClasses
wm.UserFilters = e.UserFilters
wm.Timeout = e.Timeout
wm.LDAPAttributes = e.LDAPAttributes wm.LDAPAttributes = e.LDAPAttributes
wm.Options = e.Options wm.Options = e.Options
wm.State = domain.IDPStateActive wm.State = domain.IDPStateActive
@ -1060,44 +1063,48 @@ func (wm *LDAPIDPWriteModel) reduceChangedEvent(e *idp.LDAPIDPChangedEvent) {
if e.Name != nil { if e.Name != nil {
wm.Name = *e.Name wm.Name = *e.Name
} }
if e.Host != nil { if e.Servers != nil {
wm.Host = *e.Host wm.Servers = e.Servers
} }
if e.Port != nil { if e.StartTLS != nil {
wm.Port = *e.Port wm.StartTLS = *e.StartTLS
}
if e.TLS != nil {
wm.TLS = *e.TLS
} }
if e.BaseDN != nil { if e.BaseDN != nil {
wm.BaseDN = *e.BaseDN wm.BaseDN = *e.BaseDN
} }
if e.UserObjectClass != nil { if e.BindDN != nil {
wm.UserObjectClass = *e.UserObjectClass wm.BindDN = *e.BindDN
} }
if e.UserUniqueAttribute != nil { if e.BindPassword != nil {
wm.UserUniqueAttribute = *e.UserUniqueAttribute wm.BindPassword = e.BindPassword
} }
if e.Admin != nil { if e.UserBase != nil {
wm.Admin = *e.Admin wm.UserBase = *e.UserBase
} }
if e.Password != nil { if e.UserObjectClasses != nil {
wm.Password = e.Password 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.LDAPAttributes.ReduceChanges(e.LDAPAttributeChanges)
wm.Options.ReduceChanges(e.OptionChanges) wm.Options.ReduceChanges(e.OptionChanges)
} }
func (wm *LDAPIDPWriteModel) NewChanges( func (wm *LDAPIDPWriteModel) NewChanges(
name, name string,
host, servers []string,
port string, startTLS bool,
tls bool, baseDN string,
baseDN, bindDN string,
userObjectClass, bindPassword string,
userUniqueAttribute, userBase string,
admin string, userObjectClasses []string,
password string, userFilters []string,
timeout time.Duration,
secretCrypto crypto.Crypto, secretCrypto crypto.Crypto,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
@ -1105,36 +1112,39 @@ func (wm *LDAPIDPWriteModel) NewChanges(
changes := make([]idp.LDAPIDPChanges, 0) changes := make([]idp.LDAPIDPChanges, 0)
var cryptedPassword *crypto.CryptoValue var cryptedPassword *crypto.CryptoValue
var err error var err error
if password != "" { if bindPassword != "" {
cryptedPassword, err = crypto.Crypt([]byte(password), secretCrypto) cryptedPassword, err = crypto.Crypt([]byte(bindPassword), secretCrypto)
if err != nil { if err != nil {
return nil, err return nil, err
} }
changes = append(changes, idp.ChangeLDAPPassword(cryptedPassword)) changes = append(changes, idp.ChangeLDAPBindPassword(cryptedPassword))
} }
if wm.Name != name { if wm.Name != name {
changes = append(changes, idp.ChangeLDAPName(name)) changes = append(changes, idp.ChangeLDAPName(name))
} }
if wm.Host != host { if !reflect.DeepEqual(wm.Servers, servers) {
changes = append(changes, idp.ChangeLDAPHost(host)) changes = append(changes, idp.ChangeLDAPServers(servers))
} }
if wm.Port != port { if wm.StartTLS != startTLS {
changes = append(changes, idp.ChangeLDAPPort(port)) changes = append(changes, idp.ChangeLDAPStartTLS(startTLS))
}
if wm.TLS != tls {
changes = append(changes, idp.ChangeLDAPTLS(tls))
} }
if wm.BaseDN != baseDN { if wm.BaseDN != baseDN {
changes = append(changes, idp.ChangeLDAPBaseDN(baseDN)) changes = append(changes, idp.ChangeLDAPBaseDN(baseDN))
} }
if wm.UserObjectClass != userObjectClass { if wm.BindDN != bindDN {
changes = append(changes, idp.ChangeLDAPUserObjectClass(userObjectClass)) changes = append(changes, idp.ChangeLDAPBindDN(bindDN))
} }
if wm.UserUniqueAttribute != userUniqueAttribute { if wm.UserBase != userBase {
changes = append(changes, idp.ChangeLDAPUserUniqueAttribute(userUniqueAttribute)) changes = append(changes, idp.ChangeLDAPUserBase(userBase))
} }
if wm.Admin != admin { if !reflect.DeepEqual(wm.UserObjectClasses, userObjectClasses) {
changes = append(changes, idp.ChangeLDAPAdmin(admin)) 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) attrs := wm.LDAPAttributes.Changes(attributes)
if !attrs.IsZero() { if !attrs.IsZero() {

View File

@ -1278,23 +1278,26 @@ func (c *Commands) prepareAddInstanceLDAPProvider(a *instance.Aggregate, writeMo
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" { if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SAfdd", "Errors.Invalid.Argument") 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 == "" { if provider.BaseDN = strings.TrimSpace(provider.BaseDN); provider.BaseDN == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-sv31s", "Errors.Invalid.Argument") 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") 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") 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") return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SAD5n", "Errors.Invalid.Argument")
} }
if provider.Password = strings.TrimSpace(provider.Password); provider.Password == "" { if len(provider.Servers) == 0 {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-sdf5h", "Errors.Invalid.Argument") 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) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query()) events, err := filter(ctx, writeModel.Query())
@ -1305,7 +1308,7 @@ func (c *Commands) prepareAddInstanceLDAPProvider(a *instance.Aggregate, writeMo
if err = writeModel.Reduce(); err != nil { if err = writeModel.Reduce(); err != nil {
return nil, err return nil, err
} }
secret, err := crypto.Encrypt([]byte(provider.Password), c.idpConfigEncryption) secret, err := crypto.Encrypt([]byte(provider.BindPassword), c.idpConfigEncryption)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1315,14 +1318,15 @@ func (c *Commands) prepareAddInstanceLDAPProvider(a *instance.Aggregate, writeMo
&a.Aggregate, &a.Aggregate,
writeModel.ID, writeModel.ID,
provider.Name, provider.Name,
provider.Host, provider.Servers,
provider.Port, provider.StartTLS,
provider.TLS,
provider.BaseDN, provider.BaseDN,
provider.UserObjectClass, provider.BindDN,
provider.UserUniqueAttribute,
provider.Admin,
secret, secret,
provider.UserBase,
provider.UserObjectClasses,
provider.UserFilters,
provider.Timeout,
provider.LDAPAttributes, provider.LDAPAttributes,
provider.IDPOptions, provider.IDPOptions,
), ),
@ -1339,21 +1343,24 @@ func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, writ
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" { if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Sffgd", "Errors.Invalid.Argument") 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 == "" { if provider.BaseDN = strings.TrimSpace(provider.BaseDN); provider.BaseDN == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-vb3ss", "Errors.Invalid.Argument") 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") return nil, caos_errs.ThrowInvalidArgument(nil, "INST-hbere", "Errors.Invalid.Argument")
} }
if provider.UserUniqueAttribute = strings.TrimSpace(provider.UserUniqueAttribute); provider.UserUniqueAttribute == "" { if provider.UserBase = strings.TrimSpace(provider.UserBase); provider.UserBase == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-ASFt6", "Errors.Invalid.Argument")
}
if provider.Admin = strings.TrimSpace(provider.Admin); provider.Admin == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-DG45z", "Errors.Invalid.Argument") 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) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query()) events, err := filter(ctx, writeModel.Query())
if err != nil { if err != nil {
@ -1370,16 +1377,16 @@ func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, writ
ctx, ctx,
&a.Aggregate, &a.Aggregate,
writeModel.ID, writeModel.ID,
writeModel.Name,
provider.Name, provider.Name,
provider.Host, provider.Servers,
provider.Port, provider.StartTLS,
provider.TLS,
provider.BaseDN, provider.BaseDN,
provider.UserObjectClass, provider.BindDN,
provider.UserUniqueAttribute, provider.BindPassword,
provider.Admin, provider.UserBase,
provider.Password, provider.UserObjectClasses,
provider.UserFilters,
provider.Timeout,
c.idpConfigEncryption, c.idpConfigEncryption,
provider.LDAPAttributes, provider.LDAPAttributes,
provider.IDPOptions, provider.IDPOptions,

View File

@ -2,6 +2,7 @@ package command
import ( import (
"context" "context"
"time"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
@ -744,16 +745,16 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
ctx context.Context, ctx context.Context,
aggregate *eventstore.Aggregate, aggregate *eventstore.Aggregate,
id, id,
oldName, name string,
name, servers []string,
host, startTLS bool,
port string, baseDN string,
tls bool, bindDN string,
baseDN, bindPassword string,
userObjectClass, userBase string,
userUniqueAttribute, userObjectClasses []string,
admin string, userFilters []string,
password string, timeout time.Duration,
secretCrypto crypto.Crypto, secretCrypto crypto.Crypto,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
@ -761,14 +762,15 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
changes, err := wm.LDAPIDPWriteModel.NewChanges( changes, err := wm.LDAPIDPWriteModel.NewChanges(
name, name,
host, servers,
port, startTLS,
tls,
baseDN, baseDN,
userObjectClass, bindDN,
userUniqueAttribute, bindPassword,
admin, userBase,
password, userObjectClasses,
userFilters,
timeout,
secretCrypto, secretCrypto,
attributes, attributes,
options, options,
@ -776,7 +778,7 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
if err != nil || len(changes) == 0 { if err != nil || len(changes) == 0 {
return nil, err return nil, err
} }
return instance.NewLDAPIDPChangedEvent(ctx, aggregate, id, oldName, changes) return instance.NewLDAPIDPChangedEvent(ctx, aggregate, id, changes)
} }
type InstanceIDPRemoveWriteModel struct { type InstanceIDPRemoveWriteModel struct {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"testing" "testing"
"time"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -18,7 +19,6 @@ import (
"github.com/zitadel/zitadel/internal/id" "github.com/zitadel/zitadel/internal/id"
id_mock "github.com/zitadel/zitadel/internal/id/mock" id_mock "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/idp" "github.com/zitadel/zitadel/internal/repository/idp"
"github.com/zitadel/zitadel/internal/repository/idpconfig"
"github.com/zitadel/zitadel/internal/repository/instance" "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", "invalid baseDN",
fields{ fields{
@ -3705,7 +3687,6 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
ctx: authz.WithInstanceID(context.Background(), "instance1"), ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host",
}, },
}, },
res{ res{
@ -3715,7 +3696,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
}, },
}, },
{ {
"invalid userObjectClass", "invalid bindDN",
fields{ fields{
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@ -3724,7 +3705,6 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
ctx: authz.WithInstanceID(context.Background(), "instance1"), ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host",
BaseDN: "baseDN", BaseDN: "baseDN",
}, },
}, },
@ -3735,7 +3715,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
}, },
}, },
{ {
"invalid userUniqueAttribute", "invalid bindPassword",
fields{ fields{
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@ -3743,10 +3723,9 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
args{ args{
ctx: authz.WithInstanceID(context.Background(), "instance1"), ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", BindDN: "binddn",
BaseDN: "baseDN", BaseDN: "baseDN",
UserObjectClass: "userObjectClass",
}, },
}, },
res{ res{
@ -3756,7 +3735,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
}, },
}, },
{ {
"invalid admin", "invalid userBase",
fields{ fields{
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@ -3764,11 +3743,10 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
args{ args{
ctx: authz.WithInstanceID(context.Background(), "instance1"), ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", BindDN: "binddn",
BaseDN: "baseDN", BaseDN: "baseDN",
UserObjectClass: "userObjectClass", BindPassword: "password",
UserUniqueAttribute: "userUniqueAttribute",
}, },
}, },
res{ res{
@ -3778,7 +3756,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
}, },
}, },
{ {
"invalid password", "invalid servers",
fields{ fields{
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@ -3786,17 +3764,63 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
args{ args{
ctx: authz.WithInstanceID(context.Background(), "instance1"), ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", BindDN: "binddn",
BaseDN: "baseDN", BaseDN: "baseDN",
UserObjectClass: "userObjectClass", BindPassword: "password",
UserUniqueAttribute: "userUniqueAttribute", UserBase: "user",
Admin: "admin",
}, },
}, },
res{ res{
err: func(err error) bool { 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, instance.NewLDAPIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1", "id1",
"name", "name",
"host", []string{"server"},
"",
false, false,
"baseDN", "baseDN",
"userObjectClass", "dn",
"userUniqueAttribute",
"admin",
&crypto.CryptoValue{ &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
Algorithm: "enc", Algorithm: "enc",
KeyID: "id", KeyID: "id",
Crypted: []byte("password"), Crypted: []byte("password"),
}, },
"user",
[]string{"object"},
[]string{"filter"},
time.Second*30,
idp.LDAPAttributes{}, idp.LDAPAttributes{},
idp.Options{}, idp.Options{},
)), )),
}, },
uniqueConstraintsFromEventConstraintWithInstanceID("instance1", idpconfig.NewAddIDPConfigNameUniqueConstraint("name", "instance1")),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@ -3838,13 +3862,16 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
args: args{ args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"), ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", Servers: []string{"server"},
BaseDN: "baseDN", StartTLS: false,
UserObjectClass: "userObjectClass", BaseDN: "baseDN",
UserUniqueAttribute: "userUniqueAttribute", BindDN: "dn",
Admin: "admin", BindPassword: "password",
Password: "password", UserBase: "user",
UserObjectClasses: []string{"object"},
UserFilters: []string{"filter"},
Timeout: time.Second * 30,
}, },
}, },
res: res{ res: res{
@ -3864,19 +3891,20 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
instance.NewLDAPIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, instance.NewLDAPIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1", "id1",
"name", "name",
"host", []string{"server"},
"port", false,
true,
"baseDN", "baseDN",
"userObjectClass", "dn",
"userUniqueAttribute",
"admin",
&crypto.CryptoValue{ &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
Algorithm: "enc", Algorithm: "enc",
KeyID: "id", KeyID: "id",
Crypted: []byte("password"), Crypted: []byte("password"),
}, },
"user",
[]string{"object"},
[]string{"filter"},
time.Second*30,
idp.LDAPAttributes{ idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "firstName", FirstNameAttribute: "firstName",
@ -3900,7 +3928,6 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
}, },
)), )),
}, },
uniqueConstraintsFromEventConstraintWithInstanceID("instance1", idpconfig.NewAddIDPConfigNameUniqueConstraint("name", "instance1")),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@ -3909,15 +3936,16 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
args: args{ args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"), ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", Servers: []string{"server"},
Port: "port", StartTLS: false,
TLS: true, BaseDN: "baseDN",
BaseDN: "baseDN", BindDN: "dn",
UserObjectClass: "userObjectClass", BindPassword: "password",
UserUniqueAttribute: "userUniqueAttribute", UserBase: "user",
Admin: "admin", UserObjectClasses: []string{"object"},
Password: "password", UserFilters: []string{"filter"},
Timeout: time.Second * 30,
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "firstName", 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", "invalid baseDN",
fields{ fields{
@ -4048,7 +4058,6 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
id: "id1", id: "id1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host",
}, },
}, },
res{ res{
@ -4058,7 +4067,7 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
}, },
}, },
{ {
"invalid userObjectClass", "invalid bindDN",
fields{ fields{
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
}, },
@ -4067,7 +4076,6 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
id: "id1", id: "id1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host",
BaseDN: "baseDN", BaseDN: "baseDN",
}, },
}, },
@ -4078,7 +4086,7 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
}, },
}, },
{ {
"invalid userUniqueAttribute", "invalid userbase",
fields{ fields{
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
}, },
@ -4086,32 +4094,9 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
ctx: authz.WithInstanceID(context.Background(), "instance1"), ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1", id: "id1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", BaseDN: "baseDN",
BaseDN: "baseDN", BindDN: "bindDN",
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",
}, },
}, },
res{ 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", name: "not found",
fields: fields{ fields: fields{
@ -4131,16 +4182,20 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
ctx: authz.WithInstanceID(context.Background(), "instance1"), ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1", id: "id1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", Servers: []string{"server"},
BaseDN: "baseDN", BaseDN: "baseDN",
UserObjectClass: "userObjectClass", BindDN: "binddn",
UserUniqueAttribute: "userUniqueAttribute", BindPassword: "password",
Admin: "admin", UserBase: "user",
UserObjectClasses: []string{"object"},
UserFilters: []string{"filter"},
}, },
}, },
res: res{ 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, instance.NewLDAPIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1", "id1",
"name", "name",
"host", []string{"server"},
"",
false, false,
"baseDN", "basedn",
"userObjectClass", "binddn",
"userUniqueAttribute",
"admin",
&crypto.CryptoValue{ &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
Algorithm: "enc", Algorithm: "enc",
KeyID: "id", KeyID: "id",
Crypted: []byte("password"), Crypted: []byte("password"),
}, },
"user",
[]string{"object"},
[]string{"filter"},
time.Second*30,
idp.LDAPAttributes{}, idp.LDAPAttributes{},
idp.Options{}, idp.Options{},
)), )),
@ -4175,12 +4231,15 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
ctx: authz.WithInstanceID(context.Background(), "instance1"), ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1", id: "id1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", Servers: []string{"server"},
BaseDN: "baseDN", StartTLS: false,
UserObjectClass: "userObjectClass", BaseDN: "basedn",
UserUniqueAttribute: "userUniqueAttribute", BindDN: "binddn",
Admin: "admin", UserBase: "user",
UserObjectClasses: []string{"object"},
UserFilters: []string{"filter"},
Timeout: time.Second * 30,
}, },
}, },
res: res{ res: res{
@ -4196,19 +4255,20 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
instance.NewLDAPIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, instance.NewLDAPIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1", "id1",
"name", "name",
"host", []string{"server"},
"port",
false, false,
"baseDN", "basedn",
"userObjectClass", "binddn",
"userUniqueAttribute",
"admin",
&crypto.CryptoValue{ &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
Algorithm: "enc", Algorithm: "enc",
KeyID: "id", KeyID: "id",
Crypted: []byte("password"), Crypted: []byte("password"),
}, },
"user",
[]string{"object"},
[]string{"filter"},
time.Second*30,
idp.LDAPAttributes{}, idp.LDAPAttributes{},
idp.Options{}, idp.Options{},
)), )),
@ -4221,22 +4281,22 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
t := true t := true
event, _ := instance.NewLDAPIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, event, _ := instance.NewLDAPIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1", "id1",
"name",
[]idp.LDAPIDPChanges{ []idp.LDAPIDPChanges{
idp.ChangeLDAPName("new name"), idp.ChangeLDAPName("new name"),
idp.ChangeLDAPHost("new host"), idp.ChangeLDAPServers([]string{"new server"}),
idp.ChangeLDAPPort("new port"), idp.ChangeLDAPStartTLS(true),
idp.ChangeLDAPTLS(true), idp.ChangeLDAPBaseDN("new basedn"),
idp.ChangeLDAPBaseDN("new baseDN"), idp.ChangeLDAPBindDN("new binddn"),
idp.ChangeLDAPUserObjectClass("new userObjectClass"), idp.ChangeLDAPBindPassword(&crypto.CryptoValue{
idp.ChangeLDAPUserUniqueAttribute("new userUniqueAttribute"),
idp.ChangeLDAPAdmin("new admin"),
idp.ChangeLDAPPassword(&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
Algorithm: "enc", Algorithm: "enc",
KeyID: "id", KeyID: "id",
Crypted: []byte("new password"), 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{ idp.ChangeLDAPAttributes(idp.LDAPAttributeChanges{
IDAttribute: stringPointer("new id"), IDAttribute: stringPointer("new id"),
FirstNameAttribute: stringPointer("new firstName"), 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)), secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
@ -4274,15 +4332,16 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
ctx: authz.WithInstanceID(context.Background(), "instance1"), ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1", id: "id1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "new name", Name: "new name",
Host: "new host", Servers: []string{"new server"},
Port: "new port", StartTLS: true,
TLS: true, BaseDN: "new basedn",
BaseDN: "new baseDN", BindDN: "new binddn",
UserObjectClass: "new userObjectClass", BindPassword: "new password",
UserUniqueAttribute: "new userUniqueAttribute", UserBase: "new user",
Admin: "new admin", UserObjectClasses: []string{"new object"},
Password: "new password", UserFilters: []string{"new filter"},
Timeout: time.Second * 20,
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "new id", IDAttribute: "new id",
FirstNameAttribute: "new firstName", FirstNameAttribute: "new firstName",

View File

@ -1268,23 +1268,26 @@ func (c *Commands) prepareAddOrgLDAPProvider(a *org.Aggregate, writeModel *OrgLD
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" { if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SAfdd", "Errors.Invalid.Argument") 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 == "" { if provider.BaseDN = strings.TrimSpace(provider.BaseDN); provider.BaseDN == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-sv31s", "Errors.Invalid.Argument") 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") 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") 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") return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SAD5n", "Errors.Invalid.Argument")
} }
if provider.Password = strings.TrimSpace(provider.Password); provider.Password == "" { if len(provider.Servers) == 0 {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-sdf5h", "Errors.Invalid.Argument") 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) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query()) 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 { if err = writeModel.Reduce(); err != nil {
return nil, err return nil, err
} }
secret, err := crypto.Encrypt([]byte(provider.Password), c.idpConfigEncryption) secret, err := crypto.Encrypt([]byte(provider.BindPassword), c.idpConfigEncryption)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1305,14 +1308,15 @@ func (c *Commands) prepareAddOrgLDAPProvider(a *org.Aggregate, writeModel *OrgLD
&a.Aggregate, &a.Aggregate,
writeModel.ID, writeModel.ID,
provider.Name, provider.Name,
provider.Host, provider.Servers,
provider.Port, provider.StartTLS,
provider.TLS,
provider.BaseDN, provider.BaseDN,
provider.UserObjectClass, provider.BindDN,
provider.UserUniqueAttribute,
provider.Admin,
secret, secret,
provider.UserBase,
provider.UserObjectClasses,
provider.UserFilters,
provider.Timeout,
provider.LDAPAttributes, provider.LDAPAttributes,
provider.IDPOptions, provider.IDPOptions,
), ),
@ -1329,21 +1333,24 @@ func (c *Commands) prepareUpdateOrgLDAPProvider(a *org.Aggregate, writeModel *Or
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" { if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Sffgd", "Errors.Invalid.Argument") 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 == "" { if provider.BaseDN = strings.TrimSpace(provider.BaseDN); provider.BaseDN == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-vb3ss", "Errors.Invalid.Argument") 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") return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-hbere", "Errors.Invalid.Argument")
} }
if provider.UserUniqueAttribute = strings.TrimSpace(provider.UserUniqueAttribute); provider.UserUniqueAttribute == "" { if provider.UserBase = strings.TrimSpace(provider.UserBase); provider.UserBase == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-ASFt6", "Errors.Invalid.Argument")
}
if provider.Admin = strings.TrimSpace(provider.Admin); provider.Admin == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-DG45z", "Errors.Invalid.Argument") 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) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query()) events, err := filter(ctx, writeModel.Query())
if err != nil { if err != nil {
@ -1360,16 +1367,16 @@ func (c *Commands) prepareUpdateOrgLDAPProvider(a *org.Aggregate, writeModel *Or
ctx, ctx,
&a.Aggregate, &a.Aggregate,
writeModel.ID, writeModel.ID,
writeModel.Name,
provider.Name, provider.Name,
provider.Host, provider.Servers,
provider.Port, provider.StartTLS,
provider.TLS,
provider.BaseDN, provider.BaseDN,
provider.UserObjectClass, provider.BindDN,
provider.UserUniqueAttribute, provider.BindPassword,
provider.Admin, provider.UserBase,
provider.Password, provider.UserObjectClasses,
provider.UserFilters,
provider.Timeout,
c.idpConfigEncryption, c.idpConfigEncryption,
provider.LDAPAttributes, provider.LDAPAttributes,
provider.IDPOptions, provider.IDPOptions,

View File

@ -2,6 +2,7 @@ package command
import ( import (
"context" "context"
"time"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
@ -754,16 +755,16 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
ctx context.Context, ctx context.Context,
aggregate *eventstore.Aggregate, aggregate *eventstore.Aggregate,
id, id,
oldName, name string,
name, servers []string,
host, startTLS bool,
port string, baseDN string,
tls bool, bindDN string,
baseDN, bindPassword string,
userObjectClass, userBase string,
userUniqueAttribute, userObjectClasses []string,
admin string, userFilters []string,
password string, timeout time.Duration,
secretCrypto crypto.Crypto, secretCrypto crypto.Crypto,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
@ -771,14 +772,15 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
changes, err := wm.LDAPIDPWriteModel.NewChanges( changes, err := wm.LDAPIDPWriteModel.NewChanges(
name, name,
host, servers,
port, startTLS,
tls,
baseDN, baseDN,
userObjectClass, bindDN,
userUniqueAttribute, bindPassword,
admin, userBase,
password, userObjectClasses,
userFilters,
timeout,
secretCrypto, secretCrypto,
attributes, attributes,
options, options,
@ -786,7 +788,7 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
if err != nil || len(changes) == 0 { if err != nil || len(changes) == 0 {
return nil, err return nil, err
} }
return org.NewLDAPIDPChangedEvent(ctx, aggregate, id, oldName, changes) return org.NewLDAPIDPChangedEvent(ctx, aggregate, id, changes)
} }
type OrgIDPRemoveWriteModel struct { type OrgIDPRemoveWriteModel struct {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"testing" "testing"
"time"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -17,7 +18,6 @@ import (
"github.com/zitadel/zitadel/internal/id" "github.com/zitadel/zitadel/internal/id"
id_mock "github.com/zitadel/zitadel/internal/id/mock" id_mock "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/idp" "github.com/zitadel/zitadel/internal/repository/idp"
"github.com/zitadel/zitadel/internal/repository/idpconfig"
"github.com/zitadel/zitadel/internal/repository/org" "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", "invalid baseDN",
fields{ fields{
@ -3764,7 +3745,6 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
resourceOwner: "org1", resourceOwner: "org1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host",
}, },
}, },
res{ res{
@ -3774,7 +3754,7 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
}, },
}, },
{ {
"invalid userObjectClass", "invalid binddn",
fields{ fields{
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@ -3784,7 +3764,6 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
resourceOwner: "org1", resourceOwner: "org1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host",
BaseDN: "baseDN", 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", "invalid password",
fields{ fields{
@ -3849,17 +3783,108 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
resourceOwner: "org1", resourceOwner: "org1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", BindDN: "binddn",
BaseDN: "baseDN", BaseDN: "baseDN",
UserObjectClass: "userObjectClass",
UserUniqueAttribute: "userUniqueAttribute",
Admin: "admin",
}, },
}, },
res{ res{
err: func(err error) bool { 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, org.NewLDAPIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1", "id1",
"name", "name",
"host", []string{"server"},
"",
false, false,
"baseDN", "baseDN",
"userObjectClass", "dn",
"userUniqueAttribute",
"admin",
&crypto.CryptoValue{ &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
Algorithm: "enc", Algorithm: "enc",
KeyID: "id", KeyID: "id",
Crypted: []byte("password"), Crypted: []byte("password"),
}, },
"user",
[]string{"object"},
[]string{"filter"},
time.Second*30,
idp.LDAPAttributes{}, idp.LDAPAttributes{},
idp.Options{}, idp.Options{},
)), )),
uniqueConstraintsFromEventConstraint(idpconfig.NewAddIDPConfigNameUniqueConstraint("name", "org1")),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@ -3899,13 +3924,16 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
resourceOwner: "org1", resourceOwner: "org1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", Servers: []string{"server"},
BaseDN: "baseDN", StartTLS: false,
UserObjectClass: "userObjectClass", BaseDN: "baseDN",
UserUniqueAttribute: "userUniqueAttribute", BindDN: "dn",
Admin: "admin", BindPassword: "password",
Password: "password", UserBase: "user",
UserObjectClasses: []string{"object"},
UserFilters: []string{"filter"},
Timeout: time.Second * 30,
}, },
}, },
res: res{ res: res{
@ -3923,19 +3951,20 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
org.NewLDAPIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, org.NewLDAPIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1", "id1",
"name", "name",
"host", []string{"server"},
"port", false,
true,
"baseDN", "baseDN",
"userObjectClass", "dn",
"userUniqueAttribute",
"admin",
&crypto.CryptoValue{ &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
Algorithm: "enc", Algorithm: "enc",
KeyID: "id", KeyID: "id",
Crypted: []byte("password"), Crypted: []byte("password"),
}, },
"user",
[]string{"object"},
[]string{"filter"},
time.Second*30,
idp.LDAPAttributes{ idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "firstName", FirstNameAttribute: "firstName",
@ -3958,7 +3987,6 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
IsAutoUpdate: true, IsAutoUpdate: true,
}, },
)), )),
uniqueConstraintsFromEventConstraint(idpconfig.NewAddIDPConfigNameUniqueConstraint("name", "org1")),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@ -3968,15 +3996,16 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
resourceOwner: "org1", resourceOwner: "org1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", Servers: []string{"server"},
Port: "port", StartTLS: false,
TLS: true, BaseDN: "baseDN",
BaseDN: "baseDN", BindDN: "dn",
UserObjectClass: "userObjectClass", BindPassword: "password",
UserUniqueAttribute: "userUniqueAttribute", UserBase: "user",
Admin: "admin", UserObjectClasses: []string{"object"},
Password: "password", UserFilters: []string{"filter"},
Timeout: time.Second * 30,
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "firstName", 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", "invalid baseDN",
fields{ fields{
@ -4112,7 +4122,6 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
id: "id1", id: "id1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host",
}, },
}, },
res{ res{
@ -4122,7 +4131,7 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
}, },
}, },
{ {
"invalid userObjectClass", "invalid binddn",
fields{ fields{
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
}, },
@ -4132,7 +4141,6 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
id: "id1", id: "id1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host",
BaseDN: "baseDN", BaseDN: "baseDN",
}, },
}, },
@ -4143,7 +4151,7 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
}, },
}, },
{ {
"invalid userUniqueAttribute", "invalid userbase",
fields{ fields{
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
}, },
@ -4152,33 +4160,9 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
resourceOwner: "org1", resourceOwner: "org1",
id: "id1", id: "id1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", BaseDN: "baseDN",
BaseDN: "baseDN", BindDN: "bindDN",
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",
}, },
}, },
res{ 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", name: "not found",
fields: fields{ fields: fields{
@ -4199,16 +4252,20 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
resourceOwner: "org1", resourceOwner: "org1",
id: "id1", id: "id1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", Servers: []string{"server"},
BaseDN: "baseDN", BaseDN: "baseDN",
UserObjectClass: "userObjectClass", BindDN: "binddn",
UserUniqueAttribute: "userUniqueAttribute", BindPassword: "password",
Admin: "admin", UserBase: "user",
UserObjectClasses: []string{"object"},
UserFilters: []string{"filter"},
}, },
}, },
res: res{ 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, org.NewLDAPIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1", "id1",
"name", "name",
"host", []string{"server"},
"",
false, false,
"baseDN", "basedn",
"userObjectClass", "binddn",
"userUniqueAttribute",
"admin",
&crypto.CryptoValue{ &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
Algorithm: "enc", Algorithm: "enc",
KeyID: "id", KeyID: "id",
Crypted: []byte("password"), Crypted: []byte("password"),
}, },
"user",
[]string{"object"},
[]string{"filter"},
time.Second*30,
idp.LDAPAttributes{}, idp.LDAPAttributes{},
idp.Options{}, idp.Options{},
)), )),
@ -4244,12 +4302,14 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
resourceOwner: "org1", resourceOwner: "org1",
id: "id1", id: "id1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "name", Name: "name",
Host: "host", Servers: []string{"server"},
BaseDN: "baseDN", BaseDN: "basedn",
UserObjectClass: "userObjectClass", BindDN: "binddn",
UserUniqueAttribute: "userUniqueAttribute", UserObjectClasses: []string{"object"},
Admin: "admin", UserFilters: []string{"filter"},
UserBase: "user",
Timeout: time.Second * 30,
}, },
}, },
res: res{ res: res{
@ -4265,19 +4325,20 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
org.NewLDAPIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, org.NewLDAPIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1", "id1",
"name", "name",
"host", []string{"server"},
"port",
false, false,
"baseDN", "basedn",
"userObjectClass", "binddn",
"userUniqueAttribute",
"admin",
&crypto.CryptoValue{ &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
Algorithm: "enc", Algorithm: "enc",
KeyID: "id", KeyID: "id",
Crypted: []byte("password"), Crypted: []byte("password"),
}, },
"user",
[]string{"object"},
[]string{"filter"},
time.Second*30,
idp.LDAPAttributes{}, idp.LDAPAttributes{},
idp.Options{}, idp.Options{},
)), )),
@ -4288,22 +4349,22 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
t := true t := true
event, _ := org.NewLDAPIDPChangedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, event, _ := org.NewLDAPIDPChangedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1", "id1",
"name",
[]idp.LDAPIDPChanges{ []idp.LDAPIDPChanges{
idp.ChangeLDAPName("new name"), idp.ChangeLDAPName("new name"),
idp.ChangeLDAPHost("new host"), idp.ChangeLDAPServers([]string{"new server"}),
idp.ChangeLDAPPort("new port"), idp.ChangeLDAPStartTLS(true),
idp.ChangeLDAPTLS(true), idp.ChangeLDAPBaseDN("new basedn"),
idp.ChangeLDAPBaseDN("new baseDN"), idp.ChangeLDAPBindDN("new binddn"),
idp.ChangeLDAPUserObjectClass("new userObjectClass"), idp.ChangeLDAPBindPassword(&crypto.CryptoValue{
idp.ChangeLDAPUserUniqueAttribute("new userUniqueAttribute"),
idp.ChangeLDAPAdmin("new admin"),
idp.ChangeLDAPPassword(&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
Algorithm: "enc", Algorithm: "enc",
KeyID: "id", KeyID: "id",
Crypted: []byte("new password"), 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{ idp.ChangeLDAPAttributes(idp.LDAPAttributeChanges{
IDAttribute: stringPointer("new id"), IDAttribute: stringPointer("new id"),
FirstNameAttribute: stringPointer("new firstName"), FirstNameAttribute: stringPointer("new firstName"),
@ -4330,8 +4391,6 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
return event return event
}(), }(),
), ),
uniqueConstraintsFromEventConstraint(idpconfig.NewRemoveIDPConfigNameUniqueConstraint("name", "org1")),
uniqueConstraintsFromEventConstraint(idpconfig.NewAddIDPConfigNameUniqueConstraint("new name", "org1")),
), ),
), ),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
@ -4341,15 +4400,16 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
resourceOwner: "org1", resourceOwner: "org1",
id: "id1", id: "id1",
provider: LDAPProvider{ provider: LDAPProvider{
Name: "new name", Name: "new name",
Host: "new host", Servers: []string{"new server"},
Port: "new port", StartTLS: true,
TLS: true, BaseDN: "new basedn",
BaseDN: "new baseDN", BindDN: "new binddn",
UserObjectClass: "new userObjectClass", BindPassword: "new password",
UserUniqueAttribute: "new userUniqueAttribute", UserBase: "new user",
Admin: "new admin", UserObjectClasses: []string{"new object"},
Password: "new password", UserFilters: []string{"new filter"},
Timeout: time.Second * 20,
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "new id", IDAttribute: "new id",
FirstNameAttribute: "new firstName", FirstNameAttribute: "new firstName",

View File

@ -2,6 +2,7 @@ package ldap
import ( import (
"context" "context"
"time"
"github.com/zitadel/zitadel/internal/idp" "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 // Provider is the [idp.Provider] implementation for a generic LDAP provider
type Provider struct { type Provider struct {
name string name string
host string servers []string
port string startTLS bool
tls bool baseDN string
baseDN string bindDN string
userObjectClass string bindPassword string
userUniqueAttribute string userBase string
admin string userObjectClasses []string
password string userFilters []string
loginUrl string timeout time.Duration
loginUrl string
isLinkingAllowed bool isLinkingAllowed bool
isCreationAllowed 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 // WithoutStartTLS configures to communication insecure with the LDAP server without startTLS
func WithCustomPort(port string) ProviderOpts { func WithoutStartTLS() ProviderOpts {
return func(p *Provider) { return func(p *Provider) {
p.port = port p.startTLS = false
}
}
// Insecure configures to communication insecure with the LDAP server without TLS
func Insecure() ProviderOpts {
return func(p *Provider) {
p.tls = false
} }
} }
@ -181,27 +177,29 @@ func WithProfileAttribute(name string) ProviderOpts {
func New( func New(
name string, name string,
host string, servers []string,
baseDN string, baseDN string,
userObjectClass string, bindDN string,
userUniqueAttribute string, bindPassword string,
admin string, userBase string,
password string, userObjectClasses []string,
userFilters []string,
timeout time.Duration,
loginUrl string, loginUrl string,
options ...ProviderOpts, options ...ProviderOpts,
) *Provider { ) *Provider {
provider := &Provider{ provider := &Provider{
name: name, name: name,
host: host, servers: servers,
port: DefaultPort, startTLS: true,
tls: true, baseDN: baseDN,
baseDN: baseDN, bindDN: bindDN,
userObjectClass: userObjectClass, bindPassword: bindPassword,
userUniqueAttribute: userUniqueAttribute, userBase: userBase,
admin: admin, userObjectClasses: userObjectClasses,
password: password, userFilters: userFilters,
loginUrl: loginUrl, timeout: timeout,
idAttribute: userUniqueAttribute, loginUrl: loginUrl,
} }
for _, option := range options { for _, option := range options {
option(provider) 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) { func (p *Provider) BeginAuth(ctx context.Context, state string, params ...any) (idp.Session, error) {
return &Session{ return &Session{
Provider: p, Provider: p,
loginUrl: p.loginUrl + "?state=" + state, loginUrl: p.loginUrl + state,
}, nil }, nil
} }
@ -235,3 +233,47 @@ func (p *Provider) IsAutoCreation() bool {
func (p *Provider) IsAutoUpdate() bool { func (p *Provider) IsAutoUpdate() bool {
return p.isAutoUpdate 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
}

View File

@ -2,26 +2,28 @@ package ldap
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestProvider_Options(t *testing.T) { func TestProvider_Options(t *testing.T) {
type fields struct { type fields struct {
name string name string
host string servers []string
baseDN string baseDN string
userObjectClass string bindDN string
userUniqueAttribute string bindPassword string
admin string userBase string
password string userObjectClasses []string
loginUrl string userFilters []string
opts []ProviderOpts timeout time.Duration
loginUrl string
opts []ProviderOpts
} }
type want struct { type want struct {
name string name string
port string startTls bool
tls bool
linkingAllowed bool linkingAllowed bool
creationAllowed bool creationAllowed bool
autoCreation bool autoCreation bool
@ -48,39 +50,43 @@ func TestProvider_Options(t *testing.T) {
{ {
name: "default", name: "default",
fields: fields{ fields: fields{
name: "ldap", name: "ldap",
host: "host", servers: []string{"server"},
baseDN: "base", baseDN: "base",
userObjectClass: "class", bindDN: "binddn",
userUniqueAttribute: "attr", bindPassword: "password",
admin: "admin", userBase: "user",
password: "password", userObjectClasses: []string{"object"},
loginUrl: "url", userFilters: []string{"filter"},
opts: nil, timeout: 30 * time.Second,
loginUrl: "url",
opts: nil,
}, },
want: want{ want: want{
name: "ldap", name: "ldap",
port: DefaultPort, startTls: true,
tls: true,
linkingAllowed: false, linkingAllowed: false,
creationAllowed: false, creationAllowed: false,
autoCreation: false, autoCreation: false,
autoUpdate: false, autoUpdate: false,
idAttribute: "attr", idAttribute: "",
}, },
}, },
{ {
name: "all true", name: "all true",
fields: fields{ fields: fields{
name: "ldap", name: "ldap",
host: "host", servers: []string{"server"},
baseDN: "base", baseDN: "base",
userObjectClass: "class", bindDN: "binddn",
userUniqueAttribute: "attr", bindPassword: "password",
admin: "admin", userBase: "user",
password: "password", userObjectClasses: []string{"object"},
loginUrl: "url", userFilters: []string{"filter"},
timeout: 30 * time.Second,
loginUrl: "url",
opts: []ProviderOpts{ opts: []ProviderOpts{
WithoutStartTLS(),
WithLinkingAllowed(), WithLinkingAllowed(),
WithCreationAllowed(), WithCreationAllowed(),
WithAutoCreation(), WithAutoCreation(),
@ -89,28 +95,28 @@ func TestProvider_Options(t *testing.T) {
}, },
want: want{ want: want{
name: "ldap", name: "ldap",
port: DefaultPort, startTls: false,
tls: true,
linkingAllowed: true, linkingAllowed: true,
creationAllowed: true, creationAllowed: true,
autoCreation: true, autoCreation: true,
autoUpdate: true, autoUpdate: true,
idAttribute: "attr", idAttribute: "",
}, },
}, { }, {
name: "all true, attributes set", name: "all true, attributes set",
fields: fields{ fields: fields{
name: "ldap", name: "ldap",
host: "host", servers: []string{"server"},
baseDN: "base", baseDN: "base",
userObjectClass: "class", bindDN: "binddn",
userUniqueAttribute: "attr", bindPassword: "password",
admin: "admin", userBase: "user",
password: "password", userObjectClasses: []string{"object"},
loginUrl: "url", userFilters: []string{"filter"},
timeout: 30 * time.Second,
loginUrl: "url",
opts: []ProviderOpts{ opts: []ProviderOpts{
Insecure(), WithoutStartTLS(),
WithCustomPort("port"),
WithLinkingAllowed(), WithLinkingAllowed(),
WithCreationAllowed(), WithCreationAllowed(),
WithAutoCreation(), WithAutoCreation(),
@ -132,8 +138,7 @@ func TestProvider_Options(t *testing.T) {
}, },
want: want{ want: want{
name: "ldap", name: "ldap",
port: "port", startTls: false,
tls: false,
linkingAllowed: true, linkingAllowed: true,
creationAllowed: true, creationAllowed: true,
autoCreation: true, autoCreation: true,
@ -157,11 +162,22 @@ func TestProvider_Options(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
a := assert.New(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.name, provider.Name())
a.Equal(tt.want.port, provider.port) a.Equal(tt.want.startTls, provider.startTLS)
a.Equal(tt.want.tls, provider.tls)
a.Equal(tt.want.linkingAllowed, provider.IsLinkingAllowed()) a.Equal(tt.want.linkingAllowed, provider.IsLinkingAllowed())
a.Equal(tt.want.creationAllowed, provider.IsCreationAllowed()) a.Equal(tt.want.creationAllowed, provider.IsCreationAllowed())
a.Equal(tt.want.autoCreation, provider.IsAutoCreation()) a.Equal(tt.want.autoCreation, provider.IsAutoCreation())

View File

@ -4,8 +4,10 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "net"
"net/url"
"strconv" "strconv"
"time"
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"golang.org/x/text/language" "golang.org/x/text/language"
@ -15,49 +17,154 @@ import (
) )
var ErrNoSingleUser = errors.New("user does not exist or too many entries returned") 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) var _ idp.Session = (*Session)(nil)
type Session struct { type Session struct {
Provider *Provider Provider *Provider
loginUrl string loginUrl string
user string User string
password string Password string
} }
func (s *Session) GetAuthURL() string { func (s *Session) GetAuthURL() string {
return s.loginUrl 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 { if err != nil {
return nil, err return nil, err
} }
defer l.Close()
if s.Provider.tls { return mapLDAPEntryToUser(
err = l.StartTLS(&tls.Config{ServerName: s.Provider.host}) 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 { if err != nil {
return nil, err return nil, err
} }
} }
return conn, nil
}
// Bind as the admin to search for user func trySearchAndUserBind(
err = l.Bind("cn="+s.Provider.admin+","+s.Provider.baseDN, s.Provider.password) conn *ldap.Conn,
if err != nil { baseDN string,
return nil, err 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 // Search for user with the unique attribute for the userDN
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
s.Provider.baseDN, baseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, int(timeout.Seconds()), false,
fmt.Sprintf("(&(objectClass="+s.Provider.userObjectClass+")("+s.Provider.userUniqueAttribute+"=%s))", ldap.EscapeFilter(s.user)), searchQuery,
[]string{"dn"}, attributes,
nil, nil,
) )
sr, err := l.Search(searchRequest) sr, err := conn.Search(searchRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -67,33 +174,100 @@ func (s *Session) FetchUser(_ context.Context) (idp.User, error) {
user := sr.Entries[0] user := sr.Entries[0]
// Bind as the user to verify their password // Bind as the user to verify their password
err = l.Bind(user.DN, s.password) if err = conn.Bind(user.DN, password); err != nil {
if err != nil { return nil, ErrFailedLogin
return nil, err
} }
return user, nil
}
emailVerified, err := strconv.ParseBool(user.GetAttributeValue(s.Provider.emailVerifiedAttribute)) func queriesAndToSearchQuery(queries ...string) string {
if err != nil { if len(queries) == 0 {
return nil, err return ""
} }
phoneVerified, err := strconv.ParseBool(user.GetAttributeValue(s.Provider.phoneVerifiedAttribute)) if len(queries) == 1 {
if err != nil { return queries[0]
return nil, err }
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( return NewUser(
user.GetAttributeValue(s.Provider.idAttribute), user.GetAttributeValue(idAttribute),
user.GetAttributeValue(s.Provider.firstNameAttribute), user.GetAttributeValue(firstNameAttribute),
user.GetAttributeValue(s.Provider.lastNameAttribute), user.GetAttributeValue(lastNameAttribute),
user.GetAttributeValue(s.Provider.displayNameAttribute), user.GetAttributeValue(displayNameAttribute),
user.GetAttributeValue(s.Provider.nickNameAttribute), user.GetAttributeValue(nickNameAttribute),
user.GetAttributeValue(s.Provider.preferredUsernameAttribute), user.GetAttributeValue(preferredUsernameAttribute),
domain.EmailAddress(user.GetAttributeValue(s.Provider.emailAttribute)), domain.EmailAddress(user.GetAttributeValue(emailAttribute)),
emailVerified, emailVerified,
domain.PhoneNumber(user.GetAttributeValue(s.Provider.phoneAttribute)), domain.PhoneNumber(user.GetAttributeValue(phoneAttribute)),
phoneVerified, phoneVerified,
language.Make(user.GetAttributeValue(s.Provider.preferredLanguageAttribute)), language.Make(user.GetAttributeValue(preferredLanguageAttribute)),
user.GetAttributeValue(s.Provider.avatarURLAttribute), user.GetAttributeValue(avatarURLAttribute),
user.GetAttributeValue(s.Provider.profileAttribute), user.GetAttributeValue(profileAttribute),
), nil ), nil
} }

View File

@ -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)
}
})
}
}

View File

@ -128,15 +128,16 @@ type GoogleIDPTemplate struct {
} }
type LDAPIDPTemplate struct { type LDAPIDPTemplate struct {
IDPID string IDPID string
Host string Servers []string
Port string StartTLS bool
TLS bool BaseDN string
BaseDN string BindDN string
UserObjectClass string BindPassword *crypto.CryptoValue
UserUniqueAttribute string UserBase string
Admin string UserObjectClasses []string
Password *crypto.CryptoValue UserFilters []string
Timeout time.Duration
idp.LDAPAttributes idp.LDAPAttributes
} }
@ -515,36 +516,40 @@ var (
name: projection.LDAPInstanceIDCol, name: projection.LDAPInstanceIDCol,
table: ldapIdpTemplateTable, table: ldapIdpTemplateTable,
} }
LDAPHostCol = Column{ LDAPServersCol = Column{
name: projection.LDAPHostCol, name: projection.LDAPServersCol,
table: ldapIdpTemplateTable, table: ldapIdpTemplateTable,
} }
LDAPPortCol = Column{ LDAPStartTLSCol = Column{
name: projection.LDAPPortCol, name: projection.LDAPStartTLSCol,
table: ldapIdpTemplateTable,
}
LDAPTlsCol = Column{
name: projection.LDAPTlsCol,
table: ldapIdpTemplateTable, table: ldapIdpTemplateTable,
} }
LDAPBaseDNCol = Column{ LDAPBaseDNCol = Column{
name: projection.LDAPBaseDNCol, name: projection.LDAPBaseDNCol,
table: ldapIdpTemplateTable, table: ldapIdpTemplateTable,
} }
LDAPUserObjectClassCol = Column{ LDAPBindDNCol = Column{
name: projection.LDAPUserObjectClassCol, name: projection.LDAPBindDNCol,
table: ldapIdpTemplateTable, table: ldapIdpTemplateTable,
} }
LDAPUserUniqueAttributeCol = Column{ LDAPBindPasswordCol = Column{
name: projection.LDAPUserUniqueAttributeCol, name: projection.LDAPBindPasswordCol,
table: ldapIdpTemplateTable, table: ldapIdpTemplateTable,
} }
LDAPAdminCol = Column{ LDAPUserBaseCol = Column{
name: projection.LDAPAdminCol, name: projection.LDAPUserBaseCol,
table: ldapIdpTemplateTable, table: ldapIdpTemplateTable,
} }
LDAPPasswordCol = Column{ LDAPUserObjectClassesCol = Column{
name: projection.LDAPPasswordCol, name: projection.LDAPUserObjectClassesCol,
table: ldapIdpTemplateTable,
}
LDAPUserFiltersCol = Column{
name: projection.LDAPUserFiltersCol,
table: ldapIdpTemplateTable,
}
LDAPTimeoutCol = Column{
name: projection.LDAPTimeoutCol,
table: ldapIdpTemplateTable, table: ldapIdpTemplateTable,
} }
LDAPIDAttributeCol = Column{ LDAPIDAttributeCol = Column{
@ -772,14 +777,15 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
GoogleScopesCol.identifier(), GoogleScopesCol.identifier(),
// ldap // ldap
LDAPIDCol.identifier(), LDAPIDCol.identifier(),
LDAPHostCol.identifier(), LDAPServersCol.identifier(),
LDAPPortCol.identifier(), LDAPStartTLSCol.identifier(),
LDAPTlsCol.identifier(),
LDAPBaseDNCol.identifier(), LDAPBaseDNCol.identifier(),
LDAPUserObjectClassCol.identifier(), LDAPBindDNCol.identifier(),
LDAPUserUniqueAttributeCol.identifier(), LDAPBindPasswordCol.identifier(),
LDAPAdminCol.identifier(), LDAPUserBaseCol.identifier(),
LDAPPasswordCol.identifier(), LDAPUserObjectClassesCol.identifier(),
LDAPUserFiltersCol.identifier(),
LDAPTimeoutCol.identifier(),
LDAPIDAttributeCol.identifier(), LDAPIDAttributeCol.identifier(),
LDAPFirstNameAttributeCol.identifier(), LDAPFirstNameAttributeCol.identifier(),
LDAPLastNameAttributeCol.identifier(), LDAPLastNameAttributeCol.identifier(),
@ -869,14 +875,15 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
googleScopes := database.StringArray{} googleScopes := database.StringArray{}
ldapID := sql.NullString{} ldapID := sql.NullString{}
ldapHost := sql.NullString{} ldapServers := database.StringArray{}
ldapPort := sql.NullString{} ldapStartTls := sql.NullBool{}
ldapTls := sql.NullBool{}
ldapBaseDN := sql.NullString{} ldapBaseDN := sql.NullString{}
ldapUserObjectClass := sql.NullString{} ldapBindDN := sql.NullString{}
ldapUserUniqueAttribute := sql.NullString{} ldapBindPassword := new(crypto.CryptoValue)
ldapAdmin := sql.NullString{} ldapUserBase := sql.NullString{}
ldapPassword := new(crypto.CryptoValue) ldapUserObjectClasses := database.StringArray{}
ldapUserFilters := database.StringArray{}
ldapTimeout := sql.NullInt64{}
ldapIDAttribute := sql.NullString{} ldapIDAttribute := sql.NullString{}
ldapFirstNameAttribute := sql.NullString{} ldapFirstNameAttribute := sql.NullString{}
ldapLastNameAttribute := sql.NullString{} ldapLastNameAttribute := sql.NullString{}
@ -965,14 +972,15 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
&googleScopes, &googleScopes,
// ldap // ldap
&ldapID, &ldapID,
&ldapHost, &ldapServers,
&ldapPort, &ldapStartTls,
&ldapTls,
&ldapBaseDN, &ldapBaseDN,
&ldapUserObjectClass, &ldapBindDN,
&ldapUserUniqueAttribute, &ldapBindPassword,
&ldapAdmin, &ldapUserBase,
&ldapPassword, &ldapUserObjectClasses,
&ldapUserFilters,
&ldapTimeout,
&ldapIDAttribute, &ldapIDAttribute,
&ldapFirstNameAttribute, &ldapFirstNameAttribute,
&ldapLastNameAttribute, &ldapLastNameAttribute,
@ -1083,15 +1091,16 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
} }
if ldapID.Valid { if ldapID.Valid {
idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{ idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{
IDPID: ldapID.String, IDPID: ldapID.String,
Host: ldapHost.String, Servers: ldapServers,
Port: ldapPort.String, StartTLS: ldapStartTls.Bool,
TLS: ldapTls.Bool, BaseDN: ldapBaseDN.String,
BaseDN: ldapBaseDN.String, BindDN: ldapBindDN.String,
UserObjectClass: ldapUserObjectClass.String, BindPassword: ldapBindPassword,
UserUniqueAttribute: ldapUserUniqueAttribute.String, UserBase: ldapUserBase.String,
Admin: ldapAdmin.String, UserObjectClasses: ldapUserObjectClasses,
Password: ldapPassword, UserFilters: ldapUserFilters,
Timeout: time.Duration(ldapTimeout.Int64),
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: ldapIDAttribute.String, IDAttribute: ldapIDAttribute.String,
FirstNameAttribute: ldapFirstNameAttribute.String, FirstNameAttribute: ldapFirstNameAttribute.String,
@ -1189,14 +1198,15 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
GoogleScopesCol.identifier(), GoogleScopesCol.identifier(),
// ldap // ldap
LDAPIDCol.identifier(), LDAPIDCol.identifier(),
LDAPHostCol.identifier(), LDAPServersCol.identifier(),
LDAPPortCol.identifier(), LDAPStartTLSCol.identifier(),
LDAPTlsCol.identifier(),
LDAPBaseDNCol.identifier(), LDAPBaseDNCol.identifier(),
LDAPUserObjectClassCol.identifier(), LDAPBindDNCol.identifier(),
LDAPUserUniqueAttributeCol.identifier(), LDAPBindPasswordCol.identifier(),
LDAPAdminCol.identifier(), LDAPUserBaseCol.identifier(),
LDAPPasswordCol.identifier(), LDAPUserObjectClassesCol.identifier(),
LDAPUserFiltersCol.identifier(),
LDAPTimeoutCol.identifier(),
LDAPIDAttributeCol.identifier(), LDAPIDAttributeCol.identifier(),
LDAPFirstNameAttributeCol.identifier(), LDAPFirstNameAttributeCol.identifier(),
LDAPLastNameAttributeCol.identifier(), LDAPLastNameAttributeCol.identifier(),
@ -1290,14 +1300,15 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
googleScopes := database.StringArray{} googleScopes := database.StringArray{}
ldapID := sql.NullString{} ldapID := sql.NullString{}
ldapHost := sql.NullString{} ldapServers := database.StringArray{}
ldapPort := sql.NullString{} ldapStartTls := sql.NullBool{}
ldapTls := sql.NullBool{}
ldapBaseDN := sql.NullString{} ldapBaseDN := sql.NullString{}
ldapUserObjectClass := sql.NullString{} ldapBindDN := sql.NullString{}
ldapUserUniqueAttribute := sql.NullString{} ldapBindPassword := new(crypto.CryptoValue)
ldapAdmin := sql.NullString{} ldapUserBase := sql.NullString{}
ldapPassword := new(crypto.CryptoValue) ldapUserObjectClasses := database.StringArray{}
ldapUserFilters := database.StringArray{}
ldapTimeout := sql.NullInt64{}
ldapIDAttribute := sql.NullString{} ldapIDAttribute := sql.NullString{}
ldapFirstNameAttribute := sql.NullString{} ldapFirstNameAttribute := sql.NullString{}
ldapLastNameAttribute := sql.NullString{} ldapLastNameAttribute := sql.NullString{}
@ -1386,14 +1397,15 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
&googleScopes, &googleScopes,
// ldap // ldap
&ldapID, &ldapID,
&ldapHost, &ldapServers,
&ldapPort, &ldapStartTls,
&ldapTls,
&ldapBaseDN, &ldapBaseDN,
&ldapUserObjectClass, &ldapBindDN,
&ldapUserUniqueAttribute, &ldapBindPassword,
&ldapAdmin, &ldapUserBase,
&ldapPassword, &ldapUserObjectClasses,
&ldapUserFilters,
&ldapTimeout,
&ldapIDAttribute, &ldapIDAttribute,
&ldapFirstNameAttribute, &ldapFirstNameAttribute,
&ldapLastNameAttribute, &ldapLastNameAttribute,
@ -1503,15 +1515,16 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
} }
if ldapID.Valid { if ldapID.Valid {
idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{ idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{
IDPID: ldapID.String, IDPID: ldapID.String,
Host: ldapHost.String, Servers: ldapServers,
Port: ldapPort.String, StartTLS: ldapStartTls.Bool,
TLS: ldapTls.Bool, BaseDN: ldapBaseDN.String,
BaseDN: ldapBaseDN.String, BindDN: ldapBindDN.String,
UserObjectClass: ldapUserObjectClass.String, BindPassword: ldapBindPassword,
UserUniqueAttribute: ldapUserUniqueAttribute.String, UserBase: ldapUserBase.String,
Admin: ldapAdmin.String, UserObjectClasses: ldapUserObjectClasses,
Password: ldapPassword, UserFilters: ldapUserFilters,
Timeout: time.Duration(ldapTimeout.Int64),
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: ldapIDAttribute.String, IDAttribute: ldapIDAttribute.String,
FirstNameAttribute: ldapFirstNameAttribute.String, FirstNameAttribute: ldapFirstNameAttribute.String,

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"regexp" "regexp"
"testing" "testing"
"time"
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
@ -87,28 +88,29 @@ var (
` projections.idp_templates4_google.client_secret,` + ` projections.idp_templates4_google.client_secret,` +
` projections.idp_templates4_google.scopes,` + ` projections.idp_templates4_google.scopes,` +
// ldap // ldap
` projections.idp_templates4_ldap.idp_id,` + ` projections.idp_templates4_ldap2.idp_id,` +
` projections.idp_templates4_ldap.host,` + ` projections.idp_templates4_ldap2.servers,` +
` projections.idp_templates4_ldap.port,` + ` projections.idp_templates4_ldap2.start_tls,` +
` projections.idp_templates4_ldap.tls,` + ` projections.idp_templates4_ldap2.base_dn,` +
` projections.idp_templates4_ldap.base_dn,` + ` projections.idp_templates4_ldap2.bind_dn,` +
` projections.idp_templates4_ldap.user_object_class,` + ` projections.idp_templates4_ldap2.bind_password,` +
` projections.idp_templates4_ldap.user_unique_attribute,` + ` projections.idp_templates4_ldap2.user_base,` +
` projections.idp_templates4_ldap.admin,` + ` projections.idp_templates4_ldap2.user_object_classes,` +
` projections.idp_templates4_ldap.password,` + ` projections.idp_templates4_ldap2.user_filters,` +
` projections.idp_templates4_ldap.id_attribute,` + ` projections.idp_templates4_ldap2.timeout,` +
` projections.idp_templates4_ldap.first_name_attribute,` + ` projections.idp_templates4_ldap2.id_attribute,` +
` projections.idp_templates4_ldap.last_name_attribute,` + ` projections.idp_templates4_ldap2.first_name_attribute,` +
` projections.idp_templates4_ldap.display_name_attribute,` + ` projections.idp_templates4_ldap2.last_name_attribute,` +
` projections.idp_templates4_ldap.nick_name_attribute,` + ` projections.idp_templates4_ldap2.display_name_attribute,` +
` projections.idp_templates4_ldap.preferred_username_attribute,` + ` projections.idp_templates4_ldap2.nick_name_attribute,` +
` projections.idp_templates4_ldap.email_attribute,` + ` projections.idp_templates4_ldap2.preferred_username_attribute,` +
` projections.idp_templates4_ldap.email_verified,` + ` projections.idp_templates4_ldap2.email_attribute,` +
` projections.idp_templates4_ldap.phone_attribute,` + ` projections.idp_templates4_ldap2.email_verified,` +
` projections.idp_templates4_ldap.phone_verified_attribute,` + ` projections.idp_templates4_ldap2.phone_attribute,` +
` projections.idp_templates4_ldap.preferred_language_attribute,` + ` projections.idp_templates4_ldap2.phone_verified_attribute,` +
` projections.idp_templates4_ldap.avatar_url_attribute,` + ` projections.idp_templates4_ldap2.preferred_language_attribute,` +
` projections.idp_templates4_ldap.profile_attribute` + ` projections.idp_templates4_ldap2.avatar_url_attribute,` +
` projections.idp_templates4_ldap2.profile_attribute` +
` FROM projections.idp_templates4` + ` 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_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` + ` 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 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_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_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'` ` AS OF SYSTEM TIME '-1 ms'`
idpTemplateCols = []string{ idpTemplateCols = []string{
"id", "id",
@ -195,14 +197,15 @@ var (
"scopes", "scopes",
// ldap config // ldap config
"idp_id", "idp_id",
"host", "servers",
"port", "start_tls",
"tls",
"base_dn", "base_dn",
"user_object_class", "bind_dn",
"user_unique_attribute", "bind_password",
"admin", "user_base",
"password", "user_object_classes",
"user_filters",
"timeout",
"id_attribute", "id_attribute",
"first_name_attribute", "first_name_attribute",
"last_name_attribute", "last_name_attribute",
@ -289,28 +292,29 @@ var (
` projections.idp_templates4_google.client_secret,` + ` projections.idp_templates4_google.client_secret,` +
` projections.idp_templates4_google.scopes,` + ` projections.idp_templates4_google.scopes,` +
// ldap // ldap
` projections.idp_templates4_ldap.idp_id,` + ` projections.idp_templates4_ldap2.idp_id,` +
` projections.idp_templates4_ldap.host,` + ` projections.idp_templates4_ldap2.servers,` +
` projections.idp_templates4_ldap.port,` + ` projections.idp_templates4_ldap2.start_tls,` +
` projections.idp_templates4_ldap.tls,` + ` projections.idp_templates4_ldap2.base_dn,` +
` projections.idp_templates4_ldap.base_dn,` + ` projections.idp_templates4_ldap2.bind_dn,` +
` projections.idp_templates4_ldap.user_object_class,` + ` projections.idp_templates4_ldap2.bind_password,` +
` projections.idp_templates4_ldap.user_unique_attribute,` + ` projections.idp_templates4_ldap2.user_base,` +
` projections.idp_templates4_ldap.admin,` + ` projections.idp_templates4_ldap2.user_object_classes,` +
` projections.idp_templates4_ldap.password,` + ` projections.idp_templates4_ldap2.user_filters,` +
` projections.idp_templates4_ldap.id_attribute,` + ` projections.idp_templates4_ldap2.timeout,` +
` projections.idp_templates4_ldap.first_name_attribute,` + ` projections.idp_templates4_ldap2.id_attribute,` +
` projections.idp_templates4_ldap.last_name_attribute,` + ` projections.idp_templates4_ldap2.first_name_attribute,` +
` projections.idp_templates4_ldap.display_name_attribute,` + ` projections.idp_templates4_ldap2.last_name_attribute,` +
` projections.idp_templates4_ldap.nick_name_attribute,` + ` projections.idp_templates4_ldap2.display_name_attribute,` +
` projections.idp_templates4_ldap.preferred_username_attribute,` + ` projections.idp_templates4_ldap2.nick_name_attribute,` +
` projections.idp_templates4_ldap.email_attribute,` + ` projections.idp_templates4_ldap2.preferred_username_attribute,` +
` projections.idp_templates4_ldap.email_verified,` + ` projections.idp_templates4_ldap2.email_attribute,` +
` projections.idp_templates4_ldap.phone_attribute,` + ` projections.idp_templates4_ldap2.email_verified,` +
` projections.idp_templates4_ldap.phone_verified_attribute,` + ` projections.idp_templates4_ldap2.phone_attribute,` +
` projections.idp_templates4_ldap.preferred_language_attribute,` + ` projections.idp_templates4_ldap2.phone_verified_attribute,` +
` projections.idp_templates4_ldap.avatar_url_attribute,` + ` projections.idp_templates4_ldap2.preferred_language_attribute,` +
` projections.idp_templates4_ldap.profile_attribute,` + ` projections.idp_templates4_ldap2.avatar_url_attribute,` +
` projections.idp_templates4_ldap2.profile_attribute,` +
` COUNT(*) OVER ()` + ` COUNT(*) OVER ()` +
` FROM projections.idp_templates4` + ` 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_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 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_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_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'` ` AS OF SYSTEM TIME '-1 ms'`
idpTemplatesCols = []string{ idpTemplatesCols = []string{
"id", "id",
@ -398,14 +402,15 @@ var (
"scopes", "scopes",
// ldap config // ldap config
"idp_id", "idp_id",
"host", "servers",
"port", "start_tls",
"tls",
"base_dn", "base_dn",
"user_object_class", "bind_dn",
"user_unique_attribute", "bind_password",
"admin", "user_base",
"password", "user_object_classes",
"user_filters",
"timeout",
"id_attribute", "id_attribute",
"first_name_attribute", "first_name_attribute",
"last_name_attribute", "last_name_attribute",
@ -554,6 +559,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
), ),
}, },
@ -685,6 +691,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
), ),
}, },
@ -814,6 +821,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
), ),
}, },
@ -942,6 +950,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
), ),
}, },
@ -1069,6 +1078,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
), ),
}, },
@ -1196,6 +1206,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
), ),
}, },
@ -1324,6 +1335,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
), ),
}, },
@ -1430,14 +1442,15 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
// ldap config // ldap config
"idp-id", "idp-id",
"host", database.StringArray{"server"},
"port",
true, true,
"base", "base",
"user", "dn",
"uid",
"admin",
nil, nil,
"user",
database.StringArray{"object"},
database.StringArray{"filter"},
time.Duration(30000000000),
"id", "id",
"first", "first",
"last", "last",
@ -1469,14 +1482,15 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
IsAutoCreation: true, IsAutoCreation: true,
IsAutoUpdate: true, IsAutoUpdate: true,
LDAPIDPTemplate: &LDAPIDPTemplate{ LDAPIDPTemplate: &LDAPIDPTemplate{
IDPID: "idp-id", IDPID: "idp-id",
Host: "host", Servers: []string{"server"},
Port: "port", StartTLS: true,
TLS: true, BaseDN: "base",
BaseDN: "base", BindDN: "dn",
UserObjectClass: "user", UserBase: "user",
UserUniqueAttribute: "uid", UserObjectClasses: []string{"object"},
Admin: "admin", UserFilters: []string{"filter"},
Timeout: time.Duration(30000000000),
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "first", FirstNameAttribute: "first",
@ -1597,6 +1611,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
), ),
}, },
@ -1733,14 +1748,15 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
// ldap config // ldap config
"idp-id", "idp-id",
"host", database.StringArray{"server"},
"port",
true, true,
"base", "base",
"user", "dn",
"uid",
"admin",
nil, nil,
"user",
database.StringArray{"object"},
database.StringArray{"filter"},
time.Duration(30000000000),
"id", "id",
"first", "first",
"last", "last",
@ -1778,14 +1794,15 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
IsAutoCreation: true, IsAutoCreation: true,
IsAutoUpdate: true, IsAutoUpdate: true,
LDAPIDPTemplate: &LDAPIDPTemplate{ LDAPIDPTemplate: &LDAPIDPTemplate{
IDPID: "idp-id", IDPID: "idp-id",
Host: "host", Servers: []string{"server"},
Port: "port", StartTLS: true,
TLS: true, BaseDN: "base",
BaseDN: "base", BindDN: "dn",
UserObjectClass: "user", UserBase: "user",
UserUniqueAttribute: "uid", UserObjectClasses: []string{"object"},
Admin: "admin", UserFilters: []string{"filter"},
Timeout: time.Duration(30000000000),
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "first", FirstNameAttribute: "first",
@ -1909,6 +1926,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
}, },
), ),
@ -2018,14 +2036,15 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
// ldap config // ldap config
"idp-id-ldap", "idp-id-ldap",
"host", database.StringArray{"server"},
"port",
true, true,
"base", "base",
"user", "dn",
"uid",
"admin",
nil, nil,
"user",
database.StringArray{"object"},
database.StringArray{"filter"},
time.Duration(30000000000),
"id", "id",
"first", "first",
"last", "last",
@ -2135,6 +2154,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
{ {
"idp-id-oauth", "idp-id-oauth",
@ -2231,6 +2251,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
{ {
"idp-id-oidc", "idp-id-oidc",
@ -2327,6 +2348,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
{ {
"idp-id-jwt", "idp-id-jwt",
@ -2423,6 +2445,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
}, },
), ),
@ -2447,14 +2470,15 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
IsAutoCreation: true, IsAutoCreation: true,
IsAutoUpdate: true, IsAutoUpdate: true,
LDAPIDPTemplate: &LDAPIDPTemplate{ LDAPIDPTemplate: &LDAPIDPTemplate{
IDPID: "idp-id-ldap", IDPID: "idp-id-ldap",
Host: "host", Servers: []string{"server"},
Port: "port", StartTLS: true,
TLS: true, BaseDN: "base",
BaseDN: "base", BindDN: "dn",
UserObjectClass: "user", UserBase: "user",
UserUniqueAttribute: "uid", UserObjectClasses: []string{"object"},
Admin: "admin", UserFilters: []string{"filter"},
Timeout: time.Duration(30000000000),
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "first", FirstNameAttribute: "first",

View File

@ -38,7 +38,7 @@ const (
IDPTemplateGitLabSuffix = "gitlab" IDPTemplateGitLabSuffix = "gitlab"
IDPTemplateGitLabSelfHostedSuffix = "gitlab_self_hosted" IDPTemplateGitLabSelfHostedSuffix = "gitlab_self_hosted"
IDPTemplateGoogleSuffix = "google" IDPTemplateGoogleSuffix = "google"
IDPTemplateLDAPSuffix = "ldap" IDPTemplateLDAPSuffix = "ldap2"
IDPTemplateIDCol = "id" IDPTemplateIDCol = "id"
IDPTemplateCreationDateCol = "creation_date" IDPTemplateCreationDateCol = "creation_date"
@ -125,14 +125,15 @@ const (
LDAPIDCol = "idp_id" LDAPIDCol = "idp_id"
LDAPInstanceIDCol = "instance_id" LDAPInstanceIDCol = "instance_id"
LDAPHostCol = "host" LDAPServersCol = "servers"
LDAPPortCol = "port" LDAPStartTLSCol = "start_tls"
LDAPTlsCol = "tls"
LDAPBaseDNCol = "base_dn" LDAPBaseDNCol = "base_dn"
LDAPUserObjectClassCol = "user_object_class" LDAPBindDNCol = "bind_dn"
LDAPUserUniqueAttributeCol = "user_unique_attribute" LDAPBindPasswordCol = "bind_password"
LDAPAdminCol = "admin" LDAPUserBaseCol = "user_base"
LDAPPasswordCol = "password" LDAPUserObjectClassesCol = "user_object_classes"
LDAPUserFiltersCol = "user_filters"
LDAPTimeoutCol = "timeout"
LDAPIDAttributeCol = "id_attribute" LDAPIDAttributeCol = "id_attribute"
LDAPFirstNameAttributeCol = "first_name_attribute" LDAPFirstNameAttributeCol = "first_name_attribute"
LDAPLastNameAttributeCol = "last_name_attribute" LDAPLastNameAttributeCol = "last_name_attribute"
@ -293,14 +294,15 @@ func newIDPTemplateProjection(ctx context.Context, config crdb.StatementHandlerC
crdb.NewSuffixedTable([]*crdb.Column{ crdb.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(LDAPIDCol, crdb.ColumnTypeText), crdb.NewColumn(LDAPIDCol, crdb.ColumnTypeText),
crdb.NewColumn(LDAPInstanceIDCol, crdb.ColumnTypeText), crdb.NewColumn(LDAPInstanceIDCol, crdb.ColumnTypeText),
crdb.NewColumn(LDAPHostCol, crdb.ColumnTypeText, crdb.Nullable()), crdb.NewColumn(LDAPServersCol, crdb.ColumnTypeTextArray),
crdb.NewColumn(LDAPPortCol, crdb.ColumnTypeText, crdb.Nullable()), crdb.NewColumn(LDAPStartTLSCol, crdb.ColumnTypeBool),
crdb.NewColumn(LDAPTlsCol, crdb.ColumnTypeBool, crdb.Nullable()), crdb.NewColumn(LDAPBaseDNCol, crdb.ColumnTypeText),
crdb.NewColumn(LDAPBaseDNCol, crdb.ColumnTypeText, crdb.Nullable()), crdb.NewColumn(LDAPBindDNCol, crdb.ColumnTypeText),
crdb.NewColumn(LDAPUserObjectClassCol, crdb.ColumnTypeText, crdb.Nullable()), crdb.NewColumn(LDAPBindPasswordCol, crdb.ColumnTypeJSONB),
crdb.NewColumn(LDAPUserUniqueAttributeCol, crdb.ColumnTypeText, crdb.Nullable()), crdb.NewColumn(LDAPUserBaseCol, crdb.ColumnTypeText),
crdb.NewColumn(LDAPAdminCol, crdb.ColumnTypeText, crdb.Nullable()), crdb.NewColumn(LDAPUserObjectClassesCol, crdb.ColumnTypeTextArray),
crdb.NewColumn(LDAPPasswordCol, crdb.ColumnTypeJSONB, crdb.Nullable()), crdb.NewColumn(LDAPUserFiltersCol, crdb.ColumnTypeTextArray),
crdb.NewColumn(LDAPTimeoutCol, crdb.ColumnTypeInt64),
crdb.NewColumn(LDAPIDAttributeCol, crdb.ColumnTypeText, crdb.Nullable()), crdb.NewColumn(LDAPIDAttributeCol, crdb.ColumnTypeText, crdb.Nullable()),
crdb.NewColumn(LDAPFirstNameAttributeCol, crdb.ColumnTypeText, crdb.Nullable()), crdb.NewColumn(LDAPFirstNameAttributeCol, crdb.ColumnTypeText, crdb.Nullable()),
crdb.NewColumn(LDAPLastNameAttributeCol, 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.Column{
handler.NewCol(LDAPIDCol, idpEvent.ID), handler.NewCol(LDAPIDCol, idpEvent.ID),
handler.NewCol(LDAPInstanceIDCol, idpEvent.Aggregate().InstanceID), handler.NewCol(LDAPInstanceIDCol, idpEvent.Aggregate().InstanceID),
handler.NewCol(LDAPHostCol, idpEvent.Host), handler.NewCol(LDAPServersCol, database.StringArray(idpEvent.Servers)),
handler.NewCol(LDAPPortCol, idpEvent.Port), handler.NewCol(LDAPStartTLSCol, idpEvent.StartTLS),
handler.NewCol(LDAPTlsCol, idpEvent.TLS),
handler.NewCol(LDAPBaseDNCol, idpEvent.BaseDN), handler.NewCol(LDAPBaseDNCol, idpEvent.BaseDN),
handler.NewCol(LDAPUserObjectClassCol, idpEvent.UserObjectClass), handler.NewCol(LDAPBindDNCol, idpEvent.BindDN),
handler.NewCol(LDAPUserUniqueAttributeCol, idpEvent.UserUniqueAttribute), handler.NewCol(LDAPBindPasswordCol, idpEvent.BindPassword),
handler.NewCol(LDAPAdminCol, idpEvent.Admin), handler.NewCol(LDAPUserBaseCol, idpEvent.UserBase),
handler.NewCol(LDAPPasswordCol, idpEvent.Password), 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(LDAPIDAttributeCol, idpEvent.IDAttribute),
handler.NewCol(LDAPFirstNameAttributeCol, idpEvent.FirstNameAttribute), handler.NewCol(LDAPFirstNameAttributeCol, idpEvent.FirstNameAttribute),
handler.NewCol(LDAPLastNameAttributeCol, idpEvent.LastNameAttribute), handler.NewCol(LDAPLastNameAttributeCol, idpEvent.LastNameAttribute),
@ -1962,29 +1965,32 @@ func reduceGoogleIDPChangedColumns(idpEvent idp.GoogleIDPChangedEvent) []handler
func reduceLDAPIDPChangedColumns(idpEvent idp.LDAPIDPChangedEvent) []handler.Column { func reduceLDAPIDPChangedColumns(idpEvent idp.LDAPIDPChangedEvent) []handler.Column {
ldapCols := make([]handler.Column, 0, 4) ldapCols := make([]handler.Column, 0, 4)
if idpEvent.Host != nil { if idpEvent.Servers != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPHostCol, *idpEvent.Host)) ldapCols = append(ldapCols, handler.NewCol(LDAPServersCol, database.StringArray(idpEvent.Servers)))
} }
if idpEvent.Port != nil { if idpEvent.StartTLS != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPPortCol, *idpEvent.Port)) ldapCols = append(ldapCols, handler.NewCol(LDAPStartTLSCol, *idpEvent.StartTLS))
}
if idpEvent.TLS != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPTlsCol, *idpEvent.TLS))
} }
if idpEvent.BaseDN != nil { if idpEvent.BaseDN != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPBaseDNCol, *idpEvent.BaseDN)) ldapCols = append(ldapCols, handler.NewCol(LDAPBaseDNCol, *idpEvent.BaseDN))
} }
if idpEvent.UserObjectClass != nil { if idpEvent.BindDN != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPUserObjectClassCol, *idpEvent.UserObjectClass)) ldapCols = append(ldapCols, handler.NewCol(LDAPBindDNCol, *idpEvent.BindDN))
} }
if idpEvent.UserUniqueAttribute != nil { if idpEvent.BindPassword != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPUserUniqueAttributeCol, *idpEvent.UserUniqueAttribute)) ldapCols = append(ldapCols, handler.NewCol(LDAPBindPasswordCol, idpEvent.BindPassword))
} }
if idpEvent.Admin != nil { if idpEvent.UserBase != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPAdminCol, *idpEvent.Admin)) ldapCols = append(ldapCols, handler.NewCol(LDAPUserBaseCol, *idpEvent.UserBase))
} }
if idpEvent.Password != nil { if idpEvent.UserObjectClasses != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPPasswordCol, *idpEvent.Password)) 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 { if idpEvent.IDAttribute != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPIDAttributeCol, *idpEvent.IDAttribute)) ldapCols = append(ldapCols, handler.NewCol(LDAPIDAttributeCol, *idpEvent.IDAttribute))

View File

@ -2,6 +2,7 @@ package projection
import ( import (
"testing" "testing"
"time"
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
@ -2033,18 +2034,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
[]byte(`{ []byte(`{
"id": "idp-id", "id": "idp-id",
"name": "custom-zitadel-instance", "name": "custom-zitadel-instance",
"host": "host", "servers": ["server"],
"port": "port", "startTls": false,
"tls": true, "baseDN": "basedn",
"baseDN": "base", "bindDN": "binddn",
"userObjectClass": "user", "bindPassword": {
"userUniqueAttribute": "uid",
"admin": "admin",
"password": {
"cryptoType": 0, "cryptoType": 0,
"algorithm": "RSA-265", "algorithm": "RSA-265",
"keyId": "key-id" "keyId": "key-id"
}, },
"userBase": "user",
"userObjectClasses": ["object"],
"userFilters": ["filter"],
"timeout": 30000000000,
"idAttribute": "id", "idAttribute": "id",
"firstNameAttribute": "first", "firstNameAttribute": "first",
"lastNameAttribute": "last", "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{}{ expectedArgs: []interface{}{
"idp-id", "idp-id",
"instance-id", "instance-id",
"host", database.StringArray{"server"},
"port", false,
true, "basedn",
"base", "binddn",
"user",
"uid",
"admin",
anyArg{}, anyArg{},
"user",
database.StringArray{"object"},
database.StringArray{"filter"},
time.Duration(30000000000),
"id", "id",
"first", "first",
"last", "last",
@ -2132,18 +2135,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
[]byte(`{ []byte(`{
"id": "idp-id", "id": "idp-id",
"name": "custom-zitadel-instance", "name": "custom-zitadel-instance",
"host": "host", "servers": ["server"],
"port": "port", "startTls": false,
"tls": true, "baseDN": "basedn",
"baseDN": "base", "bindDN": "binddn",
"userObjectClass": "user", "bindPassword": {
"userUniqueAttribute": "uid",
"admin": "admin",
"password": {
"cryptoType": 0, "cryptoType": 0,
"algorithm": "RSA-265", "algorithm": "RSA-265",
"keyId": "key-id" "keyId": "key-id"
}, },
"userBase": "user",
"userObjectClasses": ["object"],
"userFilters": ["filter"],
"timeout": 30000000000,
"idAttribute": "id", "idAttribute": "id",
"firstNameAttribute": "first", "firstNameAttribute": "first",
"lastNameAttribute": "last", "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{}{ expectedArgs: []interface{}{
"idp-id", "idp-id",
"instance-id", "instance-id",
"host", database.StringArray{"server"},
"port", false,
true, "basedn",
"base", "binddn",
"user",
"uid",
"admin",
anyArg{}, anyArg{},
"user",
database.StringArray{"object"},
database.StringArray{"filter"},
time.Duration(30000000000),
"id", "id",
"first", "first",
"last", "last",
@ -2231,7 +2236,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
[]byte(`{ []byte(`{
"id": "idp-id", "id": "idp-id",
"name": "custom-zitadel-instance", "name": "custom-zitadel-instance",
"host": "host" "baseDN": "basedn"
}`), }`),
), instance.LDAPIDPChangedEventMapper), ), 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{}{ expectedArgs: []interface{}{
"host", "basedn",
"idp-id", "idp-id",
"instance-id", "instance-id",
}, },
@ -2273,18 +2278,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
[]byte(`{ []byte(`{
"id": "idp-id", "id": "idp-id",
"name": "custom-zitadel-instance", "name": "custom-zitadel-instance",
"host": "host", "servers": ["server"],
"port": "port", "startTls": false,
"tls": true, "baseDN": "basedn",
"baseDN": "base", "bindDN": "binddn",
"userObjectClass": "user", "bindPassword": {
"userUniqueAttribute": "uid",
"admin": "admin",
"password": {
"cryptoType": 0, "cryptoType": 0,
"algorithm": "RSA-265", "algorithm": "RSA-265",
"keyId": "key-id" "keyId": "key-id"
}, },
"userBase": "user",
"userObjectClasses": ["object"],
"userFilters": ["filter"],
"timeout": 30000000000,
"idAttribute": "id", "idAttribute": "id",
"firstNameAttribute": "first", "firstNameAttribute": "first",
"lastNameAttribute": "last", "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{}{ expectedArgs: []interface{}{
"host", database.StringArray{"server"},
"port", false,
true, "basedn",
"base", "binddn",
"user",
"uid",
"admin",
anyArg{}, anyArg{},
"user",
database.StringArray{"object"},
database.StringArray{"filter"},
time.Duration(30000000000),
"id", "id",
"first", "first",
"last", "last",

View File

@ -2,27 +2,28 @@ package idp
import ( import (
"encoding/json" "encoding/json"
"time"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository" "github.com/zitadel/zitadel/internal/eventstore/repository"
"github.com/zitadel/zitadel/internal/repository/idpconfig"
) )
type LDAPIDPAddedEvent struct { type LDAPIDPAddedEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Host string `json:"host"` Servers []string `json:"servers"`
Port string `json:"port,omitempty"` StartTLS bool `json:"startTLS"`
TLS bool `json:"tls"` BaseDN string `json:"baseDN"`
BaseDN string `json:"baseDN"` BindDN string `json:"bindDN"`
UserObjectClass string `json:"userObjectClass"` BindPassword *crypto.CryptoValue `json:"bindPassword"`
UserUniqueAttribute string `json:"userUniqueAttribute"` UserBase string `json:"userBase"`
Admin string `json:"admin"` UserObjectClasses []string `json:"userObjectClasses"`
Password *crypto.CryptoValue `json:"password"` UserFilters []string `json:"userFilters"`
Timeout time.Duration `json:"timeout"`
LDAPAttributes LDAPAttributes
Options Options
@ -132,33 +133,35 @@ func (o *LDAPAttributes) ReduceChanges(changes LDAPAttributeChanges) {
func NewLDAPIDPAddedEvent( func NewLDAPIDPAddedEvent(
base *eventstore.BaseEvent, base *eventstore.BaseEvent,
id, id string,
name, name string,
host, servers []string,
port string, startTLS bool,
tls bool, baseDN string,
baseDN, bindDN string,
userObjectClass, bindPassword *crypto.CryptoValue,
userUniqueAttribute, userBase string,
admin string, userObjectClasses []string,
password *crypto.CryptoValue, userFilters []string,
timeout time.Duration,
attributes LDAPAttributes, attributes LDAPAttributes,
options Options, options Options,
) *LDAPIDPAddedEvent { ) *LDAPIDPAddedEvent {
return &LDAPIDPAddedEvent{ return &LDAPIDPAddedEvent{
BaseEvent: *base, BaseEvent: *base,
ID: id, ID: id,
Name: name, Name: name,
Host: host, Servers: servers,
Port: port, StartTLS: startTLS,
TLS: tls, BaseDN: baseDN,
BaseDN: baseDN, BindDN: bindDN,
UserObjectClass: userObjectClass, BindPassword: bindPassword,
UserUniqueAttribute: userUniqueAttribute, UserBase: userBase,
Admin: admin, UserObjectClasses: userObjectClasses,
Password: password, UserFilters: userFilters,
LDAPAttributes: attributes, Timeout: timeout,
Options: options, LDAPAttributes: attributes,
Options: options,
} }
} }
@ -167,7 +170,7 @@ func (e *LDAPIDPAddedEvent) Data() interface{} {
} }
func (e *LDAPIDPAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { 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) { func LDAPIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
@ -186,18 +189,17 @@ func LDAPIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error)
type LDAPIDPChangedEvent struct { type LDAPIDPChangedEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`
oldName string ID string `json:"id"`
Name *string `json:"name,omitempty"`
ID string `json:"id"` Servers []string `json:"servers,omitempty"`
Name *string `json:"name,omitempty"` StartTLS *bool `json:"startTLS,omitempty"`
Host *string `json:"host,omitempty"` BaseDN *string `json:"baseDN,omitempty"`
Port *string `json:"port,omitempty"` BindDN *string `json:"bindDN,omitempty"`
TLS *bool `json:"tls,omitempty"` BindPassword *crypto.CryptoValue `json:"bindPassword,omitempty"`
BaseDN *string `json:"baseDN,omitempty"` UserBase *string `json:"userBase,omitempty"`
UserObjectClass *string `json:"userObjectClass,omitempty"` UserObjectClasses []string `json:"userObjectClasses,omitempty"`
UserUniqueAttribute *string `json:"userUniqueAttribute,omitempty"` UserFilters []string `json:"userFilters,omitempty"`
Admin *string `json:"admin,omitempty"` Timeout *time.Duration `json:"timeout,omitempty"`
Password *crypto.CryptoValue `json:"password,omitempty"`
LDAPAttributeChanges LDAPAttributeChanges
OptionChanges OptionChanges
@ -238,7 +240,6 @@ func (o LDAPAttributeChanges) IsZero() bool {
func NewLDAPIDPChangedEvent( func NewLDAPIDPChangedEvent(
base *eventstore.BaseEvent, base *eventstore.BaseEvent,
id string, id string,
oldName string,
changes []LDAPIDPChanges, changes []LDAPIDPChanges,
) (*LDAPIDPChangedEvent, error) { ) (*LDAPIDPChangedEvent, error) {
if len(changes) == 0 { if len(changes) == 0 {
@ -247,7 +248,6 @@ func NewLDAPIDPChangedEvent(
changedEvent := &LDAPIDPChangedEvent{ changedEvent := &LDAPIDPChangedEvent{
BaseEvent: *base, BaseEvent: *base,
ID: id, ID: id,
oldName: oldName,
} }
for _, change := range changes { for _, change := range changes {
change(changedEvent) 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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 { func (e *LDAPIDPChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
if e.Name == nil || e.oldName == *e.Name { // TODO: nil check should be enough? return nil
return nil
}
return []*eventstore.EventUniqueConstraint{
idpconfig.NewRemoveIDPConfigNameUniqueConstraint(e.oldName, e.Aggregate().ResourceOwner),
idpconfig.NewAddIDPConfigNameUniqueConstraint(*e.Name, e.Aggregate().ResourceOwner),
}
} }
func LDAPIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) { func LDAPIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {

View File

@ -2,6 +2,7 @@ package instance
import ( import (
"context" "context"
"time"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
@ -28,8 +29,8 @@ const (
GitLabSelfHostedIDPChangedEventType eventstore.EventType = "instance.idp.gitlab_self_hosted.changed" GitLabSelfHostedIDPChangedEventType eventstore.EventType = "instance.idp.gitlab_self_hosted.changed"
GoogleIDPAddedEventType eventstore.EventType = "instance.idp.google.added" GoogleIDPAddedEventType eventstore.EventType = "instance.idp.google.added"
GoogleIDPChangedEventType eventstore.EventType = "instance.idp.google.changed" GoogleIDPChangedEventType eventstore.EventType = "instance.idp.google.changed"
LDAPIDPAddedEventType eventstore.EventType = "instance.idp.ldap.added" LDAPIDPAddedEventType eventstore.EventType = "instance.idp.ldap.v2.added"
LDAPIDPChangedEventType eventstore.EventType = "instance.idp.ldap.changed" LDAPIDPChangedEventType eventstore.EventType = "instance.idp.ldap.v2.changed"
IDPRemovedEventType eventstore.EventType = "instance.idp.removed" IDPRemovedEventType eventstore.EventType = "instance.idp.removed"
) )
@ -751,15 +752,16 @@ func NewLDAPIDPAddedEvent(
ctx context.Context, ctx context.Context,
aggregate *eventstore.Aggregate, aggregate *eventstore.Aggregate,
id, id,
name, name string,
host, servers []string,
port string, startTLS bool,
tls bool, baseDN string,
baseDN, bindDN string,
userObjectClass, bindPassword *crypto.CryptoValue,
userUniqueAttribute, userBase string,
admin string, userObjectClasses []string,
password *crypto.CryptoValue, userFilters []string,
timeout time.Duration,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
) *LDAPIDPAddedEvent { ) *LDAPIDPAddedEvent {
@ -773,14 +775,15 @@ func NewLDAPIDPAddedEvent(
), ),
id, id,
name, name,
host, servers,
port, startTLS,
tls,
baseDN, baseDN,
userObjectClass, bindDN,
userUniqueAttribute, bindPassword,
admin, userBase,
password, userObjectClasses,
userFilters,
timeout,
attributes, attributes,
options, options,
), ),
@ -803,8 +806,7 @@ type LDAPIDPChangedEvent struct {
func NewLDAPIDPChangedEvent( func NewLDAPIDPChangedEvent(
ctx context.Context, ctx context.Context,
aggregate *eventstore.Aggregate, aggregate *eventstore.Aggregate,
id, id string,
oldName string,
changes []idp.LDAPIDPChanges, changes []idp.LDAPIDPChanges,
) (*LDAPIDPChangedEvent, error) { ) (*LDAPIDPChangedEvent, error) {
@ -815,7 +817,6 @@ func NewLDAPIDPChangedEvent(
LDAPIDPChangedEventType, LDAPIDPChangedEventType,
), ),
id, id,
oldName,
changes, changes,
) )
if err != nil { if err != nil {

View File

@ -2,6 +2,7 @@ package org
import ( import (
"context" "context"
"time"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
@ -751,15 +752,16 @@ func NewLDAPIDPAddedEvent(
ctx context.Context, ctx context.Context,
aggregate *eventstore.Aggregate, aggregate *eventstore.Aggregate,
id, id,
name, name string,
host, servers []string,
port string, startTLS bool,
tls bool, baseDN string,
baseDN, bindDN string,
userObjectClass, bindPassword *crypto.CryptoValue,
userUniqueAttribute, userBase string,
admin string, userObjectClasses []string,
password *crypto.CryptoValue, userFilters []string,
timeout time.Duration,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
) *LDAPIDPAddedEvent { ) *LDAPIDPAddedEvent {
@ -773,14 +775,15 @@ func NewLDAPIDPAddedEvent(
), ),
id, id,
name, name,
host, servers,
port, startTLS,
tls,
baseDN, baseDN,
userObjectClass, bindDN,
userUniqueAttribute, bindPassword,
admin, userBase,
password, userObjectClasses,
userFilters,
timeout,
attributes, attributes,
options, options,
), ),
@ -803,8 +806,7 @@ type LDAPIDPChangedEvent struct {
func NewLDAPIDPChangedEvent( func NewLDAPIDPChangedEvent(
ctx context.Context, ctx context.Context,
aggregate *eventstore.Aggregate, aggregate *eventstore.Aggregate,
id, id string,
oldName string,
changes []idp.LDAPIDPChanges, changes []idp.LDAPIDPChanges,
) (*LDAPIDPChangedEvent, error) { ) (*LDAPIDPChangedEvent, error) {
@ -815,7 +817,6 @@ func NewLDAPIDPChangedEvent(
LDAPIDPChangedEventType, LDAPIDPChangedEventType,
), ),
id, id,
oldName,
changes, changes,
) )
if err != nil { if err != nil {

View File

@ -4732,16 +4732,17 @@ message UpdateGoogleProviderResponse {
message AddLDAPProviderRequest { message AddLDAPProviderRequest {
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string host = 2 [(validate.rules).string = {min_len: 1, max_len: 200}]; repeated string servers = 2 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
string port = 3 [(validate.rules).string = {max_len: 5}]; bool start_tls = 3;
bool tls = 4; string base_dn = 4 [(validate.rules).string = {min_len: 1, max_len: 200}];
string base_dn = 5 [(validate.rules).string = {min_len: 1, max_len: 200}]; string bind_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 bind_password = 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 user_base = 7 [(validate.rules).string = {min_len: 1, max_len: 200}];
string admin = 8 [(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}}}];
string password = 9 [(validate.rules).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}}}];
zitadel.idp.v1.LDAPAttributes attributes = 10; google.protobuf.Duration timeout = 10;
zitadel.idp.v1.Options provider_options = 11; zitadel.idp.v1.LDAPAttributes attributes = 11;
zitadel.idp.v1.Options provider_options = 12;
} }
message AddLDAPProviderResponse { message AddLDAPProviderResponse {
@ -4752,16 +4753,17 @@ message AddLDAPProviderResponse {
message UpdateLDAPProviderRequest { message UpdateLDAPProviderRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; 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 name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string host = 3 [(validate.rules).string = {min_len: 1, max_len: 200}]; repeated string servers = 3 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
string port = 4 [(validate.rules).string = {max_len: 5}]; bool start_tls = 4;
bool tls = 5; string base_dn = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
string base_dn = 6 [(validate.rules).string = {min_len: 1, max_len: 200}]; string bind_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 bind_password = 7 [(validate.rules).string = {max_len: 200}];
string user_unique_attribute = 8 [(validate.rules).string = {min_len: 1, max_len: 200}]; string user_base = 8 [(validate.rules).string = {min_len: 1, max_len: 200}];
string admin = 9 [(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}}}];
string password = 10 [(validate.rules).string = {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}}}];
zitadel.idp.v1.LDAPAttributes attributes = 11; google.protobuf.Duration timeout = 11;
zitadel.idp.v1.Options provider_options = 12; zitadel.idp.v1.LDAPAttributes attributes = 12;
zitadel.idp.v1.Options provider_options = 13;
} }
message UpdateLDAPProviderResponse { message UpdateLDAPProviderResponse {

View File

@ -3,6 +3,7 @@ syntax = "proto3";
import "zitadel/object.proto"; import "zitadel/object.proto";
import "validate/validate.proto"; import "validate/validate.proto";
import "protoc-gen-openapiv2/options/annotations.proto"; import "protoc-gen-openapiv2/options/annotations.proto";
import "google/protobuf/duration.proto";
package zitadel.idp.v1; package zitadel.idp.v1;
@ -321,15 +322,15 @@ message GitLabSelfHostedConfig {
} }
message LDAPConfig { message LDAPConfig {
string host = 1; repeated string servers = 1;
string port = 2; bool start_tls = 2;
bool tls = 3; string base_dn = 3;
string base_dn = 4; string bind_dn = 4;
string user_object_class = 5; string user_base = 5;
string user_unique_attribute = 6; repeated string user_object_classes = 6;
string admin = 7; repeated string user_filters = 7;
LDAPAttributes attributes = 8; google.protobuf.Duration timeout = 8;
Options provider_options = 9; LDAPAttributes attributes = 9;
} }
message AzureADConfig { message AzureADConfig {

View File

@ -11406,16 +11406,17 @@ message UpdateGoogleProviderResponse {
message AddLDAPProviderRequest { message AddLDAPProviderRequest {
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string host = 2 [(validate.rules).string = {min_len: 1, max_len: 200}]; repeated string servers = 2 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
string port = 3 [(validate.rules).string = {max_len: 5}]; bool start_tls = 3;
bool tls = 4; string base_dn = 4 [(validate.rules).string = {min_len: 1, max_len: 200}];
string base_dn = 5 [(validate.rules).string = {min_len: 1, max_len: 200}]; string bind_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 bind_password = 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 user_base = 7 [(validate.rules).string = {min_len: 1, max_len: 200}];
string admin = 8 [(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}}}];
string password = 9 [(validate.rules).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}}}];
zitadel.idp.v1.LDAPAttributes attributes = 10; google.protobuf.Duration timeout = 10;
zitadel.idp.v1.Options provider_options = 11; zitadel.idp.v1.LDAPAttributes attributes = 11;
zitadel.idp.v1.Options provider_options = 12;
} }
message AddLDAPProviderResponse { message AddLDAPProviderResponse {
@ -11426,16 +11427,17 @@ message AddLDAPProviderResponse {
message UpdateLDAPProviderRequest { message UpdateLDAPProviderRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; 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 name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string host = 3 [(validate.rules).string = {min_len: 1, max_len: 200}]; repeated string servers = 3 [(validate.rules).repeated = {min_items: 1, max_items: 20, items: {string: {min_len: 1, max_len: 200}}}];
string port = 4 [(validate.rules).string = {max_len: 5}]; bool start_tls = 4;
bool tls = 5; string base_dn = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
string base_dn = 6 [(validate.rules).string = {min_len: 1, max_len: 200}]; string bind_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 bind_password = 7 [(validate.rules).string = {max_len: 200}];
string user_unique_attribute = 8 [(validate.rules).string = {min_len: 1, max_len: 200}]; string user_base = 8 [(validate.rules).string = {min_len: 1, max_len: 200}];
string admin = 9 [(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}}}];
string password = 10 [(validate.rules).string = {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}}}];
zitadel.idp.v1.LDAPAttributes attributes = 11; google.protobuf.Duration timeout = 11;
zitadel.idp.v1.Options provider_options = 12; zitadel.idp.v1.LDAPAttributes attributes = 12;
zitadel.idp.v1.Options provider_options = 13;
} }
message UpdateLDAPProviderResponse { message UpdateLDAPProviderResponse {