Files
zitadel/internal/idp/providers/ldap/ldap.go
T
4936bdf2c9 feat(rt): IDP Intents Relational Table (#11374)
# Which Problems Are Solved

In the context of moving our datastore to relational tables, Identity
Provider Intents have no definition of a relational table nor any DB API
that can be used to store the IDP intents as relational objects.

# How the Problems Are Solved

The PR follows the guidelines left in #11190

- A table `zitadel.identity_provider_intents` is created: it defines its
relational constraints (foreign keys) with the other tables, as well as
an ENUM to represent the state of the intent.
- An IDP Intent repository has been created, which offers CRUD
operations implemented as `Create`, `Get`, `Update` and `Delete` methods
for the IDP intent.
- The repository also defines all columns and conditions useful for
performing DB operations on the IDP intents.
- DB mocks have been added as well to ease testing on the `domain`
package.
- Reduction functions have been implemented for the existing IDP events
so to keep the relational table in sync with the event store.

# Additional Changes

To allow testing LDAP intent flow, the `Session.FetchUser()` method has
been extracted to a separate file. A corresponding mock has been created
as well in `fetch_user_mock.go`. The files are mutually excluded and
added at build time through go build tags: the mock is added when the
`integration` tag is specified, while the real implementation is added
when the same tag IS NOT added to the build tags.

# Additional Context

- Closes #11190

---------

Co-authored-by: Livio Spring <[email protected]>
Co-authored-by: Silvan <[email protected]>
2026-02-05 03:44:07 +00:00

292 lines
7.4 KiB
Go

package ldap
import (
"context"
"time"
"github.com/zitadel/zitadel/internal/idp"
)
const DefaultPort = "389"
var _ idp.Provider = (*Provider)(nil)
// Provider is the [idp.Provider] implementation for a generic LDAP provider
type Provider struct {
name string
servers []string
startTLS bool
baseDN string
bindDN string
bindPassword string
userBase string
userObjectClasses []string
userFilters []string
timeout time.Duration
rootCA []byte
loginUrl string
isLinkingAllowed bool
isCreationAllowed bool
isAutoCreation bool
isAutoUpdate bool
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 ProviderOpts func(provider *Provider)
// WithLinkingAllowed allows end users to link the federated user to an existing one.
func WithLinkingAllowed() ProviderOpts {
return func(p *Provider) {
p.isLinkingAllowed = true
}
}
// WithCreationAllowed allows end users to create a new user using the federated information.
func WithCreationAllowed() ProviderOpts {
return func(p *Provider) {
p.isCreationAllowed = true
}
}
// WithAutoCreation enables that federated users are automatically created if not already existing.
func WithAutoCreation() ProviderOpts {
return func(p *Provider) {
p.isAutoCreation = true
}
}
// WithAutoUpdate enables that information retrieved from the provider is automatically used to update
// the existing user on each authentication.
func WithAutoUpdate() ProviderOpts {
return func(p *Provider) {
p.isAutoUpdate = true
}
}
// WithoutStartTLS configures to communication insecure with the LDAP server without startTLS
func WithoutStartTLS() ProviderOpts {
return func(p *Provider) {
p.startTLS = false
}
}
// WithCustomIDAttribute configures to map the LDAP attribute to the user, default is the uniqueUserAttribute
func WithCustomIDAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.idAttribute = name
}
}
// WithFirstNameAttribute configures to map the LDAP attribute to the user
func WithFirstNameAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.firstNameAttribute = name
}
}
// WithLastNameAttribute configures to map the LDAP attribute to the user
func WithLastNameAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.lastNameAttribute = name
}
}
// WithDisplayNameAttribute configures to map the LDAP attribute to the user
func WithDisplayNameAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.displayNameAttribute = name
}
}
// WithNickNameAttribute configures to map the LDAP attribute to the user
func WithNickNameAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.nickNameAttribute = name
}
}
// WithPreferredUsernameAttribute configures to map the LDAP attribute to the user
func WithPreferredUsernameAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.preferredUsernameAttribute = name
}
}
// WithEmailAttribute configures to map the LDAP attribute to the user
func WithEmailAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.emailAttribute = name
}
}
// WithEmailVerifiedAttribute configures to map the LDAP attribute to the user
func WithEmailVerifiedAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.emailVerifiedAttribute = name
}
}
// WithPhoneAttribute configures to map the LDAP attribute to the user
func WithPhoneAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.phoneAttribute = name
}
}
// WithPhoneVerifiedAttribute configures to map the LDAP attribute to the user
func WithPhoneVerifiedAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.phoneVerifiedAttribute = name
}
}
// WithPreferredLanguageAttribute configures to map the LDAP attribute to the user
func WithPreferredLanguageAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.preferredLanguageAttribute = name
}
}
// WithAvatarURLAttribute configures to map the LDAP attribute to the user
func WithAvatarURLAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.avatarURLAttribute = name
}
}
// WithProfileAttribute configures to map the LDAP attribute to the user
func WithProfileAttribute(name string) ProviderOpts {
return func(p *Provider) {
p.profileAttribute = name
}
}
func New(
name string,
servers []string,
baseDN string,
bindDN string,
bindPassword string,
userBase string,
userObjectClasses []string,
userFilters []string,
timeout time.Duration,
rootCA []byte,
loginUrl string,
options ...ProviderOpts,
) *Provider {
provider := &Provider{
name: name,
servers: servers,
startTLS: true,
baseDN: baseDN,
bindDN: bindDN,
bindPassword: bindPassword,
userBase: userBase,
userObjectClasses: userObjectClasses,
userFilters: userFilters,
timeout: timeout,
rootCA: rootCA,
loginUrl: loginUrl,
}
for _, option := range options {
option(provider)
}
return provider
}
func (p *Provider) Name() string {
return p.name
}
func (p *Provider) BeginAuth(ctx context.Context, state string, _ ...idp.Parameter) (idp.Session, error) {
return &Session{
Provider: p,
loginUrl: p.loginUrl + state,
}, nil
}
func (p *Provider) GetSession(username, password string) *Session {
return &Session{
Provider: p,
User: username,
Password: password,
}
}
func (p *Provider) IsLinkingAllowed() bool {
return p.isLinkingAllowed
}
func (p *Provider) IsCreationAllowed() bool {
return p.isCreationAllowed
}
func (p *Provider) IsAutoCreation() bool {
return p.isAutoCreation
}
func (p *Provider) IsAutoUpdate() bool {
return p.isAutoUpdate
}
// Do not remove, used in [Session.FetchUser] @ fetch_user.go
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
}