connector: add a group interface and an LDAP implementation

This commit is contained in:
Eric Chiang 2016-07-15 16:00:21 -07:00
parent af6aade6d0
commit 731dadb29d
2 changed files with 70 additions and 27 deletions

View file

@ -112,6 +112,7 @@ type LDAPConnector struct {
searchScope int searchScope int
searchBindDN string searchBindDN string
searchBindPw string searchBindPw string
searchGroupFilter string
bindTemplate string bindTemplate string
@ -212,6 +213,7 @@ func (cfg *LDAPConnectorConfig) Connector(ns url.URL, lf oidc.LoginFunc, tpls *t
emailAttribute: cfg.EmailAttribute, emailAttribute: cfg.EmailAttribute,
searchBeforeAuth: cfg.SearchBeforeAuth, searchBeforeAuth: cfg.SearchBeforeAuth,
searchFilter: cfg.SearchFilter, searchFilter: cfg.SearchFilter,
searchGroupFilter: cfg.SearchGroupFilter,
searchScope: searchScope, searchScope: searchScope,
searchBindDN: cfg.SearchBindDN, searchBindDN: cfg.SearchBindDN,
searchBindPw: cfg.SearchBindPw, searchBindPw: cfg.SearchBindPw,
@ -433,12 +435,47 @@ func invalidBindCredentials(err error) bool {
func (c *LDAPConnector) formatDN(template, username string) string { func (c *LDAPConnector) formatDN(template, username string) string {
result := template result := template
result = strings.Replace(result, "%u", username, -1) result = strings.Replace(result, "%u", ldap.EscapeFilter(username), -1)
result = strings.Replace(result, "%b", c.baseDN, -1) result = strings.Replace(result, "%b", c.baseDN, -1)
return result return result
} }
func (c *LDAPConnector) Groups(fullUserID string) ([]string, error) {
if !c.searchBeforeAuth {
return nil, fmt.Errorf("cannot search without service account")
}
if c.searchGroupFilter == "" {
return nil, fmt.Errorf("no group filter specified")
}
var groups []string
err := c.ldapPool.Do(func(conn *ldap.Conn) error {
if err := conn.Bind(c.searchBindDN, c.searchBindPw); err != nil {
if !invalidBindCredentials(err) {
log.Errorf("failed to connect to LDAP for search bind: %v", err)
}
return fmt.Errorf("failed to bind: %v", err)
}
req := &ldap.SearchRequest{
BaseDN: c.baseDN,
Scope: c.searchScope,
Filter: c.formatDN(c.searchGroupFilter, fullUserID),
}
resp, err := conn.Search(req)
if err != nil {
return fmt.Errorf("search failed: %v", err)
}
groups = make([]string, len(resp.Entries))
for i, entry := range resp.Entries {
groups[i] = entry.DN
}
return nil
})
return groups, err
}
func (c *LDAPConnector) Identity(username, password string) (*oidc.Identity, error) { func (c *LDAPConnector) Identity(username, password string) (*oidc.Identity, error) {
var ( var (
identity *oidc.Identity identity *oidc.Identity
@ -447,8 +484,10 @@ func (c *LDAPConnector) Identity(username, password string) (*oidc.Identity, err
if c.searchBeforeAuth { if c.searchBeforeAuth {
err = c.ldapPool.Do(func(conn *ldap.Conn) error { err = c.ldapPool.Do(func(conn *ldap.Conn) error {
if err := conn.Bind(c.searchBindDN, c.searchBindPw); err != nil { if err := conn.Bind(c.searchBindDN, c.searchBindPw); err != nil {
// Don't wrap error as it may be a specific LDAP error. if !invalidBindCredentials(err) {
return err log.Errorf("failed to connect to LDAP for search bind: %v", err)
}
return fmt.Errorf("failed to bind: %v", err)
} }
filter := c.formatDN(c.searchFilter, username) filter := c.formatDN(c.searchFilter, username)
@ -491,8 +530,10 @@ func (c *LDAPConnector) Identity(username, password string) (*oidc.Identity, err
err = c.ldapPool.Do(func(conn *ldap.Conn) error { err = c.ldapPool.Do(func(conn *ldap.Conn) error {
userBindDN := c.formatDN(c.bindTemplate, username) userBindDN := c.formatDN(c.bindTemplate, username)
if err := conn.Bind(userBindDN, password); err != nil { if err := conn.Bind(userBindDN, password); err != nil {
// Don't wrap error as it may be a specific LDAP error. if !invalidBindCredentials(err) {
return err log.Errorf("failed to connect to LDAP for search bind: %v", err)
}
return fmt.Errorf("failed to bind: %v", err)
} }
req := &ldap.SearchRequest{ req := &ldap.SearchRequest{
@ -522,11 +563,7 @@ func (c *LDAPConnector) Identity(username, password string) (*oidc.Identity, err
return nil return nil
}) })
} }
if err != nil { if err != nil {
if !invalidBindCredentials(err) {
log.Errorf("failed to connect to LDAP for search bind: %v", err)
}
return nil, err return nil, err
} }
return identity, nil return identity, nil

View file

@ -60,6 +60,12 @@ type ConnectorConfig interface {
Connector(ns url.URL, loginFunc oidc.LoginFunc, tpls *template.Template) (Connector, error) Connector(ns url.URL, loginFunc oidc.LoginFunc, tpls *template.Template) (Connector, error)
} }
// GroupsConnector is a strategy for mapping a user to a set of groups. This is optionally
// implemented by some connectors.
type GroupsConnector interface {
Groups(fullUserID string) ([]string, error)
}
type ConnectorConfigRepo interface { type ConnectorConfigRepo interface {
All() ([]ConnectorConfig, error) All() ([]ConnectorConfig, error)
GetConnectorByID(repo.Transaction, string) (ConnectorConfig, error) GetConnectorByID(repo.Transaction, string) (ConnectorConfig, error)