1
0
Fork 0
mirror of https://github.com/Luzifer/nginx-sso.git synced 2024-12-20 12:51:17 +00:00

Add configurable username to LDAP auth

closes #4

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2018-06-13 16:23:12 +02:00
parent bc6ed4ee08
commit b7038a312e
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
3 changed files with 34 additions and 13 deletions

View file

@ -179,6 +179,9 @@ providers:
group_search_base: "ou=groups,dc=example,dc=com" group_search_base: "ou=groups,dc=example,dc=com"
# Optional, defaults to '(|(member={0})(uniqueMember={0}))' # Optional, defaults to '(|(member={0})(uniqueMember={0}))'
group_membership_filter: "" group_membership_filter: ""
# Replace DN as the username with another attribute
# Optional, defaults to "dn"
username_attribute: "uid"
``` ```
To use this provider you need to have a LDAP server set up and filled with users. The example (and default) config above assumes each of your users carries an `uid` attribute and groups does contains `member` or `uniqueMember` attributes. Inside the groups full DNs are expected. For the ACL also full DNs are used. To use this provider you need to have a LDAP server set up and filled with users. The example (and default) config above assumes each of your users carries an `uid` attribute and groups does contains `member` or `uniqueMember` attributes. Inside the groups full DNs are expected. For the ACL also full DNs are used.
@ -192,6 +195,7 @@ To use this provider you need to have a LDAP server set up and filled with users
- `user_search_filter` - optional - The query to issue to find the user from its `uid` (`{0}` is replaced with the `uid`). If unset the query `(uid={0})` is used - `user_search_filter` - optional - The query to issue to find the user from its `uid` (`{0}` is replaced with the `uid`). If unset the query `(uid={0})` is used
- `group_search_base` - optional - Like the `user_search_base` this limits the sub-tree where to search for groups, also defaults to `root_dn` - `group_search_base` - optional - Like the `user_search_base` this limits the sub-tree where to search for groups, also defaults to `root_dn`
- `group_membership_filter` - optional - The query to issue to list all groups the user is a member of. The DN of each group is used as the group name. If unset the query `(|(member={0})(uniqueMember={0}))` is used - `group_membership_filter` - optional - The query to issue to list all groups the user is a member of. The DN of each group is used as the group name. If unset the query `(|(member={0})(uniqueMember={0}))` is used
- `username_attribute` - optional - The attribute containing the username returned to nginx instead of the dn. If unset the `dn` is used
When using the LDAP provider you need to pay attention when writing your ACL. As DNs are used as names for users and groups you also need to specify those in the ACL: When using the LDAP provider you need to pay attention when writing your ACL. As DNs are used as names for users and groups you also need to specify those in the ACL:

View file

@ -24,6 +24,7 @@ type authLDAP struct {
Server string `yaml:"server"` Server string `yaml:"server"`
UserSearchBase string `yaml:"user_search_base"` UserSearchBase string `yaml:"user_search_base"`
UserSearchFilter string `yaml:"user_search_filter"` UserSearchFilter string `yaml:"user_search_filter"`
UsernameAttribute string `yaml:"username_attribute"`
} }
// AuthenticatorID needs to return an unique string to identify // AuthenticatorID needs to return an unique string to identify
@ -58,6 +59,7 @@ func (a *authLDAP) Configure(yamlSource []byte) error {
a.Server = envelope.Providers.LDAP.Server a.Server = envelope.Providers.LDAP.Server
a.UserSearchBase = envelope.Providers.LDAP.UserSearchBase a.UserSearchBase = envelope.Providers.LDAP.UserSearchBase
a.UserSearchFilter = envelope.Providers.LDAP.UserSearchFilter a.UserSearchFilter = envelope.Providers.LDAP.UserSearchFilter
a.UsernameAttribute = envelope.Providers.LDAP.UsernameAttribute
// Set defaults // Set defaults
if a.UserSearchFilter == "" { if a.UserSearchFilter == "" {
@ -74,6 +76,10 @@ func (a *authLDAP) Configure(yamlSource []byte) error {
a.GroupSearchBase = a.RootDN a.GroupSearchBase = a.RootDN
} }
if a.UsernameAttribute == "" {
a.UsernameAttribute = "dn"
}
return nil return nil
} }
@ -82,14 +88,15 @@ func (a *authLDAP) Configure(yamlSource []byte) error {
// If no user was detected the errNoValidUserFound needs to be // If no user was detected the errNoValidUserFound needs to be
// returned // returned
func (a authLDAP) DetectUser(res http.ResponseWriter, r *http.Request) (string, []string, error) { func (a authLDAP) DetectUser(res http.ResponseWriter, r *http.Request) (string, []string, error) {
var user string var alias, user string
if a.EnableBasicAuth { if a.EnableBasicAuth {
if basicUser, basicPass, ok := r.BasicAuth(); ok { if basicUser, basicPass, ok := r.BasicAuth(); ok {
if userDN, err := a.checkLogin(basicUser, basicPass); err != nil { if userDN, a, err := a.checkLogin(basicUser, basicPass, a.UsernameAttribute); err != nil {
return "", nil, err return "", nil, err
} else { } else {
user = userDN user = userDN
alias = a
} }
} }
} }
@ -101,8 +108,12 @@ func (a authLDAP) DetectUser(res http.ResponseWriter, r *http.Request) (string,
} }
var ok bool var ok bool
user, ok = sess.Values["user"].(string) if user, ok = sess.Values["user"].(string); !ok {
if !ok { return "", nil, errNoValidUserFound
}
if alias, ok = sess.Values["alias"].(string); !ok {
// Most likely an old cookie, force re-login
return "", nil, errNoValidUserFound return "", nil, errNoValidUserFound
} }
@ -114,7 +125,8 @@ func (a authLDAP) DetectUser(res http.ResponseWriter, r *http.Request) (string,
} }
groups, err := a.getUserGroups(user) groups, err := a.getUserGroups(user)
return user, groups, err
return alias, groups, err
} }
// Login is called when the user submits the login form and needs // Login is called when the user submits the login form and needs
@ -129,16 +141,18 @@ func (a authLDAP) Login(res http.ResponseWriter, r *http.Request) error {
var ( var (
userDN string userDN string
alias string
err error err error
) )
if userDN, err = a.checkLogin(username, password); err != nil { if userDN, alias, err = a.checkLogin(username, password, a.UsernameAttribute); err != nil {
return err return err
} }
sess, _ := cookieStore.Get(r, strings.Join([]string{mainCfg.Cookie.Prefix, a.AuthenticatorID()}, "-")) sess, _ := cookieStore.Get(r, strings.Join([]string{mainCfg.Cookie.Prefix, a.AuthenticatorID()}, "-"))
sess.Options = mainCfg.GetSessionOpts() sess.Options = mainCfg.GetSessionOpts()
sess.Values["user"] = userDN sess.Values["user"] = userDN
sess.Values["alias"] = alias
return sess.Save(r, res) return sess.Save(r, res)
} }
@ -173,10 +187,10 @@ func (a authLDAP) Logout(res http.ResponseWriter, r *http.Request) (err error) {
// checkLogin searches for the username using the specified UserSearchFilter // checkLogin searches for the username using the specified UserSearchFilter
// and returns the UserDN and an error (errNoValidUserFound / processing error) // and returns the UserDN and an error (errNoValidUserFound / processing error)
func (a authLDAP) checkLogin(username, password string) (string, error) { func (a authLDAP) checkLogin(username, password, aliasAttribute string) (string, string, error) {
l, err := a.dial() l, err := a.dial()
if err != nil { if err != nil {
return "", err return "", "", err
} }
defer l.Close() defer l.Close()
@ -186,26 +200,26 @@ func (a authLDAP) checkLogin(username, password string) (string, error) {
ldap.NeverDerefAliases, ldap.NeverDerefAliases,
0, 0, false, 0, 0, false,
strings.Replace(a.UserSearchFilter, `{0}`, username, -1), strings.Replace(a.UserSearchFilter, `{0}`, username, -1),
[]string{"dn"}, []string{"dn", aliasAttribute},
nil, nil,
) )
sres, err := l.Search(sreq) sres, err := l.Search(sreq)
if err != nil { if err != nil {
return "", fmt.Errorf("Unable to search for user: %s", err) return "", "", fmt.Errorf("Unable to search for user: %s", err)
} }
if len(sres.Entries) != 1 { if len(sres.Entries) != 1 {
return "", errNoValidUserFound return "", "", errNoValidUserFound
} }
userDN := sres.Entries[0].DN userDN := sres.Entries[0].DN
if err := l.Bind(userDN, password); err != nil { if err := l.Bind(userDN, password); err != nil {
return "", errNoValidUserFound return "", "", errNoValidUserFound
} }
return userDN, nil return userDN, sres.Entries[0].GetAttributeValue(aliasAttribute), nil
} }
// dial connects to the LDAP server and authenticates using manager_dn // dial connects to the LDAP server and authenticates using manager_dn

View file

@ -52,6 +52,9 @@ providers:
group_search_base: "ou=groups,dc=example,dc=com" group_search_base: "ou=groups,dc=example,dc=com"
# Optional, defaults to '(|(member={0})(uniqueMember={0}))' # Optional, defaults to '(|(member={0})(uniqueMember={0}))'
group_membership_filter: "" group_membership_filter: ""
# Replace DN as the username with another attribute
# Optional, defaults to "dn"
username_attribute: "uid"
# Authentication against embedded user database # Authentication against embedded user database
# Supports: Users, Groups # Supports: Users, Groups