From 264a94d4e72efc4607cbb0c8be059798c525db94 Mon Sep 17 00:00:00 2001 From: ViViDboarder Date: Wed, 27 Feb 2019 21:34:18 -0800 Subject: [PATCH] Add ability to search for groups using {uid} On some LDAP servers, the `uid` attribute is more like a guid, while the username exists instead in a dedicated field, like `username`. This means the `uid` is not necessarily equal to `username`. This is allows referencing using the `uid` to search for groups in the same way as `dn` so that one can explicitly match the `memberuid` to the `uid` for the user without the assumptions that come with using `{0}`. --- config.template.yml | 1 + example/kube/authelia/configs/config.yml | 1 + .../backends/ldap/Session.spec.ts | 43 +++++++++++++++++++ .../authentication/backends/ldap/Session.ts | 28 +++++++++--- 4 files changed, 66 insertions(+), 7 deletions(-) diff --git a/config.template.yml b/config.template.yml index 2bfcb28e6..30eb7be60 100644 --- a/config.template.yml +++ b/config.template.yml @@ -61,6 +61,7 @@ authentication_backend: # The groups filter used for retrieving groups of a given user. # {0} is a matcher replaced by username. # {dn} is a matcher replaced by user DN. + # {uid} is a matcher replaced by user uid. # 'member={dn}' by default. groups_filter: (&(member={dn})(objectclass=groupOfNames)) diff --git a/example/kube/authelia/configs/config.yml b/example/kube/authelia/configs/config.yml index 8230fc0a9..e715a14c8 100644 --- a/example/kube/authelia/configs/config.yml +++ b/example/kube/authelia/configs/config.yml @@ -54,6 +54,7 @@ authentication_backend: # The groups filter used for retrieving groups of a given user. # {0} is a matcher replaced by username. # {dn} is a matcher replaced by user DN. + # {uid} is a matcher replaced by user uid. # 'member={dn}' by default. groups_filter: (&(member={dn})(objectclass=groupOfNames)) diff --git a/server/src/lib/authentication/backends/ldap/Session.spec.ts b/server/src/lib/authentication/backends/ldap/Session.spec.ts index d55f6a805..ce11c5ab8 100644 --- a/server/src/lib/authentication/backends/ldap/Session.spec.ts +++ b/server/src/lib/authentication/backends/ldap/Session.spec.ts @@ -83,6 +83,49 @@ describe("ldap/Session", function () { }); }); + it("should replace {uid} by user uid when searching for groups in LDAP", function () { + const USER_UID = "user1"; + const options: LdapConfiguration = { + url: "ldap://ldap", + additional_users_dn: "ou=users", + additional_groups_dn: "ou=groups", + base_dn: "dc=example,dc=com", + users_filter: "cn={0}", + groups_filter: "member=cn={uid},ou=users,dc=example,dc=com", + group_name_attribute: "cn", + mail_attribute: "mail", + user: "cn=admin,dc=example,dc=com", + password: "password" + }; + const ldapClient = new ConnectorStub(); + + // Retrieve user DN + ldapClient.searchAsyncStub.withArgs("ou=users,dc=example,dc=com", { + scope: "sub", + sizeLimit: 1, + attributes: ["uid"], + filter: "cn=user1" + }).returns(BluebirdPromise.resolve([{ + uid: USER_UID + }])); + + // Retrieve groups + ldapClient.searchAsyncStub.withArgs("ou=groups,dc=example,dc=com", { + scope: "sub", + attributes: ["cn"], + filter: "member=cn=user1,ou=users,dc=example,dc=com" + }).returns(BluebirdPromise.resolve([{ + cn: "group1" + }])); + + const client = new Session(ADMIN_USER_DN, ADMIN_PASSWORD, options, ldapClient, Winston); + + return client.searchGroups("user1") + .then(function (groups: string[]) { + Assert.deepEqual(groups, ["group1"]); + }); + }); + it("should retrieve mail from custom attribute", function () { const USER_DN = "cn=user1,ou=users,dc=example,dc=com"; const options: LdapConfiguration = { diff --git a/server/src/lib/authentication/backends/ldap/Session.ts b/server/src/lib/authentication/backends/ldap/Session.ts index e0284b3c4..3f37b226f 100644 --- a/server/src/lib/authentication/backends/ldap/Session.ts +++ b/server/src/lib/authentication/backends/ldap/Session.ts @@ -61,6 +61,12 @@ export class Session implements ISession { return BluebirdPromise.resolve(userGroupsFilter.replace("{dn}", userDN)); }); } + else if (userGroupsFilter.indexOf("{uid}") > 0) { + return this.searchUserUid(username) + .then(function (userUid: string) { + return BluebirdPromise.resolve(userGroupsFilter.replace("{uid}", userUid)); + }); + } return BluebirdPromise.resolve(userGroupsFilter); } @@ -83,29 +89,37 @@ export class Session implements ISession { }); } - searchUserDn(username: string): BluebirdPromise { + searchUserAttribute(username: string, attribute: string): BluebirdPromise { const that = this; const filter = this.options.users_filter.replace("{0}", username); this.logger.debug("Computed users filter is %s", filter); const query = { scope: "sub", sizeLimit: 1, - attributes: ["dn"], + attributes: [attribute], filter: filter }; - that.logger.debug("LDAP: searching for user dn of %s", username); + that.logger.debug("LDAP: searching for user %s of %s", attribute, username); return that.connector.searchAsync(this.usersSearchBase, query) - .then(function (users: { dn: string }[]) { + .then(function (users: { [attribute: string]: string }[]) { if (users.length > 0) { - that.logger.debug("LDAP: retrieved user dn is %s", users[0].dn); - return BluebirdPromise.resolve(users[0].dn); + that.logger.debug("LDAP: retrieved user %s is %s", attribute, users[0][attribute]); + return BluebirdPromise.resolve(users[0][attribute]); } return BluebirdPromise.reject(new Error( - Util.format("No user DN found for user '%s'", username))); + Util.format("No user %s found for user '%s'", attribute, username))); }); } + searchUserDn(username: string): BluebirdPromise { + return this.searchUserAttribute(username, "dn"); + } + + searchUserUid(username: string): BluebirdPromise { + return this.searchUserAttribute(username, "uid"); + } + searchEmails(username: string): BluebirdPromise { const that = this; const query = {