github.com/greenpau/go-authcrunch@v1.1.4/pkg/ids/ldap/authenticator.go (about)

     1  // Copyright 2022 Paul Greenberg greenpau@outlook.com
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ldap
    16  
    17  import (
    18  	"crypto/tls"
    19  	"crypto/x509"
    20  	"fmt"
    21  	ldap "github.com/go-ldap/ldap/v3"
    22  	"github.com/greenpau/go-authcrunch/pkg/errors"
    23  	"github.com/greenpau/go-authcrunch/pkg/requests"
    24  	"go.uber.org/zap"
    25  	"io/ioutil"
    26  	"net"
    27  	"net/url"
    28  	"os"
    29  	"strings"
    30  	"sync"
    31  	"time"
    32  )
    33  
    34  // Authenticator represents database connector.
    35  type Authenticator struct {
    36  	mux               sync.Mutex
    37  	realm             string
    38  	servers           []*AuthServer
    39  	username          string
    40  	password          string
    41  	searchBaseDN      string
    42  	searchUserFilter  string
    43  	searchGroupFilter string
    44  	userAttributes    UserAttributes
    45  	fallbackRoles     []string
    46  	rootCAs           *x509.CertPool
    47  	groups            []*UserGroup
    48  	logger            *zap.Logger
    49  }
    50  
    51  // NewAuthenticator returns an instance of Authenticator.
    52  func NewAuthenticator() *Authenticator {
    53  	return &Authenticator{
    54  		servers: []*AuthServer{},
    55  		groups:  []*UserGroup{},
    56  	}
    57  }
    58  
    59  // ConfigureRealm configures a domain name (realm) associated with
    60  // the instance of authenticator.
    61  func (sa *Authenticator) ConfigureRealm(cfg *Config) error {
    62  	sa.mux.Lock()
    63  	defer sa.mux.Unlock()
    64  	if cfg.Realm == "" {
    65  		return fmt.Errorf("no realm found")
    66  	}
    67  	sa.realm = cfg.Realm
    68  	sa.logger.Info(
    69  		"LDAP plugin configuration",
    70  		zap.String("phase", "realm"),
    71  		zap.String("realm", cfg.Realm),
    72  	)
    73  	return nil
    74  }
    75  
    76  // ConfigureServers configures the addresses of LDAP servers.
    77  func (sa *Authenticator) ConfigureServers(cfg *Config) error {
    78  	sa.mux.Lock()
    79  	defer sa.mux.Unlock()
    80  	if len(cfg.Servers) == 0 {
    81  		return fmt.Errorf("no authentication servers found")
    82  	}
    83  	for _, entry := range cfg.Servers {
    84  		if !strings.HasPrefix(entry.Address, "ldaps://") && !strings.HasPrefix(entry.Address, "ldap://") {
    85  			return fmt.Errorf("the server address does not have neither ldaps:// nor ldap:// prefix, address: %s", entry.Address)
    86  		}
    87  		if entry.Timeout == 0 {
    88  			entry.Timeout = 5
    89  		}
    90  		if entry.Timeout > 10 {
    91  			return fmt.Errorf("invalid timeout value: %d, cannot exceed 10 seconds", entry.Timeout)
    92  		}
    93  
    94  		server := &AuthServer{
    95  			Address:          entry.Address,
    96  			IgnoreCertErrors: entry.IgnoreCertErrors,
    97  			Timeout:          entry.Timeout,
    98  			PosixGroups:      entry.PosixGroups,
    99  		}
   100  
   101  		url, err := url.Parse(entry.Address)
   102  		if err != nil {
   103  			return fmt.Errorf("failed parsing LDAP server address: %s, %s", entry.Address, err)
   104  		}
   105  		server.URL = url
   106  
   107  		switch {
   108  		case strings.HasPrefix(entry.Address, "ldaps://"):
   109  			server.Port = "636"
   110  			server.Encrypted = true
   111  		case strings.HasPrefix(entry.Address, "ldap://"):
   112  			server.Port = "389"
   113  		}
   114  
   115  		if server.URL.Port() != "" {
   116  			server.Port = server.URL.Port()
   117  		}
   118  
   119  		sa.logger.Info(
   120  			"LDAP plugin configuration",
   121  			zap.String("phase", "servers"),
   122  			zap.String("address", server.Address),
   123  			zap.String("url", server.URL.String()),
   124  			zap.String("port", server.Port),
   125  			zap.Bool("ignore_cert_errors", server.IgnoreCertErrors),
   126  			zap.Bool("posix_groups", server.PosixGroups),
   127  			zap.Int("timeout", server.Timeout),
   128  		)
   129  		sa.servers = append(sa.servers, server)
   130  	}
   131  	return nil
   132  }
   133  
   134  // ConfigureBindCredentials configures user credentials for LDAP binding.
   135  func (sa *Authenticator) ConfigureBindCredentials(cfg *Config) error {
   136  	username := cfg.BindUsername
   137  	password := cfg.BindPassword
   138  	sa.mux.Lock()
   139  	defer sa.mux.Unlock()
   140  	if username == "" {
   141  		return fmt.Errorf("no username found")
   142  	}
   143  	if password == "" {
   144  		password = os.Getenv("LDAP_USER_SECRET")
   145  		if password == "" {
   146  			return fmt.Errorf("no password found")
   147  		}
   148  	}
   149  
   150  	if strings.HasPrefix(password, "file:") {
   151  		secretFile := strings.TrimPrefix(password, "file:")
   152  		sa.logger.Info(
   153  			"LDAP plugin configuration",
   154  			zap.String("phase", "bind_credentials"),
   155  			zap.String("password_file", secretFile),
   156  		)
   157  		fileContent, err := ioutil.ReadFile(secretFile)
   158  		if err != nil {
   159  			return fmt.Errorf("failed reading password file: %s, %s", secretFile, err)
   160  		}
   161  		password = strings.TrimSpace(string(fileContent))
   162  		if password == "" {
   163  			return fmt.Errorf("no password found in file: %s", secretFile)
   164  		}
   165  	}
   166  
   167  	sa.username = username
   168  	sa.password = password
   169  
   170  	cfg.BindUsername = username
   171  	cfg.BindPassword = password
   172  
   173  	sa.logger.Info(
   174  		"LDAP plugin configuration",
   175  		zap.String("phase", "bind_credentials"),
   176  		zap.String("username", sa.username),
   177  	)
   178  	return nil
   179  }
   180  
   181  // ConfigureSearch configures base DN, search filter, attributes for LDAP queries.
   182  func (sa *Authenticator) ConfigureSearch(cfg *Config) error {
   183  	attr := cfg.Attributes
   184  	searchBaseDN := cfg.SearchBaseDN
   185  	searchUserFilter := cfg.SearchUserFilter
   186  	searchGroupFilter := cfg.SearchGroupFilter
   187  
   188  	sa.mux.Lock()
   189  	defer sa.mux.Unlock()
   190  	if searchBaseDN == "" {
   191  		return fmt.Errorf("no search_base_dn found")
   192  	}
   193  	if searchUserFilter == "" {
   194  		searchUserFilter = "(&(|(sAMAccountName=%s)(mail=%s))(objectclass=user))"
   195  		cfg.SearchUserFilter = searchUserFilter
   196  	}
   197  	if searchGroupFilter == "" {
   198  		searchGroupFilter = "(&(uniqueMember=%s)(objectClass=groupOfUniqueNames))"
   199  		cfg.SearchGroupFilter = searchGroupFilter
   200  	}
   201  	if attr.Name == "" {
   202  		attr.Name = "givenName"
   203  		cfg.Attributes.Name = attr.Name
   204  	}
   205  	if attr.Surname == "" {
   206  		attr.Surname = "sn"
   207  		cfg.Attributes.Surname = attr.Surname
   208  	}
   209  	if attr.Username == "" {
   210  		attr.Username = "sAMAccountName"
   211  		cfg.Attributes.Username = attr.Username
   212  	}
   213  	if attr.MemberOf == "" {
   214  		attr.MemberOf = "memberOf"
   215  		cfg.Attributes.MemberOf = attr.MemberOf
   216  	}
   217  	if attr.Email == "" {
   218  		attr.Email = "mail"
   219  		cfg.Attributes.Email = attr.Email
   220  	}
   221  
   222  	if len(cfg.FallbackRoles) > 0 {
   223  		sa.fallbackRoles = cfg.FallbackRoles
   224  	}
   225  	sa.logger.Info(
   226  		"LDAP plugin configuration",
   227  		zap.String("phase", "search"),
   228  		zap.String("search_base_dn", searchBaseDN),
   229  		zap.String("search_user_filter", searchUserFilter),
   230  		zap.String("search_group_filter", searchGroupFilter),
   231  		zap.String("attr.name", attr.Name),
   232  		zap.String("attr.surname", attr.Surname),
   233  		zap.String("attr.username", attr.Username),
   234  		zap.String("attr.member_of", attr.MemberOf),
   235  		zap.String("attr.email", attr.Email),
   236  	)
   237  	sa.searchBaseDN = searchBaseDN
   238  	sa.searchUserFilter = searchUserFilter
   239  	sa.searchGroupFilter = searchGroupFilter
   240  	sa.userAttributes = attr
   241  
   242  	if len(cfg.FallbackRoles) > 0 {
   243  		sa.fallbackRoles = cfg.FallbackRoles
   244  	}
   245  
   246  	return nil
   247  }
   248  
   249  // ConfigureUserGroups configures user group bindings for LDAP searching.
   250  func (sa *Authenticator) ConfigureUserGroups(cfg *Config) error {
   251  	groups := cfg.Groups
   252  	if len(groups) == 0 {
   253  		return fmt.Errorf("no groups found")
   254  	}
   255  	for i, group := range groups {
   256  		if group.GroupDN == "" {
   257  			return fmt.Errorf("Base DN for group %d is empty", i)
   258  		}
   259  		if len(group.Roles) == 0 {
   260  			return fmt.Errorf("Role assignments for group %d is empty", i)
   261  		}
   262  		for j, role := range group.Roles {
   263  			if role == "" {
   264  				return fmt.Errorf("Role assignment %d for group %d is empty", j, i)
   265  			}
   266  		}
   267  		saGroup := &UserGroup{
   268  			GroupDN: group.GroupDN,
   269  			Roles:   group.Roles,
   270  		}
   271  		sa.logger.Info(
   272  			"LDAP plugin configuration",
   273  			zap.String("phase", "user_groups"),
   274  			zap.String("roles", strings.Join(saGroup.Roles, ", ")),
   275  			zap.String("dn", saGroup.GroupDN),
   276  		)
   277  		sa.groups = append(sa.groups, saGroup)
   278  	}
   279  	return nil
   280  }
   281  
   282  // IdentifyUser returns user challenges.
   283  func (sa *Authenticator) IdentifyUser(r *requests.Request) error {
   284  	sa.mux.Lock()
   285  	defer sa.mux.Unlock()
   286  
   287  	for _, server := range sa.servers {
   288  		conn, err := sa.dial(server)
   289  		if err != nil {
   290  			continue
   291  		}
   292  		defer conn.Close()
   293  		if err := sa.findUser(conn, server, r); err != nil {
   294  			if err.Error() == errors.ErrIdentityStoreLdapAuthFailed.WithArgs("user not found").Error() {
   295  				r.User.Username = "nobody"
   296  				r.User.Email = "nobody@localhost"
   297  				r.User.Challenges = []string{"password"}
   298  				return nil
   299  			}
   300  			r.Response.Code = 401
   301  			return err
   302  		}
   303  
   304  		return nil
   305  	}
   306  	r.Response.Code = 500
   307  	return errors.ErrIdentityStoreLdapAuthFailed.WithArgs("LDAP servers are unavailable")
   308  }
   309  
   310  // AuthenticateUser checks the database for the presence of a username/email
   311  // and password and returns user claims.
   312  func (sa *Authenticator) AuthenticateUser(r *requests.Request) error {
   313  	sa.mux.Lock()
   314  	defer sa.mux.Unlock()
   315  
   316  	for _, server := range sa.servers {
   317  		ldapConnection, err := sa.dial(server)
   318  		if err != nil {
   319  			continue
   320  		}
   321  		defer ldapConnection.Close()
   322  
   323  		searchUserFilter := strings.ReplaceAll(sa.searchUserFilter, "%s", r.User.Username)
   324  
   325  		req := ldap.NewSearchRequest(
   326  			// group.GroupDN,
   327  			sa.searchBaseDN,
   328  			ldap.ScopeWholeSubtree,
   329  			ldap.NeverDerefAliases,
   330  			0,
   331  			server.Timeout,
   332  			false,
   333  			searchUserFilter,
   334  			[]string{
   335  				sa.userAttributes.Email,
   336  			},
   337  			nil, // Controls
   338  		)
   339  
   340  		if req == nil {
   341  			sa.logger.Error(
   342  				"LDAP request building failed, request is nil",
   343  				zap.String("server", server.Address),
   344  				zap.String("search_base_dn", sa.searchBaseDN),
   345  				zap.String("search_user_filter", searchUserFilter),
   346  			)
   347  			continue
   348  		}
   349  
   350  		resp, err := ldapConnection.Search(req)
   351  		if err != nil {
   352  			sa.logger.Error(
   353  				"LDAP search failed",
   354  				zap.String("server", server.Address),
   355  				zap.String("search_base_dn", sa.searchBaseDN),
   356  				zap.String("search_user_filter", searchUserFilter),
   357  				zap.String("error", err.Error()),
   358  			)
   359  			continue
   360  		}
   361  
   362  		switch len(resp.Entries) {
   363  		case 1:
   364  		case 0:
   365  			return errors.ErrIdentityStoreLdapAuthFailed.WithArgs("user not found")
   366  		default:
   367  			return errors.ErrIdentityStoreLdapAuthFailed.WithArgs("multiple users matched")
   368  		}
   369  
   370  		user := resp.Entries[0]
   371  		// Use the provided password to make an LDAP connection.
   372  		if err := ldapConnection.Bind(user.DN, r.User.Password); err != nil {
   373  			sa.logger.Error(
   374  				"LDAP auth binding failed",
   375  				zap.String("server", server.Address),
   376  				zap.String("dn", user.DN),
   377  				zap.String("username", r.User.Username),
   378  				zap.String("error", err.Error()),
   379  			)
   380  			return errors.ErrIdentityStoreLdapAuthFailed.WithArgs(err)
   381  		}
   382  
   383  		sa.logger.Debug(
   384  			"LDAP auth succeeded",
   385  			zap.String("server", server.Address),
   386  			zap.String("dn", user.DN),
   387  			zap.String("username", r.User.Username),
   388  		)
   389  		return nil
   390  	}
   391  
   392  	return errors.ErrIdentityStoreLdapAuthFailed.WithArgs("LDAP servers are unavailable")
   393  }
   394  
   395  // ConfigureTrustedAuthorities configured trusted certificate authorities, if any.
   396  func (sa *Authenticator) ConfigureTrustedAuthorities(cfg *Config) error {
   397  	authorities := cfg.TrustedAuthorities
   398  	if len(authorities) == 0 {
   399  		return nil
   400  	}
   401  	for _, authority := range authorities {
   402  		pemCerts, err := ioutil.ReadFile(authority)
   403  		if err != nil {
   404  			return fmt.Errorf("failed reading trusted authority file: %s, %s", authority, err)
   405  		}
   406  
   407  		if sa.rootCAs == nil {
   408  			sa.rootCAs = x509.NewCertPool()
   409  		}
   410  		if ok := sa.rootCAs.AppendCertsFromPEM(pemCerts); !ok {
   411  			return fmt.Errorf("failed added trusted authority file contents to Root CA pool: %s", authority)
   412  		}
   413  		sa.logger.Debug(
   414  			"added trusted authority",
   415  			zap.String("pem_file", authority),
   416  		)
   417  	}
   418  	return nil
   419  }
   420  
   421  func (sa *Authenticator) searchGroups(conn *ldap.Conn, reqData map[string]interface{}, roles map[string]bool) error {
   422  	if roles == nil {
   423  		roles = make(map[string]bool)
   424  	}
   425  
   426  	req := ldap.NewSearchRequest(reqData["base_dn"].(string), ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0,
   427  		reqData["timeout"].(int), false, reqData["search_group_filter"].(string), []string{"dn"}, nil,
   428  	)
   429  	if req == nil {
   430  		return fmt.Errorf("failed building group search LDAP request")
   431  	}
   432  
   433  	resp, err := conn.Search(req)
   434  	if err != nil {
   435  		return err
   436  	}
   437  
   438  	if len(resp.Entries) < 1 {
   439  		return fmt.Errorf("no groups found for %s", reqData["user_dn"].(string))
   440  	}
   441  
   442  	for _, entry := range resp.Entries {
   443  		for _, g := range sa.groups {
   444  			if g.GroupDN != entry.DN {
   445  				continue
   446  			}
   447  			for _, role := range g.Roles {
   448  				if role == "" {
   449  					continue
   450  				}
   451  				roles[role] = true
   452  			}
   453  		}
   454  	}
   455  	return nil
   456  }
   457  
   458  func (sa *Authenticator) dial(server *AuthServer) (*ldap.Conn, error) {
   459  	var ldapDialer net.Conn
   460  	var err error
   461  	timeout := time.Duration(server.Timeout) * time.Second
   462  	if server.Encrypted {
   463  		// Handle LDAPS servers.
   464  		tlsConfig := &tls.Config{
   465  			InsecureSkipVerify: server.IgnoreCertErrors,
   466  		}
   467  		if sa.rootCAs != nil {
   468  			tlsConfig.RootCAs = sa.rootCAs
   469  		}
   470  		ldapDialer, err = tls.DialWithDialer(
   471  			&net.Dialer{
   472  				Timeout: timeout,
   473  			},
   474  			"tcp",
   475  			net.JoinHostPort(server.URL.Hostname(), server.Port),
   476  			tlsConfig,
   477  		)
   478  		if err != nil {
   479  			sa.logger.Error(
   480  				"LDAP TLS dialer failed",
   481  				zap.String("server", server.Address),
   482  				zap.Error(err),
   483  			)
   484  			return nil, err
   485  		}
   486  		sa.logger.Debug(
   487  			"LDAP TLS dialer setup succeeded",
   488  			zap.String("server", server.Address),
   489  		)
   490  	} else {
   491  		// Handle LDAP servers.
   492  		ldapDialer, err = net.DialTimeout("tcp", net.JoinHostPort(server.URL.Hostname(), server.Port), timeout)
   493  		if err != nil {
   494  			sa.logger.Error(
   495  				"LDAP dialer failed",
   496  				zap.String("server", server.Address),
   497  				zap.Error(err),
   498  			)
   499  			return nil, err
   500  		}
   501  		sa.logger.Debug(
   502  			"LDAP dialer setup succeeded",
   503  			zap.String("server", server.Address),
   504  		)
   505  	}
   506  
   507  	ldapConnection := ldap.NewConn(ldapDialer, server.Encrypted)
   508  	if ldapConnection == nil {
   509  		err = fmt.Errorf("ldap connection is nil")
   510  		sa.logger.Error(
   511  			"LDAP connection failed",
   512  			zap.String("server", server.Address),
   513  			zap.Error(err),
   514  		)
   515  		return nil, err
   516  	}
   517  
   518  	if server.Encrypted {
   519  		tlsState, ok := ldapConnection.TLSConnectionState()
   520  		if !ok {
   521  			err = fmt.Errorf("TLSConnectionState is not ok")
   522  			sa.logger.Error(
   523  				"LDAP connection TLS state polling failed",
   524  				zap.String("server", server.Address),
   525  				zap.Error(err),
   526  			)
   527  			return nil, err
   528  		}
   529  
   530  		sa.logger.Debug(
   531  			"LDAP connection TLS state polling succeeded",
   532  			zap.String("server", server.Address),
   533  			zap.String("server_name", tlsState.ServerName),
   534  			zap.Bool("handshake_complete", tlsState.HandshakeComplete),
   535  			zap.String("version", fmt.Sprintf("%d", tlsState.Version)),
   536  			zap.String("negotiated_protocol", tlsState.NegotiatedProtocol),
   537  		)
   538  	}
   539  
   540  	ldapConnection.Start()
   541  
   542  	if err := ldapConnection.Bind(sa.username, sa.password); err != nil {
   543  		sa.logger.Error(
   544  			"LDAP connection binding failed",
   545  			zap.String("server", server.Address),
   546  			zap.String("username", sa.username),
   547  			zap.String("error", err.Error()),
   548  		)
   549  		return nil, err
   550  	}
   551  	sa.logger.Debug(
   552  		"LDAP binding succeeded",
   553  		zap.String("server", server.Address),
   554  	)
   555  	return ldapConnection, nil
   556  }
   557  
   558  func (sa *Authenticator) findUser(ldapConnection *ldap.Conn, server *AuthServer, r *requests.Request) error {
   559  	searchUserFilter := strings.ReplaceAll(sa.searchUserFilter, "%s", r.User.Username)
   560  
   561  	req := ldap.NewSearchRequest(
   562  		// group.GroupDN,
   563  		sa.searchBaseDN,
   564  		ldap.ScopeWholeSubtree,
   565  		ldap.NeverDerefAliases,
   566  		0,
   567  		server.Timeout,
   568  		false,
   569  		searchUserFilter,
   570  		[]string{
   571  			sa.userAttributes.Name,
   572  			sa.userAttributes.Surname,
   573  			sa.userAttributes.Username,
   574  			sa.userAttributes.MemberOf,
   575  			sa.userAttributes.Email,
   576  		},
   577  		nil, // Controls
   578  	)
   579  
   580  	if req == nil {
   581  		sa.logger.Error(
   582  			"LDAP request building failed, request is nil",
   583  			zap.String("server", server.Address),
   584  			zap.String("search_base_dn", sa.searchBaseDN),
   585  			zap.String("search_user_filter", searchUserFilter),
   586  		)
   587  		return errors.ErrIdentityStoreLdapAuthFailed.WithArgs("LDAP request building failed, request is nil")
   588  	}
   589  
   590  	resp, err := ldapConnection.Search(req)
   591  	if err != nil {
   592  		sa.logger.Error(
   593  			"LDAP search failed",
   594  			zap.String("server", server.Address),
   595  			zap.String("search_base_dn", sa.searchBaseDN),
   596  			zap.String("search_user_filter", searchUserFilter),
   597  			zap.String("error", err.Error()),
   598  		)
   599  		return errors.ErrIdentityStoreLdapAuthFailed.WithArgs("LDAP search failed")
   600  	}
   601  
   602  	sa.logger.Debug(
   603  		"LDAP search succeeded",
   604  		zap.String("server", server.Address),
   605  		zap.Int("entry_count", len(resp.Entries)),
   606  		zap.String("search_base_dn", sa.searchBaseDN),
   607  		zap.String("search_user_filter", searchUserFilter),
   608  		zap.Any("users", resp.Entries),
   609  	)
   610  
   611  	switch len(resp.Entries) {
   612  	case 1:
   613  	case 0:
   614  		return errors.ErrIdentityStoreLdapAuthFailed.WithArgs("user not found")
   615  	default:
   616  		return errors.ErrIdentityStoreLdapAuthFailed.WithArgs("multiple users matched")
   617  	}
   618  
   619  	user := resp.Entries[0]
   620  	var userFullName, userLastName, userFirstName, userAccountName, userMail string
   621  	userRoles := make(map[string]bool)
   622  
   623  	if server.PosixGroups {
   624  		// Handle POSIX group memberships.
   625  		searchGroupRequest := map[string]interface{}{
   626  			"user_dn":             user.DN,
   627  			"base_dn":             sa.searchBaseDN,
   628  			"search_group_filter": strings.ReplaceAll(sa.searchGroupFilter, "%s", user.DN),
   629  			"timeout":             server.Timeout,
   630  		}
   631  		if err := sa.searchGroups(ldapConnection, searchGroupRequest, userRoles); err != nil {
   632  			sa.logger.Error(
   633  				"LDAP group search failed, request",
   634  				zap.String("server", server.Address),
   635  				zap.String("base_dn", sa.searchBaseDN),
   636  				zap.String("search_group_filter", sa.searchGroupFilter),
   637  				zap.Error(err),
   638  			)
   639  			return err
   640  		}
   641  	}
   642  
   643  	for _, attr := range user.Attributes {
   644  		if len(attr.Values) < 1 {
   645  			continue
   646  		}
   647  		if attr.Name == sa.userAttributes.Name {
   648  			userFirstName = attr.Values[0]
   649  		}
   650  		if attr.Name == sa.userAttributes.Surname {
   651  			userLastName = attr.Values[0]
   652  		}
   653  		if attr.Name == sa.userAttributes.Username {
   654  			userAccountName = attr.Values[0]
   655  		}
   656  		if attr.Name == sa.userAttributes.MemberOf {
   657  			for _, v := range attr.Values {
   658  				for _, g := range sa.groups {
   659  					if g.GroupDN != v {
   660  						continue
   661  					}
   662  					for _, role := range g.Roles {
   663  						if role == "" {
   664  							continue
   665  						}
   666  						userRoles[role] = true
   667  					}
   668  				}
   669  			}
   670  		}
   671  		if attr.Name == sa.userAttributes.Email {
   672  			userMail = attr.Values[0]
   673  		}
   674  	}
   675  
   676  	if userFirstName != "" {
   677  		userFullName = userFirstName
   678  	}
   679  	if userLastName != "" {
   680  		if userFullName == "" {
   681  			userFullName = userLastName
   682  		} else {
   683  			userFullName = userFullName + " " + userLastName
   684  		}
   685  	}
   686  
   687  	switch {
   688  	case len(userRoles) == 0 && len(sa.fallbackRoles) == 0:
   689  		return errors.ErrIdentityStoreLdapAuthFailed.WithArgs("no matched groups")
   690  	case len(userRoles) == 0:
   691  		for _, role := range sa.fallbackRoles {
   692  			r.User.Roles = append(r.User.Roles, role)
   693  		}
   694  	default:
   695  		for role := range userRoles {
   696  			r.User.Roles = append(r.User.Roles, role)
   697  		}
   698  	}
   699  
   700  	r.User.Username = userAccountName
   701  	r.User.Email = userMail
   702  	r.User.FullName = userFullName
   703  
   704  	sa.logger.Debug(
   705  		"LDAP user match",
   706  		zap.String("server", server.Address),
   707  		zap.String("name", r.User.FullName),
   708  		zap.String("username", r.User.Username),
   709  		zap.String("email", r.User.Email),
   710  		zap.Any("roles", r.User.Roles),
   711  	)
   712  
   713  	r.User.Challenges = []string{"password"}
   714  	r.Response.Code = 200
   715  	return nil
   716  }