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/spf13/cobra v1.6.1
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.2
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203
github.com/ttacon/libphonenumber v1.2.1
github.com/zitadel/logging v0.3.4

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.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=

View File

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

View File

@ -1,6 +1,8 @@
package idp
import (
"google.golang.org/protobuf/types/known/durationpb"
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/domain"
iam_model "github.com/zitadel/zitadel/internal/iam/model"
@ -582,16 +584,21 @@ func googleConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.Goo
}
func ldapConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.LDAPIDPTemplate) {
var timeout *durationpb.Duration
if template.Timeout != 0 {
timeout = durationpb.New(template.Timeout)
}
providerConfig.Config = &idp_pb.ProviderConfig_Ldap{
Ldap: &idp_pb.LDAPConfig{
Host: template.Host,
Port: template.Port,
Tls: template.TLS,
BaseDn: template.BaseDN,
UserObjectClass: template.UserObjectClass,
UserUniqueAttribute: template.UserUniqueAttribute,
Admin: template.Admin,
Attributes: ldapAttributesToPb(template.LDAPAttributes),
Servers: template.Servers,
StartTls: template.StartTLS,
BaseDn: template.BaseDN,
BindDn: template.BindDN,
UserBase: template.UserBase,
UserObjectClasses: template.UserObjectClasses,
UserFilters: template.UserFilters,
Timeout: timeout,
Attributes: ldapAttributesToPb(template.LDAPAttributes),
},
}
}

View File

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

View File

@ -23,6 +23,7 @@ import (
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
"github.com/zitadel/zitadel/internal/idp/providers/google"
"github.com/zitadel/zitadel/internal/idp/providers/jwt"
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
"github.com/zitadel/zitadel/internal/query"
@ -157,8 +158,9 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
provider, err = l.gitlabSelfHostedProvider(r.Context(), identityProvider)
case domain.IDPTypeGoogle:
provider, err = l.googleProvider(r.Context(), identityProvider)
case domain.IDPTypeLDAP,
domain.IDPTypeUnspecified:
case domain.IDPTypeLDAP:
provider, err = l.ldapProvider(r.Context(), identityProvider)
case domain.IDPTypeUnspecified:
fallthrough
default:
l.renderLogin(w, r, authReq, errors.ThrowInvalidArgument(nil, "LOGIN-AShek", "Errors.ExternalIDP.IDPTypeNotImplemented"))
@ -604,6 +606,69 @@ func (l *Login) updateExternalUser(ctx context.Context, authReq *domain.AuthRequ
return nil
}
func (l *Login) ldapProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*ldap.Provider, error) {
password, err := crypto.DecryptString(identityProvider.LDAPIDPTemplate.BindPassword, l.idpConfigAlg)
if err != nil {
return nil, err
}
var opts []ldap.ProviderOpts
if !identityProvider.LDAPIDPTemplate.StartTLS {
opts = append(opts, ldap.WithoutStartTLS())
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.IDAttribute != "" {
opts = append(opts, ldap.WithCustomIDAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.IDAttribute))
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.FirstNameAttribute != "" {
opts = append(opts, ldap.WithFirstNameAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.FirstNameAttribute))
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.LastNameAttribute != "" {
opts = append(opts, ldap.WithLastNameAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.LastNameAttribute))
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.DisplayNameAttribute != "" {
opts = append(opts, ldap.WithDisplayNameAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.DisplayNameAttribute))
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.NickNameAttribute != "" {
opts = append(opts, ldap.WithNickNameAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.NickNameAttribute))
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.PreferredUsernameAttribute != "" {
opts = append(opts, ldap.WithPreferredUsernameAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.PreferredUsernameAttribute))
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.EmailAttribute != "" {
opts = append(opts, ldap.WithEmailAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.EmailAttribute))
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.EmailVerifiedAttribute != "" {
opts = append(opts, ldap.WithEmailVerifiedAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.EmailVerifiedAttribute))
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.PhoneAttribute != "" {
opts = append(opts, ldap.WithPhoneAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.PhoneAttribute))
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.PhoneVerifiedAttribute != "" {
opts = append(opts, ldap.WithPhoneVerifiedAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.PhoneVerifiedAttribute))
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.PreferredLanguageAttribute != "" {
opts = append(opts, ldap.WithPreferredLanguageAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.PreferredLanguageAttribute))
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.AvatarURLAttribute != "" {
opts = append(opts, ldap.WithAvatarURLAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.AvatarURLAttribute))
}
if identityProvider.LDAPIDPTemplate.LDAPAttributes.ProfileAttribute != "" {
opts = append(opts, ldap.WithProfileAttribute(identityProvider.LDAPIDPTemplate.LDAPAttributes.ProfileAttribute))
}
return ldap.New(
identityProvider.Name,
identityProvider.Servers,
identityProvider.BaseDN,
identityProvider.BindDN,
password,
identityProvider.UserBase,
identityProvider.UserObjectClasses,
identityProvider.UserFilters,
identityProvider.Timeout,
l.baseURL(ctx)+EndpointLDAPLogin+"?"+QueryAuthRequestID+"=",
opts...,
), nil
}
func (l *Login) googleProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*google.Provider, error) {
errorHandler := func(w http.ResponseWriter, r *http.Request, errorType string, errorDesc string, state string) {
logging.Errorf("token exchanged failed: %s - %s (state: %s)", errorType, errorType, state)

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

View File

@ -15,6 +15,8 @@ const (
EndpointExternalLoginCallback = "/login/externalidp/callback"
EndpointJWTAuthorize = "/login/jwt/authorize"
EndpointJWTCallback = "/login/jwt/callback"
EndpointLDAPLogin = "/login/ldap"
EndpointLDAPCallback = "/login/ldap/callback"
EndpointPasswordlessLogin = "/login/passwordless"
EndpointPasswordlessRegistration = "/login/passwordless/init"
EndpointPasswordlessPrompt = "/login/passwordless/prompt"
@ -102,6 +104,8 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
router.HandleFunc(EndpointRegisterOrg, login.handleRegisterOrg).Methods(http.MethodGet)
router.HandleFunc(EndpointRegisterOrg, login.handleRegisterOrgCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointLoginSuccess, login.handleLoginSuccess).Methods(http.MethodGet)
router.HandleFunc(EndpointLDAPLogin, login.handleLDAP).Methods(http.MethodGet)
router.HandleFunc(EndpointLDAPCallback, login.handleLDAPCallback).Methods(http.MethodPost)
router.SkipClean(true).Handle("", http.RedirectHandler(HandlerPrefix+"/", http.StatusMovedPermanently))
return router
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,13 @@ Login:
RegisterButtonText: 注册
NextButtonText: 继续
LDAP:
Title: 注册
Description: 输入您的登录数据。
LoginNameLabel: 登录名
PasswordLabel: 密码
NextButtonText: 继续
SelectAccount:
Title: 选择账户
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
AutoRegisterExternalUser(ctx context.Context, user *domain.Human, externalIDP *domain.UserIDPLink, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) error
ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error
ResetSelectedIDP(ctx context.Context, authReqID, userAgentID string) error
}

View File

@ -461,6 +461,15 @@ func (repo *AuthRequestRepo) ResetLinkingUsers(ctx context.Context, authReqID, u
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
func (repo *AuthRequestRepo) ResetSelectedIDP(ctx context.Context, authReqID, userAgentID string) error {
request, err := repo.getAuthRequest(ctx, authReqID, userAgentID)
if err != nil {
return err
}
request.SelectedIDPConfigID = ""
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *domain.Human, externalIDP *domain.UserIDPLink, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) (err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package ldap
import (
"context"
"time"
"github.com/zitadel/zitadel/internal/idp"
)
@ -12,16 +13,18 @@ var _ idp.Provider = (*Provider)(nil)
// Provider is the [idp.Provider] implementation for a generic LDAP provider
type Provider struct {
name string
host string
port string
tls bool
baseDN string
userObjectClass string
userUniqueAttribute string
admin string
password string
loginUrl string
name string
servers []string
startTLS bool
baseDN string
bindDN string
bindPassword string
userBase string
userObjectClasses []string
userFilters []string
timeout time.Duration
loginUrl string
isLinkingAllowed bool
isCreationAllowed bool
@ -74,17 +77,10 @@ func WithAutoUpdate() ProviderOpts {
}
}
// WithCustomPort configures a custom port used for the communication instead of :389 as per default
func WithCustomPort(port string) ProviderOpts {
// WithoutStartTLS configures to communication insecure with the LDAP server without startTLS
func WithoutStartTLS() ProviderOpts {
return func(p *Provider) {
p.port = port
}
}
// Insecure configures to communication insecure with the LDAP server without TLS
func Insecure() ProviderOpts {
return func(p *Provider) {
p.tls = false
p.startTLS = false
}
}
@ -181,27 +177,29 @@ func WithProfileAttribute(name string) ProviderOpts {
func New(
name string,
host string,
servers []string,
baseDN string,
userObjectClass string,
userUniqueAttribute string,
admin string,
password string,
bindDN string,
bindPassword string,
userBase string,
userObjectClasses []string,
userFilters []string,
timeout time.Duration,
loginUrl string,
options ...ProviderOpts,
) *Provider {
provider := &Provider{
name: name,
host: host,
port: DefaultPort,
tls: true,
baseDN: baseDN,
userObjectClass: userObjectClass,
userUniqueAttribute: userUniqueAttribute,
admin: admin,
password: password,
loginUrl: loginUrl,
idAttribute: userUniqueAttribute,
name: name,
servers: servers,
startTLS: true,
baseDN: baseDN,
bindDN: bindDN,
bindPassword: bindPassword,
userBase: userBase,
userObjectClasses: userObjectClasses,
userFilters: userFilters,
timeout: timeout,
loginUrl: loginUrl,
}
for _, option := range options {
option(provider)
@ -216,7 +214,7 @@ func (p *Provider) Name() string {
func (p *Provider) BeginAuth(ctx context.Context, state string, params ...any) (idp.Session, error) {
return &Session{
Provider: p,
loginUrl: p.loginUrl + "?state=" + state,
loginUrl: p.loginUrl + state,
}, nil
}
@ -235,3 +233,47 @@ func (p *Provider) IsAutoCreation() bool {
func (p *Provider) IsAutoUpdate() bool {
return p.isAutoUpdate
}
func (p *Provider) getNecessaryAttributes() []string {
attributes := []string{p.userBase}
if p.idAttribute != "" {
attributes = append(attributes, p.idAttribute)
}
if p.firstNameAttribute != "" {
attributes = append(attributes, p.firstNameAttribute)
}
if p.lastNameAttribute != "" {
attributes = append(attributes, p.lastNameAttribute)
}
if p.displayNameAttribute != "" {
attributes = append(attributes, p.displayNameAttribute)
}
if p.nickNameAttribute != "" {
attributes = append(attributes, p.nickNameAttribute)
}
if p.preferredUsernameAttribute != "" {
attributes = append(attributes, p.preferredUsernameAttribute)
}
if p.emailAttribute != "" {
attributes = append(attributes, p.emailAttribute)
}
if p.emailVerifiedAttribute != "" {
attributes = append(attributes, p.emailVerifiedAttribute)
}
if p.phoneAttribute != "" {
attributes = append(attributes, p.phoneAttribute)
}
if p.phoneVerifiedAttribute != "" {
attributes = append(attributes, p.phoneVerifiedAttribute)
}
if p.preferredLanguageAttribute != "" {
attributes = append(attributes, p.preferredLanguageAttribute)
}
if p.avatarURLAttribute != "" {
attributes = append(attributes, p.avatarURLAttribute)
}
if p.profileAttribute != "" {
attributes = append(attributes, p.profileAttribute)
}
return attributes
}

View File

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

View File

@ -4,8 +4,10 @@ import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/url"
"strconv"
"time"
"github.com/go-ldap/ldap/v3"
"golang.org/x/text/language"
@ -15,49 +17,154 @@ import (
)
var ErrNoSingleUser = errors.New("user does not exist or too many entries returned")
var ErrFailedLogin = errors.New("user failed to login")
var _ idp.Session = (*Session)(nil)
type Session struct {
Provider *Provider
loginUrl string
user string
password string
User string
Password string
}
func (s *Session) GetAuthURL() string {
return s.loginUrl
}
func (s *Session) FetchUser(_ context.Context) (idp.User, error) {
l, err := ldap.DialURL("ldap://" + s.Provider.host + ":" + s.Provider.port)
func (s *Session) FetchUser(_ context.Context) (_ idp.User, err error) {
var user *ldap.Entry
for _, server := range s.Provider.servers {
user, err = tryBind(server,
s.Provider.startTLS,
s.Provider.bindDN,
s.Provider.bindPassword,
s.Provider.baseDN,
s.Provider.getNecessaryAttributes(),
s.Provider.userObjectClasses,
s.Provider.userFilters,
s.User,
s.Password, s.Provider.timeout)
// If there were invalid credentials or multiple users with the credentials cancel process
if err != nil && (errors.Is(err, ErrFailedLogin) || errors.Is(err, ErrNoSingleUser)) {
return nil, err
}
// If a user bind was successful and user is filled continue with login, otherwise try next server
if err == nil && user != nil {
break
}
}
if err != nil {
return nil, err
}
defer l.Close()
if s.Provider.tls {
err = l.StartTLS(&tls.Config{ServerName: s.Provider.host})
return mapLDAPEntryToUser(
user,
s.Provider.idAttribute,
s.Provider.firstNameAttribute,
s.Provider.lastNameAttribute,
s.Provider.displayNameAttribute,
s.Provider.nickNameAttribute,
s.Provider.preferredUsernameAttribute,
s.Provider.emailAttribute,
s.Provider.emailVerifiedAttribute,
s.Provider.phoneAttribute,
s.Provider.phoneVerifiedAttribute,
s.Provider.preferredLanguageAttribute,
s.Provider.avatarURLAttribute,
s.Provider.profileAttribute,
)
}
func tryBind(
server string,
startTLS bool,
bindDN string,
bindPassword string,
baseDN string,
attributes []string,
objectClasses []string,
userFilters []string,
username string,
password string,
timeout time.Duration,
) (*ldap.Entry, error) {
conn, err := getConnection(server, startTLS, timeout)
if err != nil {
return nil, err
}
defer conn.Close()
if err := conn.Bind(bindDN, bindPassword); err != nil {
return nil, err
}
return trySearchAndUserBind(
conn,
baseDN,
attributes,
objectClasses,
userFilters,
username,
password,
timeout,
)
}
func getConnection(
server string,
startTLS bool,
timeout time.Duration,
) (*ldap.Conn, error) {
if timeout == 0 {
timeout = ldap.DefaultTimeout
}
conn, err := ldap.DialURL(server, ldap.DialWithDialer(&net.Dialer{Timeout: timeout}))
if err != nil {
return nil, err
}
u, err := url.Parse(server)
if err != nil {
return nil, err
}
if u.Scheme == "ldaps" && startTLS {
err = conn.StartTLS(&tls.Config{ServerName: u.Host})
if err != nil {
return nil, err
}
}
return conn, nil
}
// Bind as the admin to search for user
err = l.Bind("cn="+s.Provider.admin+","+s.Provider.baseDN, s.Provider.password)
if err != nil {
return nil, err
}
func trySearchAndUserBind(
conn *ldap.Conn,
baseDN string,
attributes []string,
objectClasses []string,
userFilters []string,
username string,
password string,
timeout time.Duration,
) (*ldap.Entry, error) {
searchQuery := queriesAndToSearchQuery(
objectClassesToSearchQuery(objectClasses),
queriesOrToSearchQuery(
userFiltersToSearchQuery(userFilters, username),
),
)
// Search for user with the unique attribute for the userDN
searchRequest := ldap.NewSearchRequest(
s.Provider.baseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass="+s.Provider.userObjectClass+")("+s.Provider.userUniqueAttribute+"=%s))", ldap.EscapeFilter(s.user)),
[]string{"dn"},
baseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, int(timeout.Seconds()), false,
searchQuery,
attributes,
nil,
)
sr, err := l.Search(searchRequest)
sr, err := conn.Search(searchRequest)
if err != nil {
return nil, err
}
@ -67,33 +174,100 @@ func (s *Session) FetchUser(_ context.Context) (idp.User, error) {
user := sr.Entries[0]
// Bind as the user to verify their password
err = l.Bind(user.DN, s.password)
if err != nil {
return nil, err
if err = conn.Bind(user.DN, password); err != nil {
return nil, ErrFailedLogin
}
return user, nil
}
emailVerified, err := strconv.ParseBool(user.GetAttributeValue(s.Provider.emailVerifiedAttribute))
if err != nil {
return nil, err
func queriesAndToSearchQuery(queries ...string) string {
if len(queries) == 0 {
return ""
}
phoneVerified, err := strconv.ParseBool(user.GetAttributeValue(s.Provider.phoneVerifiedAttribute))
if err != nil {
return nil, err
if len(queries) == 1 {
return queries[0]
}
joinQueries := "(&"
for _, s := range queries {
joinQueries += s
}
return joinQueries + ")"
}
func queriesOrToSearchQuery(queries ...string) string {
if len(queries) == 0 {
return ""
}
if len(queries) == 1 {
return queries[0]
}
joinQueries := "(|"
for _, s := range queries {
joinQueries += s
}
return joinQueries + ")"
}
func objectClassesToSearchQuery(classes []string) string {
searchQuery := ""
for _, class := range classes {
searchQuery += "(objectClass=" + class + ")"
}
return searchQuery
}
func userFiltersToSearchQuery(filters []string, username string) string {
searchQuery := ""
for _, filter := range filters {
searchQuery += "(" + filter + "=" + ldap.EscapeFilter(username) + ")"
}
return searchQuery
}
func mapLDAPEntryToUser(
user *ldap.Entry,
idAttribute,
firstNameAttribute,
lastNameAttribute,
displayNameAttribute,
nickNameAttribute,
preferredUsernameAttribute,
emailAttribute,
emailVerifiedAttribute,
phoneAttribute,
phoneVerifiedAttribute,
preferredLanguageAttribute,
avatarURLAttribute,
profileAttribute string,
) (_ *User, err error) {
var emailVerified bool
if v := user.GetAttributeValue(emailVerifiedAttribute); v != "" {
emailVerified, err = strconv.ParseBool(v)
if err != nil {
return nil, err
}
}
var phoneVerified bool
if v := user.GetAttributeValue(phoneVerifiedAttribute); v != "" {
phoneVerified, err = strconv.ParseBool(v)
if err != nil {
return nil, err
}
}
return NewUser(
user.GetAttributeValue(s.Provider.idAttribute),
user.GetAttributeValue(s.Provider.firstNameAttribute),
user.GetAttributeValue(s.Provider.lastNameAttribute),
user.GetAttributeValue(s.Provider.displayNameAttribute),
user.GetAttributeValue(s.Provider.nickNameAttribute),
user.GetAttributeValue(s.Provider.preferredUsernameAttribute),
domain.EmailAddress(user.GetAttributeValue(s.Provider.emailAttribute)),
user.GetAttributeValue(idAttribute),
user.GetAttributeValue(firstNameAttribute),
user.GetAttributeValue(lastNameAttribute),
user.GetAttributeValue(displayNameAttribute),
user.GetAttributeValue(nickNameAttribute),
user.GetAttributeValue(preferredUsernameAttribute),
domain.EmailAddress(user.GetAttributeValue(emailAttribute)),
emailVerified,
domain.PhoneNumber(user.GetAttributeValue(s.Provider.phoneAttribute)),
domain.PhoneNumber(user.GetAttributeValue(phoneAttribute)),
phoneVerified,
language.Make(user.GetAttributeValue(s.Provider.preferredLanguageAttribute)),
user.GetAttributeValue(s.Provider.avatarURLAttribute),
user.GetAttributeValue(s.Provider.profileAttribute),
language.Make(user.GetAttributeValue(preferredLanguageAttribute)),
user.GetAttributeValue(avatarURLAttribute),
user.GetAttributeValue(profileAttribute),
), nil
}

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package projection
import (
"testing"
"time"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
@ -2033,18 +2034,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
[]byte(`{
"id": "idp-id",
"name": "custom-zitadel-instance",
"host": "host",
"port": "port",
"tls": true,
"baseDN": "base",
"userObjectClass": "user",
"userUniqueAttribute": "uid",
"admin": "admin",
"password": {
"servers": ["server"],
"startTls": false,
"baseDN": "basedn",
"bindDN": "binddn",
"bindPassword": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
"userBase": "user",
"userObjectClasses": ["object"],
"userFilters": ["filter"],
"timeout": 30000000000,
"idAttribute": "id",
"firstNameAttribute": "first",
"lastNameAttribute": "last",
@ -2092,18 +2094,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
},
},
{
expectedStmt: "INSERT INTO projections.idp_templates4_ldap (idp_id, instance_id, host, port, tls, base_dn, user_object_class, user_unique_attribute, admin, password, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23)",
expectedStmt: "INSERT INTO projections.idp_templates4_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)",
expectedArgs: []interface{}{
"idp-id",
"instance-id",
"host",
"port",
true,
"base",
"user",
"uid",
"admin",
database.StringArray{"server"},
false,
"basedn",
"binddn",
anyArg{},
"user",
database.StringArray{"object"},
database.StringArray{"filter"},
time.Duration(30000000000),
"id",
"first",
"last",
@ -2132,18 +2135,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
[]byte(`{
"id": "idp-id",
"name": "custom-zitadel-instance",
"host": "host",
"port": "port",
"tls": true,
"baseDN": "base",
"userObjectClass": "user",
"userUniqueAttribute": "uid",
"admin": "admin",
"password": {
"servers": ["server"],
"startTls": false,
"baseDN": "basedn",
"bindDN": "binddn",
"bindPassword": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
"userBase": "user",
"userObjectClasses": ["object"],
"userFilters": ["filter"],
"timeout": 30000000000,
"idAttribute": "id",
"firstNameAttribute": "first",
"lastNameAttribute": "last",
@ -2191,18 +2195,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
},
},
{
expectedStmt: "INSERT INTO projections.idp_templates4_ldap (idp_id, instance_id, host, port, tls, base_dn, user_object_class, user_unique_attribute, admin, password, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23)",
expectedStmt: "INSERT INTO projections.idp_templates4_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)",
expectedArgs: []interface{}{
"idp-id",
"instance-id",
"host",
"port",
true,
"base",
"user",
"uid",
"admin",
database.StringArray{"server"},
false,
"basedn",
"binddn",
anyArg{},
"user",
database.StringArray{"object"},
database.StringArray{"filter"},
time.Duration(30000000000),
"id",
"first",
"last",
@ -2231,7 +2236,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
[]byte(`{
"id": "idp-id",
"name": "custom-zitadel-instance",
"host": "host"
"baseDN": "basedn"
}`),
), instance.LDAPIDPChangedEventMapper),
},
@ -2253,9 +2258,9 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.idp_templates4_ldap SET host = $1 WHERE (idp_id = $2) AND (instance_id = $3)",
expectedStmt: "UPDATE projections.idp_templates4_ldap2 SET base_dn = $1 WHERE (idp_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"host",
"basedn",
"idp-id",
"instance-id",
},
@ -2273,18 +2278,19 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
[]byte(`{
"id": "idp-id",
"name": "custom-zitadel-instance",
"host": "host",
"port": "port",
"tls": true,
"baseDN": "base",
"userObjectClass": "user",
"userUniqueAttribute": "uid",
"admin": "admin",
"password": {
"servers": ["server"],
"startTls": false,
"baseDN": "basedn",
"bindDN": "binddn",
"bindPassword": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
"userBase": "user",
"userObjectClasses": ["object"],
"userFilters": ["filter"],
"timeout": 30000000000,
"idAttribute": "id",
"firstNameAttribute": "first",
"lastNameAttribute": "last",
@ -2327,16 +2333,17 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.idp_templates4_ldap SET (host, port, tls, base_dn, user_object_class, user_unique_attribute, admin, password, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) WHERE (idp_id = $22) AND (instance_id = $23)",
expectedStmt: "UPDATE projections.idp_templates4_ldap2 SET (servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) WHERE (idp_id = $23) AND (instance_id = $24)",
expectedArgs: []interface{}{
"host",
"port",
true,
"base",
"user",
"uid",
"admin",
database.StringArray{"server"},
false,
"basedn",
"binddn",
anyArg{},
"user",
database.StringArray{"object"},
database.StringArray{"filter"},
time.Duration(30000000000),
"id",
"first",
"last",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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