mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-14 00:17:34 +00:00
chore: move the go code into a subfolder
This commit is contained in:
290
apps/api/internal/idp/providers/ldap/ldap.go
Normal file
290
apps/api/internal/idp/providers/ldap/ldap.go
Normal file
@@ -0,0 +1,290 @@
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
207
apps/api/internal/idp/providers/ldap/ldap_test.go
Normal file
207
apps/api/internal/idp/providers/ldap/ldap_test.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProvider_Options(t *testing.T) {
|
||||
type fields struct {
|
||||
name string
|
||||
servers []string
|
||||
baseDN string
|
||||
bindDN string
|
||||
bindPassword string
|
||||
userBase string
|
||||
userObjectClasses []string
|
||||
userFilters []string
|
||||
timeout time.Duration
|
||||
rootCA []byte
|
||||
loginUrl string
|
||||
opts []ProviderOpts
|
||||
}
|
||||
type want struct {
|
||||
name string
|
||||
rootCA []byte
|
||||
startTls bool
|
||||
linkingAllowed bool
|
||||
creationAllowed bool
|
||||
autoCreation bool
|
||||
autoUpdate 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
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
fields: fields{
|
||||
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",
|
||||
startTls: true,
|
||||
linkingAllowed: false,
|
||||
creationAllowed: false,
|
||||
autoCreation: false,
|
||||
autoUpdate: false,
|
||||
idAttribute: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all true",
|
||||
fields: fields{
|
||||
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(),
|
||||
WithAutoUpdate(),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
name: "ldap",
|
||||
startTls: false,
|
||||
linkingAllowed: true,
|
||||
creationAllowed: true,
|
||||
autoCreation: true,
|
||||
autoUpdate: true,
|
||||
idAttribute: "",
|
||||
},
|
||||
}, {
|
||||
name: "all true, attributes set",
|
||||
fields: fields{
|
||||
name: "ldap",
|
||||
servers: []string{"server"},
|
||||
baseDN: "base",
|
||||
bindDN: "binddn",
|
||||
bindPassword: "password",
|
||||
userBase: "user",
|
||||
userObjectClasses: []string{"object"},
|
||||
userFilters: []string{"filter"},
|
||||
timeout: 30 * time.Second,
|
||||
rootCA: []byte("certificate"),
|
||||
loginUrl: "url",
|
||||
opts: []ProviderOpts{
|
||||
WithoutStartTLS(),
|
||||
WithLinkingAllowed(),
|
||||
WithCreationAllowed(),
|
||||
WithAutoCreation(),
|
||||
WithAutoUpdate(),
|
||||
WithCustomIDAttribute("id"),
|
||||
WithFirstNameAttribute("first"),
|
||||
WithLastNameAttribute("last"),
|
||||
WithDisplayNameAttribute("display"),
|
||||
WithNickNameAttribute("nick"),
|
||||
WithPreferredUsernameAttribute("prefUser"),
|
||||
WithEmailAttribute("email"),
|
||||
WithEmailVerifiedAttribute("emailVerified"),
|
||||
WithPhoneAttribute("phone"),
|
||||
WithPhoneVerifiedAttribute("phoneVerified"),
|
||||
WithPreferredLanguageAttribute("prefLang"),
|
||||
WithAvatarURLAttribute("avatar"),
|
||||
WithProfileAttribute("profile"),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
name: "ldap",
|
||||
rootCA: []byte("certificate"),
|
||||
startTls: false,
|
||||
linkingAllowed: true,
|
||||
creationAllowed: true,
|
||||
autoCreation: true,
|
||||
autoUpdate: true,
|
||||
idAttribute: "id",
|
||||
firstNameAttribute: "first",
|
||||
lastNameAttribute: "last",
|
||||
displayNameAttribute: "display",
|
||||
nickNameAttribute: "nick",
|
||||
preferredUsernameAttribute: "prefUser",
|
||||
emailAttribute: "email",
|
||||
emailVerifiedAttribute: "emailVerified",
|
||||
phoneAttribute: "phone",
|
||||
phoneVerifiedAttribute: "phoneVerified",
|
||||
preferredLanguageAttribute: "prefLang",
|
||||
avatarURLAttribute: "avatar",
|
||||
profileAttribute: "profile",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
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.rootCA,
|
||||
tt.fields.loginUrl,
|
||||
tt.fields.opts...,
|
||||
)
|
||||
|
||||
a.Equal(tt.want.name, provider.Name())
|
||||
a.Equal(tt.want.rootCA, provider.rootCA)
|
||||
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())
|
||||
a.Equal(tt.want.autoUpdate, provider.IsAutoUpdate())
|
||||
|
||||
a.Equal(tt.want.idAttribute, provider.idAttribute)
|
||||
a.Equal(tt.want.firstNameAttribute, provider.firstNameAttribute)
|
||||
a.Equal(tt.want.lastNameAttribute, provider.lastNameAttribute)
|
||||
a.Equal(tt.want.displayNameAttribute, provider.displayNameAttribute)
|
||||
a.Equal(tt.want.nickNameAttribute, provider.nickNameAttribute)
|
||||
a.Equal(tt.want.preferredUsernameAttribute, provider.preferredUsernameAttribute)
|
||||
a.Equal(tt.want.emailAttribute, provider.emailAttribute)
|
||||
a.Equal(tt.want.emailVerifiedAttribute, provider.emailVerifiedAttribute)
|
||||
a.Equal(tt.want.phoneAttribute, provider.phoneAttribute)
|
||||
a.Equal(tt.want.phoneVerifiedAttribute, provider.phoneVerifiedAttribute)
|
||||
a.Equal(tt.want.preferredLanguageAttribute, provider.preferredLanguageAttribute)
|
||||
a.Equal(tt.want.avatarURLAttribute, provider.avatarURLAttribute)
|
||||
a.Equal(tt.want.profileAttribute, provider.profileAttribute)
|
||||
})
|
||||
}
|
||||
}
|
333
apps/api/internal/idp/providers/ldap/session.go
Normal file
333
apps/api/internal/idp/providers/ldap/session.go
Normal file
@@ -0,0 +1,333 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/zitadel/logging"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
)
|
||||
|
||||
var ErrNoSingleUser = errors.New("user does not exist or too many entries returned")
|
||||
var ErrFailedLogin = errors.New("user failed to login")
|
||||
var ErrUnableToAppendRootCA = errors.New("unable to append rootCA")
|
||||
|
||||
var _ idp.Session = (*Session)(nil)
|
||||
|
||||
type Session struct {
|
||||
Provider *Provider
|
||||
loginUrl string
|
||||
User string
|
||||
Password string
|
||||
Entry *ldap.Entry
|
||||
}
|
||||
|
||||
func NewSession(provider *Provider, username, password string) *Session {
|
||||
return &Session{Provider: provider, User: username, Password: password}
|
||||
}
|
||||
|
||||
// GetAuth implements the [idp.Session] interface.
|
||||
func (s *Session) GetAuth(ctx context.Context) (idp.Auth, error) {
|
||||
return idp.Redirect(s.loginUrl)
|
||||
}
|
||||
|
||||
// PersistentParameters implements the [idp.Session] interface.
|
||||
func (s *Session) PersistentParameters() map[string]any {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchUser implements the [idp.Session] interface.
|
||||
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,
|
||||
s.Provider.rootCA)
|
||||
// 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
|
||||
}
|
||||
s.Entry = user
|
||||
|
||||
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 (s *Session) ExpiresAt() time.Time {
|
||||
return time.Time{} // falls back to the default expiration time
|
||||
}
|
||||
|
||||
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,
|
||||
rootCA []byte,
|
||||
) (*ldap.Entry, error) {
|
||||
conn, err := getConnection(server, startTLS, timeout, rootCA)
|
||||
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,
|
||||
rootCA []byte,
|
||||
) (*ldap.Conn, error) {
|
||||
if timeout == 0 {
|
||||
timeout = ldap.DefaultTimeout
|
||||
}
|
||||
|
||||
dialer := make([]ldap.DialOpt, 1, 2)
|
||||
dialer[0] = ldap.DialWithDialer(&net.Dialer{Timeout: timeout})
|
||||
|
||||
u, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.Scheme == "ldaps" && len(rootCA) > 0 {
|
||||
rootCAs := x509.NewCertPool()
|
||||
if ok := rootCAs.AppendCertsFromPEM(rootCA); !ok {
|
||||
return nil, ErrUnableToAppendRootCA
|
||||
}
|
||||
|
||||
dialer = append(dialer, ldap.DialWithTLSConfig(&tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
}))
|
||||
}
|
||||
|
||||
conn, err := ldap.DialURL(server, dialer...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.Scheme == "ldap" && startTLS {
|
||||
err = conn.StartTLS(&tls.Config{ServerName: u.Host})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
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(
|
||||
baseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, int(timeout.Seconds()), false,
|
||||
searchQuery,
|
||||
attributes,
|
||||
nil,
|
||||
)
|
||||
|
||||
sr, err := conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(sr.Entries) != 1 {
|
||||
logging.WithFields("entries", len(sr.Entries)).Info("ldap: no single user found")
|
||||
return nil, ErrNoSingleUser
|
||||
}
|
||||
|
||||
user := sr.Entries[0]
|
||||
// Bind as the user to verify their password
|
||||
userDN, err := ldap.ParseDN(user.DN)
|
||||
if err != nil {
|
||||
logging.WithFields("userDN", user.DN).WithError(err).Info("ldap user parse DN failed")
|
||||
return nil, err
|
||||
}
|
||||
if err = conn.Bind(userDN.String(), password); err != nil {
|
||||
logging.WithFields("userDN", user.DN).WithError(err).Info("ldap user bind failed")
|
||||
return nil, ErrFailedLogin
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func queriesAndToSearchQuery(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 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 {
|
||||
searchQueries := make([]string, len(filters))
|
||||
for i, filter := range filters {
|
||||
searchQueries[i] = "(" + filter + "=" + username + ")"
|
||||
}
|
||||
return searchQueries
|
||||
}
|
||||
|
||||
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(
|
||||
getAttributeValue(user, idAttribute),
|
||||
getAttributeValue(user, firstNameAttribute),
|
||||
getAttributeValue(user, lastNameAttribute),
|
||||
getAttributeValue(user, displayNameAttribute),
|
||||
getAttributeValue(user, nickNameAttribute),
|
||||
getAttributeValue(user, preferredUsernameAttribute),
|
||||
domain.EmailAddress(user.GetAttributeValue(emailAttribute)),
|
||||
emailVerified,
|
||||
domain.PhoneNumber(user.GetAttributeValue(phoneAttribute)),
|
||||
phoneVerified,
|
||||
language.Make(user.GetAttributeValue(preferredLanguageAttribute)),
|
||||
user.GetAttributeValue(avatarURLAttribute),
|
||||
user.GetAttributeValue(profileAttribute),
|
||||
), nil
|
||||
}
|
||||
|
||||
func getAttributeValue(user *ldap.Entry, attribute string) string {
|
||||
// return an empty string if no attribute is needed
|
||||
if attribute == "" {
|
||||
return ""
|
||||
}
|
||||
value := user.GetAttributeValue(attribute)
|
||||
if utf8.ValidString(value) {
|
||||
return value
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(user.GetRawAttributeValue(attribute))
|
||||
}
|
400
apps/api/internal/idp/providers/ldap/session_test.go
Normal file
400
apps/api/internal/idp/providers/ldap/session_test.go
Normal 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: []string{},
|
||||
},
|
||||
{
|
||||
name: "one",
|
||||
fields: []string{"test"},
|
||||
username: "user",
|
||||
want: []string{"(test=user)"},
|
||||
},
|
||||
{
|
||||
name: "three",
|
||||
fields: []string{"test1", "test2", "test3"},
|
||||
username: "user",
|
||||
want: []string{"(test1=user)", "(test2=user)", "(test3=user)"},
|
||||
},
|
||||
{
|
||||
name: "five",
|
||||
fields: []string{"test1", "test2", "test3", "test4", "test5"},
|
||||
username: "user",
|
||||
want: []string{"(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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
95
apps/api/internal/idp/providers/ldap/user.go
Normal file
95
apps/api/internal/idp/providers/ldap/user.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
FirstName string `json:"firstName,omitempty"`
|
||||
LastName string `json:"lastName,omitempty"`
|
||||
DisplayName string `json:"displayName,omitempty"`
|
||||
NickName string `json:"nickName,omitempty"`
|
||||
PreferredUsername string `json:"preferredUsername,omitempty"`
|
||||
Email domain.EmailAddress `json:"email,omitempty"`
|
||||
EmailVerified bool `json:"emailVerified,omitempty"`
|
||||
Phone domain.PhoneNumber `json:"phone,omitempty"`
|
||||
PhoneVerified bool `json:"phoneVerified,omitempty"`
|
||||
PreferredLanguage language.Tag `json:"preferredLanguage,omitempty"`
|
||||
AvatarURL string `json:"avatarURL,omitempty"`
|
||||
Profile string `json:"profile,omitempty"`
|
||||
}
|
||||
|
||||
func NewUser(
|
||||
id string,
|
||||
firstName string,
|
||||
lastName string,
|
||||
displayName string,
|
||||
nickName string,
|
||||
preferredUsername string,
|
||||
email domain.EmailAddress,
|
||||
emailVerified bool,
|
||||
phone domain.PhoneNumber,
|
||||
phoneVerified bool,
|
||||
preferredLanguage language.Tag,
|
||||
avatarURL string,
|
||||
profile string,
|
||||
) *User {
|
||||
return &User{
|
||||
id,
|
||||
firstName,
|
||||
lastName,
|
||||
displayName,
|
||||
nickName,
|
||||
preferredUsername,
|
||||
email,
|
||||
emailVerified,
|
||||
phone,
|
||||
phoneVerified,
|
||||
preferredLanguage,
|
||||
avatarURL,
|
||||
profile,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) GetID() string {
|
||||
return u.ID
|
||||
}
|
||||
func (u *User) GetFirstName() string {
|
||||
return u.FirstName
|
||||
}
|
||||
func (u *User) GetLastName() string {
|
||||
return u.LastName
|
||||
}
|
||||
func (u *User) GetDisplayName() string {
|
||||
return u.DisplayName
|
||||
}
|
||||
func (u *User) GetNickname() string {
|
||||
return u.NickName
|
||||
}
|
||||
func (u *User) GetPreferredUsername() string {
|
||||
return u.PreferredUsername
|
||||
}
|
||||
func (u *User) GetEmail() domain.EmailAddress {
|
||||
return u.Email
|
||||
}
|
||||
func (u *User) IsEmailVerified() bool {
|
||||
return u.EmailVerified
|
||||
}
|
||||
func (u *User) GetPhone() domain.PhoneNumber {
|
||||
return u.Phone
|
||||
}
|
||||
func (u *User) IsPhoneVerified() bool {
|
||||
return u.PhoneVerified
|
||||
}
|
||||
func (u *User) GetPreferredLanguage() language.Tag {
|
||||
return u.PreferredLanguage
|
||||
}
|
||||
func (u *User) GetAvatarURL() string {
|
||||
return u.AvatarURL
|
||||
}
|
||||
func (u *User) GetProfile() string {
|
||||
return u.Profile
|
||||
}
|
Reference in New Issue
Block a user