From e20a795a2a4cdccfc9da9051f36e765ef8ee5d0e Mon Sep 17 00:00:00 2001 From: Vitaliy Dmitriev Date: Tue, 7 Jan 2020 14:55:13 +0100 Subject: [PATCH] connector/ldap: backward compatibility with single user to group mapping Signed-off-by: Vitaliy Dmitriev --- connector/ldap/ldap.go | 27 +++++++++- connector/ldap/ldap_test.go | 103 ++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/connector/ldap/ldap.go b/connector/ldap/ldap.go index 963aaa3d..53f9062f 100644 --- a/connector/ldap/ldap.go +++ b/connector/ldap/ldap.go @@ -134,6 +134,12 @@ type Config struct { Scope string `json:"scope"` // Defaults to "sub" + // DEPRECATED config options. Those are left for backward compatibility. + // See "UserMatchers" below for the current group to user matching implementation + // TODO: should be eventually removed from the code + UserAttr string `json:"userAttr"` + GroupAttr string `json:"groupAttr"` + // Array of the field pairs used to match a user to a group. // See the "UserMatcher" struct for the exact field names // @@ -175,6 +181,23 @@ func parseScope(s string) (int, bool) { return 0, false } +// Build a list of group attr name to user attr value matchers. +// Function exists here to allow backward compatibility between old and new +// group to user matching implementations. +// See "Config.GroupSearch.UserMatchers" comments for the details +func (c *ldapConnector) userMatchers() []UserMatcher { + if len(c.GroupSearch.UserMatchers) > 0 && c.GroupSearch.UserMatchers[0].UserAttr != "" { + return c.GroupSearch.UserMatchers[:] + } else { + return []UserMatcher{ + { + UserAttr: c.GroupSearch.UserAttr, + GroupAttr: c.GroupSearch.GroupAttr, + }, + } + } +} + // Open returns an authentication strategy using LDAP. func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { conn, err := c.OpenConnector(logger) @@ -392,7 +415,7 @@ func (c *ldapConnector) userEntry(conn *ldap.Conn, username string) (user ldap.E }, } - for _, matcher := range c.GroupSearch.UserMatchers { + for _, matcher := range c.userMatchers() { req.Attributes = append(req.Attributes, matcher.UserAttr) } @@ -549,7 +572,7 @@ func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string, } var groups []*ldap.Entry - for _, matcher := range c.GroupSearch.UserMatchers { + for _, matcher := range c.userMatchers() { for _, attr := range getAttrs(user, matcher.UserAttr) { filter := fmt.Sprintf("(%s=%s)", matcher.GroupAttr, ldap.EscapeFilter(attr)) if c.GroupSearch.Filter != "" { diff --git a/connector/ldap/ldap_test.go b/connector/ldap/ldap_test.go index 4cb6611a..1dc26afd 100644 --- a/connector/ldap/ldap_test.go +++ b/connector/ldap/ldap_test.go @@ -676,6 +676,109 @@ memberUid: janedoe runTests(t, schema, connectLDAP, c, tests) } +// Test deprecated group to user matching implementation +// which was left for backward compatibility. +// See "Config.GroupSearch.UserMatchers" comments for the details +func TestDeprecatedGroupToUserMatcher(t *testing.T) { + schema := ` +dn: ou=People,dc=example,dc=org +objectClass: organizationalUnit +ou: People + +dn: cn=jane,ou=People,dc=example,dc=org +objectClass: person +objectClass: inetOrgPerson +sn: doe +cn: jane +mail: janedoe@example.com +userpassword: foo + +dn: cn=john,ou=People,dc=example,dc=org +objectClass: person +objectClass: inetOrgPerson +sn: doe +cn: john +mail: johndoe@example.com +userpassword: bar + +# Group definitions. + +dn: ou=Seattle,dc=example,dc=org +objectClass: organizationalUnit +ou: Seattle + +dn: ou=Portland,dc=example,dc=org +objectClass: organizationalUnit +ou: Portland + +dn: ou=Groups,ou=Seattle,dc=example,dc=org +objectClass: organizationalUnit +ou: Groups + +dn: ou=Groups,ou=Portland,dc=example,dc=org +objectClass: organizationalUnit +ou: Groups + +dn: cn=qa,ou=Groups,ou=Portland,dc=example,dc=org +objectClass: groupOfNames +cn: qa +member: cn=john,ou=People,dc=example,dc=org + +dn: cn=admins,ou=Groups,ou=Seattle,dc=example,dc=org +objectClass: groupOfNames +cn: admins +member: cn=john,ou=People,dc=example,dc=org +member: cn=jane,ou=People,dc=example,dc=org + +dn: cn=developers,ou=Groups,ou=Seattle,dc=example,dc=org +objectClass: groupOfNames +cn: developers +member: cn=jane,ou=People,dc=example,dc=org +` + c := &Config{} + c.UserSearch.BaseDN = "ou=People,dc=example,dc=org" + c.UserSearch.NameAttr = "cn" + c.UserSearch.EmailAttr = "mail" + c.UserSearch.IDAttr = "DN" + c.UserSearch.Username = "cn" + c.GroupSearch.BaseDN = "dc=example,dc=org" + c.GroupSearch.UserAttr = "DN" + c.GroupSearch.GroupAttr = "member" + c.GroupSearch.NameAttr = "cn" + c.GroupSearch.Filter = "(ou:dn:=Seattle)" // ignore other groups + + tests := []subtest{ + { + name: "validpassword", + username: "jane", + password: "foo", + groups: true, + want: connector.Identity{ + UserID: "cn=jane,ou=People,dc=example,dc=org", + Username: "jane", + Email: "janedoe@example.com", + EmailVerified: true, + Groups: []string{"admins", "developers"}, + }, + }, + { + name: "validpassword2", + username: "john", + password: "bar", + groups: true, + want: connector.Identity{ + UserID: "cn=john,ou=People,dc=example,dc=org", + Username: "john", + Email: "johndoe@example.com", + EmailVerified: true, + Groups: []string{"admins"}, + }, + }, + } + + runTests(t, schema, connectLDAP, c, tests) +} + func TestStartTLS(t *testing.T) { schema := ` dn: ou=People,dc=example,dc=org