2019-04-24 21:52:08 +00:00
package authentication
import (
2019-12-06 08:15:54 +00:00
"crypto/tls"
2021-01-04 10:28:55 +00:00
"crypto/x509"
2019-04-24 21:52:08 +00:00
"fmt"
2021-08-05 04:30:00 +00:00
"net"
2022-12-21 10:31:21 +00:00
"strconv"
2019-04-24 21:52:08 +00:00
"strings"
2023-05-11 11:26:14 +00:00
"github.com/go-ldap/ldap/v3"
2021-04-12 01:10:50 +00:00
"github.com/sirupsen/logrus"
2020-04-05 12:37:21 +00:00
2021-08-11 01:04:35 +00:00
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/logging"
"github.com/authelia/authelia/v4/internal/utils"
2019-04-24 21:52:08 +00:00
)
2021-08-05 04:17:07 +00:00
// LDAPUserProvider is a UserProvider that connects to LDAP servers like ActiveDirectory, OpenLDAP, OpenDJ, FreeIPA, etc.
2019-04-24 21:52:08 +00:00
type LDAPUserProvider struct {
2022-10-17 10:51:59 +00:00
config schema . LDAPAuthenticationBackend
2022-05-02 01:51:38 +00:00
tlsConfig * tls . Config
dialOpts [ ] ldap . DialOpt
log * logrus . Logger
2022-05-10 04:38:36 +00:00
factory LDAPClientFactory
2021-07-01 23:16:16 +00:00
2022-12-21 10:31:21 +00:00
clock utils . Clock
2021-09-17 09:53:59 +00:00
disableResetPassword bool
2022-05-10 04:38:36 +00:00
// Automatically detected LDAP features.
features LDAPSupportedFeatures
2021-08-05 04:17:07 +00:00
// Dynamically generated users values.
2022-12-21 10:31:21 +00:00
usersBaseDN string
usersAttributes [ ] string
usersFilterReplacementInput bool
usersFilterReplacementDateTimeGeneralized bool
usersFilterReplacementDateTimeUnixEpoch bool
usersFilterReplacementDateTimeMicrosoftNTTimeEpoch bool
2021-08-05 04:17:07 +00:00
// Dynamically generated groups values.
2023-05-07 13:52:10 +00:00
groupsBaseDN string
groupsAttributes [ ] string
groupsFilterReplacementInput bool
groupsFilterReplacementUsername bool
groupsFilterReplacementDN bool
groupsFilterReplacementsMemberOfDN bool
groupsFilterReplacementsMemberOfRDN bool
2019-04-24 21:52:08 +00:00
}
2022-12-21 10:31:21 +00:00
// NewLDAPUserProvider creates a new instance of LDAPUserProvider with the ProductionLDAPClientFactory.
2022-10-17 10:51:59 +00:00
func NewLDAPUserProvider ( config schema . AuthenticationBackend , certPool * x509 . CertPool ) ( provider * LDAPUserProvider ) {
2022-12-21 10:31:21 +00:00
provider = NewLDAPUserProviderWithFactory ( * config . LDAP , config . PasswordReset . Disable , certPool , NewProductionLDAPClientFactory ( ) )
2021-07-01 23:16:16 +00:00
2021-09-17 09:53:59 +00:00
return provider
2021-07-01 23:16:16 +00:00
}
2022-12-21 10:31:21 +00:00
// NewLDAPUserProviderWithFactory creates a new instance of LDAPUserProvider with the specified LDAPClientFactory.
func NewLDAPUserProviderWithFactory ( config schema . LDAPAuthenticationBackend , disableResetPassword bool , certPool * x509 . CertPool , factory LDAPClientFactory ) ( provider * LDAPUserProvider ) {
2022-05-02 01:51:38 +00:00
if config . TLS == nil {
2022-10-17 10:51:59 +00:00
config . TLS = schema . DefaultLDAPAuthenticationBackendConfigurationImplementationCustom . TLS
2020-12-03 05:23:52 +00:00
}
2022-10-21 08:41:33 +00:00
tlsConfig := utils . NewTLSConfig ( config . TLS , certPool )
2020-12-03 05:23:52 +00:00
2021-08-05 04:30:00 +00:00
var dialOpts = [ ] ldap . DialOpt {
2022-05-02 01:51:38 +00:00
ldap . DialWithDialer ( & net . Dialer { Timeout : config . Timeout } ) ,
2021-08-05 04:30:00 +00:00
}
2020-12-03 05:23:52 +00:00
2021-01-04 10:28:55 +00:00
if tlsConfig != nil {
2021-08-05 04:30:00 +00:00
dialOpts = append ( dialOpts , ldap . DialWithTLSConfig ( tlsConfig ) )
2020-12-03 05:23:52 +00:00
}
2021-07-01 23:16:16 +00:00
if factory == nil {
2022-05-10 04:38:36 +00:00
factory = NewProductionLDAPClientFactory ( )
2021-07-01 23:16:16 +00:00
}
provider = & LDAPUserProvider {
2022-05-02 01:51:38 +00:00
config : config ,
2021-09-17 09:53:59 +00:00
tlsConfig : tlsConfig ,
dialOpts : dialOpts ,
2021-11-23 09:45:38 +00:00
log : logging . Logger ( ) ,
2022-05-02 01:51:38 +00:00
factory : factory ,
2021-09-17 09:53:59 +00:00
disableResetPassword : disableResetPassword ,
2022-12-21 10:31:21 +00:00
clock : & utils . RealClock { } ,
2019-04-24 21:52:08 +00:00
}
2021-01-04 10:28:55 +00:00
2021-08-05 04:17:07 +00:00
provider . parseDynamicUsersConfiguration ( )
provider . parseDynamicGroupsConfiguration ( )
2023-05-07 13:52:10 +00:00
provider . parseDynamicConfiguration ( )
2021-01-04 10:28:55 +00:00
return provider
2019-12-06 08:15:54 +00:00
}
2022-05-02 01:51:38 +00:00
// CheckUserPassword checks if provided password matches for the given user.
2022-05-10 04:38:36 +00:00
func ( p * LDAPUserProvider ) CheckUserPassword ( username string , password string ) ( valid bool , err error ) {
2022-05-02 01:51:38 +00:00
var (
2022-05-10 04:38:36 +00:00
client , clientUser LDAPClient
profile * ldapUserProfile
2022-05-02 01:51:38 +00:00
)
2022-05-10 04:38:36 +00:00
if client , err = p . connect ( ) ; err != nil {
2022-05-02 01:51:38 +00:00
return false , err
2019-12-06 08:15:54 +00:00
}
2022-05-10 04:38:36 +00:00
defer client . Close ( )
2022-05-02 01:51:38 +00:00
2023-05-11 11:26:14 +00:00
if profile , err = p . getUserProfile ( client , username , false ) ; err != nil {
2022-05-02 01:51:38 +00:00
return false , err
}
2023-05-07 06:39:17 +00:00
if clientUser , err = p . connectCustom ( p . config . Address . String ( ) , profile . DN , password , p . config . StartTLS , p . dialOpts ... ) ; err != nil {
2022-05-02 01:51:38 +00:00
return false , fmt . Errorf ( "authentication failed. Cause: %w" , err )
2020-12-03 05:23:52 +00:00
}
2022-05-10 04:38:36 +00:00
defer clientUser . Close ( )
2022-05-02 01:51:38 +00:00
return true , nil
}
// GetDetails retrieve the groups a user belongs to.
func ( p * LDAPUserProvider ) GetDetails ( username string ) ( details * UserDetails , err error ) {
var (
2022-05-10 04:38:36 +00:00
client LDAPClient
2022-05-02 01:51:38 +00:00
profile * ldapUserProfile
)
2022-05-10 04:38:36 +00:00
if client , err = p . connect ( ) ; err != nil {
2019-12-06 08:15:54 +00:00
return nil , err
}
2020-05-05 19:35:32 +00:00
2022-05-10 04:38:36 +00:00
defer client . Close ( )
2022-05-02 01:51:38 +00:00
2023-05-11 11:26:14 +00:00
if profile , err = p . getUserProfile ( client , username , false ) ; err != nil {
2022-05-02 01:51:38 +00:00
return nil , err
}
var (
2023-05-07 13:52:10 +00:00
groups [ ] string
2022-05-02 01:51:38 +00:00
)
2023-05-07 13:52:10 +00:00
if groups , err = p . getUserGroups ( client , username , profile ) ; err != nil {
return nil , err
2022-05-02 01:51:38 +00:00
}
return & UserDetails {
Username : profile . Username ,
DisplayName : profile . DisplayName ,
Emails : profile . Emails ,
Groups : groups ,
} , nil
2019-04-24 21:52:08 +00:00
}
2022-05-02 01:51:38 +00:00
// UpdatePassword update the password of the given user.
func ( p * LDAPUserProvider ) UpdatePassword ( username , password string ) ( err error ) {
var (
2022-05-10 04:38:36 +00:00
client LDAPClient
2022-05-02 01:51:38 +00:00
profile * ldapUserProfile
)
2022-05-10 04:38:36 +00:00
if client , err = p . connect ( ) ; err != nil {
2022-05-02 01:51:38 +00:00
return fmt . Errorf ( "unable to update password. Cause: %w" , err )
2019-04-24 21:52:08 +00:00
}
2022-05-02 01:51:38 +00:00
2022-05-10 04:38:36 +00:00
defer client . Close ( )
2019-04-24 21:52:08 +00:00
2023-05-11 11:26:14 +00:00
if profile , err = p . getUserProfile ( client , username , true ) ; err != nil {
2022-05-02 01:51:38 +00:00
return fmt . Errorf ( "unable to update password. Cause: %w" , err )
}
var controls [ ] ldap . Control
switch {
2022-05-10 04:38:36 +00:00
case p . features . ControlTypes . MsftPwdPolHints :
controls = append ( controls , & controlMsftServerPolicyHints { ldapOIDControlMsftServerPolicyHints } )
case p . features . ControlTypes . MsftPwdPolHintsDeprecated :
controls = append ( controls , & controlMsftServerPolicyHints { ldapOIDControlMsftServerPolicyHintsDeprecated } )
}
switch {
case p . features . Extensions . PwdModifyExOp :
2022-05-02 01:51:38 +00:00
pwdModifyRequest := ldap . NewPasswordModifyRequest (
profile . DN ,
"" ,
password ,
)
2022-05-10 04:38:36 +00:00
err = p . pwdModify ( client , pwdModifyRequest )
2022-05-02 01:51:38 +00:00
case p . config . Implementation == schema . LDAPImplementationActiveDirectory :
modifyRequest := ldap . NewModifyRequest ( profile . DN , controls )
// The password needs to be enclosed in quotes
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/6e803168-f140-4d23-b2d3-c3a8ab5917d2
pwdEncoded , _ := utf16LittleEndian . NewEncoder ( ) . String ( fmt . Sprintf ( "\"%s\"" , password ) )
modifyRequest . Replace ( ldapAttributeUnicodePwd , [ ] string { pwdEncoded } )
2022-05-10 04:38:36 +00:00
err = p . modify ( client , modifyRequest )
2022-05-02 01:51:38 +00:00
default :
modifyRequest := ldap . NewModifyRequest ( profile . DN , controls )
modifyRequest . Replace ( ldapAttributeUserPassword , [ ] string { password } )
2022-05-10 04:38:36 +00:00
err = p . modify ( client , modifyRequest )
2019-04-24 21:52:08 +00:00
}
if err != nil {
2022-05-02 01:51:38 +00:00
return fmt . Errorf ( "unable to update password. Cause: %w" , err )
2019-04-24 21:52:08 +00:00
}
2022-05-02 01:51:38 +00:00
return nil
}
2022-05-10 04:38:36 +00:00
func ( p * LDAPUserProvider ) connect ( ) ( client LDAPClient , err error ) {
2023-05-07 06:39:17 +00:00
return p . connectCustom ( p . config . Address . String ( ) , p . config . User , p . config . Password , p . config . StartTLS , p . dialOpts ... )
2019-04-24 21:52:08 +00:00
}
2022-06-17 11:03:47 +00:00
func ( p * LDAPUserProvider ) connectCustom ( url , username , password string , startTLS bool , opts ... ldap . DialOpt ) ( client LDAPClient , err error ) {
2022-05-10 04:38:36 +00:00
if client , err = p . factory . DialURL ( url , opts ... ) ; err != nil {
2022-05-02 01:51:38 +00:00
return nil , fmt . Errorf ( "dial failed with error: %w" , err )
2020-01-20 19:34:53 +00:00
}
2020-05-05 19:35:32 +00:00
2022-05-02 01:51:38 +00:00
if startTLS {
2022-05-10 04:38:36 +00:00
if err = client . StartTLS ( p . tlsConfig ) ; err != nil {
client . Close ( )
2022-05-02 01:51:38 +00:00
return nil , fmt . Errorf ( "starttls failed with error: %w" , err )
}
}
2022-06-17 11:03:47 +00:00
if password == "" {
err = client . UnauthenticatedBind ( username )
} else {
err = client . Bind ( username , password )
}
if err != nil {
2022-05-10 04:38:36 +00:00
client . Close ( )
2022-05-02 01:51:38 +00:00
return nil , fmt . Errorf ( "bind failed with error: %w" , err )
}
2022-05-10 04:38:36 +00:00
return client , nil
2020-01-20 19:34:53 +00:00
}
2022-10-17 10:51:59 +00:00
func ( p * LDAPUserProvider ) search ( client LDAPClient , request * ldap . SearchRequest ) ( result * ldap . SearchResult , err error ) {
if result , err = client . Search ( request ) ; err != nil {
2022-05-02 01:51:38 +00:00
if referral , ok := p . getReferral ( err ) ; ok {
2022-10-17 10:51:59 +00:00
if result == nil {
result = & ldap . SearchResult {
2022-05-10 04:38:36 +00:00
Referrals : [ ] string { referral } ,
}
} else {
2022-10-17 10:51:59 +00:00
result . Referrals = append ( result . Referrals , referral )
2022-05-02 01:51:38 +00:00
}
2023-05-07 13:52:10 +00:00
} else {
return nil , err
2022-05-02 01:51:38 +00:00
}
}
2022-10-17 10:51:59 +00:00
if ! p . config . PermitReferrals || len ( result . Referrals ) == 0 {
2022-05-10 04:38:36 +00:00
if err != nil {
return nil , err
}
2022-10-17 10:51:59 +00:00
return result , nil
2022-05-02 01:51:38 +00:00
}
2022-10-17 10:51:59 +00:00
if err = p . searchReferrals ( request , result ) ; err != nil {
2022-05-10 04:38:36 +00:00
return nil , err
}
2022-05-02 01:51:38 +00:00
2022-10-17 10:51:59 +00:00
return result , nil
2020-03-15 07:10:25 +00:00
}
2019-04-24 21:52:08 +00:00
2022-10-17 10:51:59 +00:00
func ( p * LDAPUserProvider ) searchReferral ( referral string , request * ldap . SearchRequest , searchResult * ldap . SearchResult ) ( err error ) {
2022-05-02 01:51:38 +00:00
var (
2022-05-10 04:38:36 +00:00
client LDAPClient
2022-05-02 01:51:38 +00:00
result * ldap . SearchResult
)
2020-03-30 22:36:04 +00:00
2022-05-10 04:38:36 +00:00
if client , err = p . connectCustom ( referral , p . config . User , p . config . Password , p . config . StartTLS , p . dialOpts ... ) ; err != nil {
return fmt . Errorf ( "error occurred connecting to referred LDAP server '%s': %w" , referral , err )
2021-08-05 04:17:07 +00:00
}
2020-03-30 22:36:04 +00:00
2022-05-10 04:38:36 +00:00
defer client . Close ( )
2021-04-12 01:10:50 +00:00
2022-10-17 10:51:59 +00:00
if result , err = client . Search ( request ) ; err != nil {
2022-05-10 04:38:36 +00:00
return fmt . Errorf ( "error occurred performing search on referred LDAP server '%s': %w" , referral , err )
2022-05-02 01:51:38 +00:00
}
for i := 0 ; i < len ( result . Entries ) ; i ++ {
if ! ldapEntriesContainsEntry ( result . Entries [ i ] , searchResult . Entries ) {
searchResult . Entries = append ( searchResult . Entries , result . Entries [ i ] )
}
}
return nil
}
2022-10-17 10:51:59 +00:00
func ( p * LDAPUserProvider ) searchReferrals ( request * ldap . SearchRequest , result * ldap . SearchResult ) ( err error ) {
for i := 0 ; i < len ( result . Referrals ) ; i ++ {
if err = p . searchReferral ( result . Referrals [ i ] , request , result ) ; err != nil {
2022-05-10 04:38:36 +00:00
return err
}
2022-05-02 01:51:38 +00:00
}
2022-05-10 04:38:36 +00:00
return nil
2020-03-30 22:36:04 +00:00
}
2023-05-11 11:26:14 +00:00
func ( p * LDAPUserProvider ) getUserProfile ( client LDAPClient , username string , reset bool ) ( profile * ldapUserProfile , err error ) {
2020-04-20 21:03:38 +00:00
// Search for the given username.
2022-10-17 10:51:59 +00:00
request := ldap . NewSearchRequest (
2021-04-12 01:10:50 +00:00
p . usersBaseDN , ldap . ScopeWholeSubtree , ldap . NeverDerefAliases ,
2023-05-11 11:26:14 +00:00
1 , 0 , false , p . resolveUsersFilter ( username , reset ) , p . usersAttributes , nil ,
2019-04-24 21:52:08 +00:00
)
2022-10-28 09:21:43 +00:00
p . log .
WithField ( "base_dn" , request . BaseDN ) .
WithField ( "filter" , request . Filter ) .
WithField ( "attr" , request . Attributes ) .
WithField ( "scope" , request . Scope ) .
WithField ( "deref" , request . DerefAliases ) .
Trace ( "Performing user search" )
2022-10-17 10:51:59 +00:00
var result * ldap . SearchResult
2022-05-02 01:51:38 +00:00
2022-10-17 10:51:59 +00:00
if result , err = p . search ( client , request ) ; err != nil {
2022-05-10 04:38:36 +00:00
return nil , fmt . Errorf ( "cannot find user DN of user '%s'. Cause: %w" , username , err )
2019-04-24 21:52:08 +00:00
}
2022-10-17 10:51:59 +00:00
if len ( result . Entries ) == 0 {
2020-05-04 19:39:25 +00:00
return nil , ErrUserNotFound
2019-04-24 21:52:08 +00:00
}
2022-10-17 10:51:59 +00:00
if len ( result . Entries ) > 1 {
return nil , fmt . Errorf ( "there were %d users found when searching for '%s' but there should only be 1" , len ( result . Entries ) , username )
2019-04-24 21:52:08 +00:00
}
2023-05-07 13:52:10 +00:00
return p . getUserProfileResultToProfile ( username , result )
}
//nolint:gocyclo // Not overly complex.
func ( p * LDAPUserProvider ) getUserProfileResultToProfile ( username string , result * ldap . SearchResult ) ( profile * ldapUserProfile , err error ) {
2020-03-15 07:10:25 +00:00
userProfile := ldapUserProfile {
2022-10-17 10:51:59 +00:00
DN : result . Entries [ 0 ] . DN ,
2019-04-24 21:52:08 +00:00
}
2020-05-05 19:35:32 +00:00
2022-10-17 10:51:59 +00:00
for _ , attr := range result . Entries [ 0 ] . Attributes {
2022-05-15 06:37:23 +00:00
attrs := len ( attr . Values )
2022-05-10 04:38:36 +00:00
2023-05-07 13:52:10 +00:00
switch attr . Name {
case p . config . Attributes . Username :
2022-05-10 04:38:36 +00:00
switch attrs {
case 1 :
userProfile . Username = attr . Values [ 0 ]
2023-05-07 13:52:10 +00:00
if attr . Name == p . config . Attributes . DisplayName && userProfile . DisplayName == "" {
userProfile . DisplayName = attr . Values [ 0 ]
}
if attr . Name == p . config . Attributes . Mail && len ( userProfile . Emails ) == 0 {
userProfile . Emails = [ ] string { attr . Values [ 0 ] }
}
2022-05-10 04:38:36 +00:00
case 0 :
return nil , fmt . Errorf ( "user '%s' must have value for attribute '%s'" ,
2023-05-07 13:52:10 +00:00
username , p . config . Attributes . Username )
2022-05-10 04:38:36 +00:00
default :
return nil , fmt . Errorf ( "user '%s' has %d values for for attribute '%s' but the attribute must be a single value attribute" ,
2023-05-07 13:52:10 +00:00
username , attrs , p . config . Attributes . Username )
}
case p . config . Attributes . Mail :
if attrs == 0 {
continue
2020-03-15 07:10:25 +00:00
}
2022-05-15 06:37:23 +00:00
userProfile . Emails = attr . Values
2023-05-07 13:52:10 +00:00
case p . config . Attributes . DisplayName :
if attrs == 0 {
continue
}
2022-05-15 06:37:23 +00:00
userProfile . DisplayName = attr . Values [ 0 ]
2023-05-07 13:52:10 +00:00
case p . config . Attributes . MemberOf :
if attrs == 0 {
continue
}
userProfile . MemberOf = attr . Values
2022-05-15 06:37:23 +00:00
}
2019-04-24 21:52:08 +00:00
}
2022-05-10 04:38:36 +00:00
if userProfile . Username == "" {
return nil , fmt . Errorf ( "user '%s' must have value for attribute '%s'" ,
2023-05-07 13:52:10 +00:00
username , p . config . Attributes . Username )
2022-05-10 04:38:36 +00:00
}
2020-03-15 07:10:25 +00:00
if userProfile . DN == "" {
2022-05-10 04:38:36 +00:00
return nil , fmt . Errorf ( "user '%s' must have a distinguished name but the result returned an empty distinguished name" , username )
2019-04-24 21:52:08 +00:00
}
2020-03-15 07:10:25 +00:00
return & userProfile , nil
2019-04-24 21:52:08 +00:00
}
2023-05-07 13:52:10 +00:00
func ( p * LDAPUserProvider ) getUserGroups ( client LDAPClient , username string , profile * ldapUserProfile ) ( groups [ ] string , err error ) {
request := ldap . NewSearchRequest (
p . groupsBaseDN , ldap . ScopeWholeSubtree , ldap . NeverDerefAliases ,
0 , 0 , false , p . resolveGroupsFilter ( username , profile ) , p . groupsAttributes , nil ,
)
p . log .
WithField ( "base_dn" , request . BaseDN ) .
WithField ( "filter" , request . Filter ) .
WithField ( "attributes" , request . Attributes ) .
WithField ( "scope" , request . Scope ) .
WithField ( "deref" , request . DerefAliases ) .
WithField ( "mode" , p . config . GroupSearchMode ) .
Trace ( "Performing group search" )
switch p . config . GroupSearchMode {
case "" , "filter" :
return p . getUserGroupsRequestFilter ( client , username , profile , request )
case "memberof" :
return p . getUserGroupsRequestMemberOf ( client , username , profile , request )
default :
return nil , fmt . Errorf ( "could not perform group search with mode '%s' as it's unknown" , p . config . GroupSearchMode )
}
}
func ( p * LDAPUserProvider ) getUserGroupsRequestFilter ( client LDAPClient , username string , _ * ldapUserProfile , request * ldap . SearchRequest ) ( groups [ ] string , err error ) {
var result * ldap . SearchResult
if result , err = p . search ( client , request ) ; err != nil {
return nil , fmt . Errorf ( "unable to retrieve groups of user '%s'. Cause: %w" , username , err )
}
for _ , entry := range result . Entries {
attributes :
for _ , attr := range entry . Attributes {
switch attr . Name {
case p . config . Attributes . GroupName :
switch n := len ( attr . Values ) ; n {
case 0 :
continue
case 1 :
groups = append ( groups , attr . Values [ 0 ] )
default :
fmt . Println ( attr . Name , n , attr . Values )
return nil , fmt . Errorf ( "unable to retrieve groups of user '%s': Cause: the group '%s' attribute '%s' (group name attribute) has more than one value" , username , entry . DN , p . config . Attributes . GroupName )
}
break attributes
}
}
}
return groups , nil
}
func ( p * LDAPUserProvider ) getUserGroupsRequestMemberOf ( client LDAPClient , username string , profile * ldapUserProfile , request * ldap . SearchRequest ) ( groups [ ] string , err error ) {
var result * ldap . SearchResult
if result , err = p . search ( client , request ) ; err != nil {
return nil , fmt . Errorf ( "unable to retrieve groups of user '%s'. Cause: %w" , username , err )
}
for _ , entry := range result . Entries {
if len ( entry . Attributes ) == 0 {
p . log .
WithField ( "dn" , entry . DN ) .
WithField ( "attributes" , request . Attributes ) .
WithField ( "mode" , "memberof" ) .
Trace ( "Skipping Group as the server did not return any requested attributes" )
continue
}
if ! utils . IsStringInSliceFold ( entry . DN , profile . MemberOf ) {
p . log .
WithField ( "dn" , entry . DN ) .
WithField ( "mode" , "memberof" ) .
Trace ( "Skipping Group as it doesn't match the users memberof entries" )
continue
}
attributes :
for _ , attr := range entry . Attributes {
switch attr . Name {
case p . config . Attributes . GroupName :
switch len ( attr . Values ) {
case 0 :
p . log .
WithField ( "dn" , entry . DN ) .
WithField ( "attribute" , attr . Name ) .
Trace ( "Group skipped as the server returned a null attribute" )
case 1 :
switch len ( attr . Values [ 0 ] ) {
case 0 :
p . log .
WithField ( "dn" , entry . DN ) .
WithField ( "attribute" , attr . Name ) .
Trace ( "Skipping group as the configured group name attribute had no value" )
default :
groups = append ( groups , attr . Values [ 0 ] )
}
default :
p . log .
WithField ( "dn" , entry . DN ) .
WithField ( "attribute" , attr . Name ) .
Trace ( "Group skipped as the server returned a multi-valued attribute but it should be a single-valued attribute" )
}
break attributes
}
}
}
return groups , nil
}
2023-05-11 11:26:14 +00:00
func ( p * LDAPUserProvider ) resolveUsersFilter ( input string , reset bool ) ( filter string ) {
if reset {
filter = p . config . UsersResetFilter
} else {
filter = p . config . UsersFilter
}
2022-05-02 01:51:38 +00:00
if p . usersFilterReplacementInput {
// The {input} placeholder is replaced by the username input.
2022-12-21 10:31:21 +00:00
filter = strings . ReplaceAll ( filter , ldapPlaceholderInput , ldapEscape ( input ) )
}
if p . usersFilterReplacementDateTimeGeneralized {
filter = strings . ReplaceAll ( filter , ldapPlaceholderDateTimeGeneralized , p . clock . Now ( ) . UTC ( ) . Format ( ldapGeneralizedTimeDateTimeFormat ) )
}
if p . usersFilterReplacementDateTimeUnixEpoch {
filter = strings . ReplaceAll ( filter , ldapPlaceholderDateTimeUnixEpoch , strconv . Itoa ( int ( p . clock . Now ( ) . Unix ( ) ) ) )
}
if p . usersFilterReplacementDateTimeMicrosoftNTTimeEpoch {
filter = strings . ReplaceAll ( filter , ldapPlaceholderDateTimeMicrosoftNTTimeEpoch , strconv . Itoa ( int ( utils . UnixNanoTimeToMicrosoftNTEpoch ( p . clock . Now ( ) . UnixNano ( ) ) ) ) )
2022-05-02 01:51:38 +00:00
}
p . log . Tracef ( "Detected user filter is %s" , filter )
return filter
}
2022-12-21 10:31:21 +00:00
func ( p * LDAPUserProvider ) resolveGroupsFilter ( input string , profile * ldapUserProfile ) ( filter string ) {
2022-05-02 01:51:38 +00:00
filter = p . config . GroupsFilter
2020-03-30 22:36:04 +00:00
2021-08-05 04:17:07 +00:00
if p . groupsFilterReplacementInput {
// The {input} placeholder is replaced by the users username input.
2022-12-21 10:31:21 +00:00
filter = strings . ReplaceAll ( p . config . GroupsFilter , ldapPlaceholderInput , ldapEscape ( input ) )
2021-08-05 04:17:07 +00:00
}
2020-05-05 19:35:32 +00:00
2020-03-30 22:36:04 +00:00
if profile != nil {
2021-08-05 04:17:07 +00:00
if p . groupsFilterReplacementUsername {
filter = strings . ReplaceAll ( filter , ldapPlaceholderUsername , ldap . EscapeFilter ( profile . Username ) )
}
if p . groupsFilterReplacementDN {
filter = strings . ReplaceAll ( filter , ldapPlaceholderDistinguishedName , ldap . EscapeFilter ( profile . DN ) )
}
2019-04-24 21:52:08 +00:00
}
2020-03-30 22:36:04 +00:00
2023-05-07 13:52:10 +00:00
if p . groupsFilterReplacementsMemberOfDN {
sep := fmt . Sprintf ( ")(%s=" , p . config . Attributes . DistinguishedName )
values := make ( [ ] string , len ( profile . MemberOf ) )
for i , memberof := range profile . MemberOf {
values [ i ] = ldap . EscapeFilter ( memberof )
}
filter = strings . ReplaceAll ( filter , ldapPlaceholderMemberOfDistinguishedName , fmt . Sprintf ( "(%s=%s)" , p . config . Attributes . DistinguishedName , strings . Join ( values , sep ) ) )
}
if p . groupsFilterReplacementsMemberOfRDN {
values := make ( [ ] string , len ( profile . MemberOf ) )
for i , memberof := range profile . MemberOf {
values [ i ] = ldap . EscapeFilter ( strings . SplitN ( memberof , "," , 2 ) [ 0 ] )
}
filter = strings . ReplaceAll ( filter , ldapPlaceholderMemberOfRelativeDistinguishedName , fmt . Sprintf ( "(%s)" , strings . Join ( values , ")(" ) ) )
}
2021-11-23 09:45:38 +00:00
p . log . Tracef ( "Computed groups filter is %s" , filter )
2021-04-12 01:10:50 +00:00
2022-10-28 09:21:43 +00:00
return filter
2019-04-24 21:52:08 +00:00
}
2022-05-10 04:38:36 +00:00
func ( p * LDAPUserProvider ) modify ( client LDAPClient , modifyRequest * ldap . ModifyRequest ) ( err error ) {
if err = client . Modify ( modifyRequest ) ; err != nil {
2022-05-02 01:51:38 +00:00
var (
referral string
ok bool
)
2019-04-24 21:52:08 +00:00
2022-05-02 01:51:38 +00:00
if referral , ok = p . getReferral ( err ) ; ! ok {
return err
}
2020-03-30 22:36:04 +00:00
2022-05-02 01:51:38 +00:00
p . log . Debugf ( "Attempting Modify on referred URL %s" , referral )
2020-05-05 19:35:32 +00:00
2022-05-02 01:51:38 +00:00
var (
2022-05-10 04:38:36 +00:00
clientRef LDAPClient
errRef error
2022-05-02 01:51:38 +00:00
)
2019-04-24 21:52:08 +00:00
2022-05-10 04:38:36 +00:00
if clientRef , errRef = p . connectCustom ( referral , p . config . User , p . config . Password , p . config . StartTLS , p . dialOpts ... ) ; errRef != nil {
return fmt . Errorf ( "error occurred connecting to referred LDAP server '%s': %+v. Original Error: %w" , referral , errRef , err )
2022-05-02 01:51:38 +00:00
}
2019-04-24 21:52:08 +00:00
2022-05-10 04:38:36 +00:00
defer clientRef . Close ( )
2020-05-05 19:35:32 +00:00
2022-05-10 04:38:36 +00:00
if errRef = clientRef . Modify ( modifyRequest ) ; errRef != nil {
return fmt . Errorf ( "error occurred performing modify on referred LDAP server '%s': %+v. Original Error: %w" , referral , errRef , err )
2020-02-27 22:21:07 +00:00
}
2022-05-10 04:38:36 +00:00
return nil
2019-04-24 21:52:08 +00:00
}
2022-05-10 04:38:36 +00:00
return nil
2019-04-24 21:52:08 +00:00
}
2022-05-10 04:38:36 +00:00
func ( p * LDAPUserProvider ) pwdModify ( client LDAPClient , pwdModifyRequest * ldap . PasswordModifyRequest ) ( err error ) {
if _ , err = client . PasswordModify ( pwdModifyRequest ) ; err != nil {
2022-05-02 01:51:38 +00:00
var (
referral string
ok bool
)
2019-04-24 21:52:08 +00:00
2022-05-02 01:51:38 +00:00
if referral , ok = p . getReferral ( err ) ; ! ok {
return err
}
2019-04-24 21:52:08 +00:00
2022-05-02 01:51:38 +00:00
p . log . Debugf ( "Attempting PwdModify ExOp (1.3.6.1.4.1.4203.1.11.1) on referred URL %s" , referral )
2019-04-24 21:52:08 +00:00
2022-05-02 01:51:38 +00:00
var (
2022-05-10 04:38:36 +00:00
clientRef LDAPClient
errRef error
2021-07-06 09:13:17 +00:00
)
2022-05-10 04:38:36 +00:00
if clientRef , errRef = p . connectCustom ( referral , p . config . User , p . config . Password , p . config . StartTLS , p . dialOpts ... ) ; errRef != nil {
return fmt . Errorf ( "error occurred connecting to referred LDAP server '%s': %+v. Original Error: %w" , referral , errRef , err )
2022-05-02 01:51:38 +00:00
}
2022-05-10 04:38:36 +00:00
defer clientRef . Close ( )
2019-04-24 21:52:08 +00:00
2022-05-10 04:38:36 +00:00
if _ , errRef = clientRef . PasswordModify ( pwdModifyRequest ) ; errRef != nil {
return fmt . Errorf ( "error occurred performing password modify on referred LDAP server '%s': %+v. Original Error: %w" , referral , errRef , err )
2022-05-02 01:51:38 +00:00
}
2022-05-10 04:38:36 +00:00
return nil
2021-07-06 09:13:17 +00:00
}
2019-04-24 21:52:08 +00:00
2022-05-10 04:38:36 +00:00
return nil
2022-05-02 01:51:38 +00:00
}
func ( p * LDAPUserProvider ) getReferral ( err error ) ( referral string , ok bool ) {
if ! p . config . PermitReferrals {
return "" , false
2019-04-24 21:52:08 +00:00
}
2022-05-02 01:51:38 +00:00
return ldapGetReferral ( err )
2019-04-24 21:52:08 +00:00
}