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:
parent
bc6ed4ee08
commit
b7038a312e
3 changed files with 34 additions and 13 deletions
|
@ -179,6 +179,9 @@ providers:
|
|||
group_search_base: "ou=groups,dc=example,dc=com"
|
||||
# Optional, defaults to '(|(member={0})(uniqueMember={0}))'
|
||||
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.
|
||||
|
@ -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
|
||||
- `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
|
||||
- `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:
|
||||
|
||||
|
|
40
auth_ldap.go
40
auth_ldap.go
|
@ -24,6 +24,7 @@ type authLDAP struct {
|
|||
Server string `yaml:"server"`
|
||||
UserSearchBase string `yaml:"user_search_base"`
|
||||
UserSearchFilter string `yaml:"user_search_filter"`
|
||||
UsernameAttribute string `yaml:"username_attribute"`
|
||||
}
|
||||
|
||||
// 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.UserSearchBase = envelope.Providers.LDAP.UserSearchBase
|
||||
a.UserSearchFilter = envelope.Providers.LDAP.UserSearchFilter
|
||||
a.UsernameAttribute = envelope.Providers.LDAP.UsernameAttribute
|
||||
|
||||
// Set defaults
|
||||
if a.UserSearchFilter == "" {
|
||||
|
@ -74,6 +76,10 @@ func (a *authLDAP) Configure(yamlSource []byte) error {
|
|||
a.GroupSearchBase = a.RootDN
|
||||
}
|
||||
|
||||
if a.UsernameAttribute == "" {
|
||||
a.UsernameAttribute = "dn"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -82,14 +88,15 @@ func (a *authLDAP) Configure(yamlSource []byte) error {
|
|||
// If no user was detected the errNoValidUserFound needs to be
|
||||
// returned
|
||||
func (a authLDAP) DetectUser(res http.ResponseWriter, r *http.Request) (string, []string, error) {
|
||||
var user string
|
||||
var alias, user string
|
||||
|
||||
if a.EnableBasicAuth {
|
||||
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
|
||||
} else {
|
||||
user = userDN
|
||||
alias = a
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,8 +108,12 @@ func (a authLDAP) DetectUser(res http.ResponseWriter, r *http.Request) (string,
|
|||
}
|
||||
|
||||
var ok bool
|
||||
user, ok = sess.Values["user"].(string)
|
||||
if !ok {
|
||||
if user, ok = sess.Values["user"].(string); !ok {
|
||||
return "", nil, errNoValidUserFound
|
||||
}
|
||||
|
||||
if alias, ok = sess.Values["alias"].(string); !ok {
|
||||
// Most likely an old cookie, force re-login
|
||||
return "", nil, errNoValidUserFound
|
||||
}
|
||||
|
||||
|
@ -114,7 +125,8 @@ func (a authLDAP) DetectUser(res http.ResponseWriter, r *http.Request) (string,
|
|||
}
|
||||
|
||||
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
|
||||
|
@ -129,16 +141,18 @@ func (a authLDAP) Login(res http.ResponseWriter, r *http.Request) error {
|
|||
|
||||
var (
|
||||
userDN string
|
||||
alias string
|
||||
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
|
||||
}
|
||||
|
||||
sess, _ := cookieStore.Get(r, strings.Join([]string{mainCfg.Cookie.Prefix, a.AuthenticatorID()}, "-"))
|
||||
sess.Options = mainCfg.GetSessionOpts()
|
||||
sess.Values["user"] = userDN
|
||||
sess.Values["alias"] = alias
|
||||
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
|
||||
// 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()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
|
@ -186,26 +200,26 @@ func (a authLDAP) checkLogin(username, password string) (string, error) {
|
|||
ldap.NeverDerefAliases,
|
||||
0, 0, false,
|
||||
strings.Replace(a.UserSearchFilter, `{0}`, username, -1),
|
||||
[]string{"dn"},
|
||||
[]string{"dn", aliasAttribute},
|
||||
nil,
|
||||
)
|
||||
|
||||
sres, err := l.Search(sreq)
|
||||
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 {
|
||||
return "", errNoValidUserFound
|
||||
return "", "", errNoValidUserFound
|
||||
}
|
||||
|
||||
userDN := sres.Entries[0].DN
|
||||
|
||||
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
|
||||
|
|
|
@ -52,6 +52,9 @@ providers:
|
|||
group_search_base: "ou=groups,dc=example,dc=com"
|
||||
# Optional, defaults to '(|(member={0})(uniqueMember={0}))'
|
||||
group_membership_filter: ""
|
||||
# Replace DN as the username with another attribute
|
||||
# Optional, defaults to "dn"
|
||||
username_attribute: "uid"
|
||||
|
||||
# Authentication against embedded user database
|
||||
# Supports: Users, Groups
|
||||
|
|
Loading…
Reference in a new issue