diff --git a/config.template.yml b/config.template.yml index 8bef04b69..846f7a8c8 100644 --- a/config.template.yml +++ b/config.template.yml @@ -33,8 +33,9 @@ ldap: # The groups filter used for retrieving groups of a given user. # {0} is a matcher replaced by username. - # 'member=cn={0},,' by default. - groups_filter: (&(member=cn={0},ou=users,dc=example,dc=com)(objectclass=groupOfNames)) + # {dn} is a matcher replaced by user DN. + # 'member={dn}' by default. + groups_filter: (&(member={dn})(objectclass=groupOfNames)) # The attribute holding the name of the group group_name_attribute: cn diff --git a/config.test.yml b/config.test.yml index 034d12ad0..d2a9b7a46 100644 --- a/config.test.yml +++ b/config.test.yml @@ -33,8 +33,9 @@ ldap: # The groups filter used for retrieving groups of a given user. # {0} is a matcher replaced by username. - # 'member=cn={0},,' by default. - groups_filter: (&(member=cn={0},ou=users,dc=example,dc=com)(objectclass=groupOfNames)) + # {dn} is a matcher replaced by user DN. + # 'member={dn}' by default. + groups_filter: (&(member={dn})(objectclass=groupOfNames)) # The attribute holding the name of the group group_name_attribute: cn diff --git a/server/src/lib/configuration/ConfigurationParser.ts b/server/src/lib/configuration/ConfigurationParser.ts index 32f5acf53..bbbe9acf1 100644 --- a/server/src/lib/configuration/ConfigurationParser.ts +++ b/server/src/lib/configuration/ConfigurationParser.ts @@ -29,10 +29,7 @@ function ensure_key_existence(config: object, path: string): void { function adaptLdapConfiguration(userConfig: UserLdapConfiguration): LdapConfiguration { const DEFAULT_USERS_FILTER = "cn={0}"; - const DEFAULT_GROUPS_FILTER = - userConfig.additional_users_dn - ? Util.format("member=cn={0},%s,%s", userConfig.additional_groups_dn, userConfig.base_dn) - : Util.format("member=cn={0},%s", userConfig.base_dn); + const DEFAULT_GROUPS_FILTER = "member={dn}"; const DEFAULT_GROUP_NAME_ATTRIBUTE = "cn"; const DEFAULT_MAIL_ATTRIBUTE = "mail"; diff --git a/server/src/lib/ldap/Client.ts b/server/src/lib/ldap/Client.ts index c391bbde2..e71a82d0c 100644 --- a/server/src/lib/ldap/Client.ts +++ b/server/src/lib/ldap/Client.ts @@ -47,18 +47,34 @@ export class Client implements IClient { }); } + private createGroupsFilter(userGroupsFilter: string, username: string): BluebirdPromise { + if (userGroupsFilter.indexOf("{0}") > 0) { + return BluebirdPromise.resolve(userGroupsFilter.replace("{0}", username)); + } + else if (userGroupsFilter.indexOf("{dn}") > 0) { + return this.searchUserDn(username) + .then(function (userDN: string) { + return BluebirdPromise.resolve(userGroupsFilter.replace("{dn}", userDN)); + }); + } + return BluebirdPromise.resolve(userGroupsFilter); + } + searchGroups(username: string): BluebirdPromise { const that = this; - const filter = that.options.groups_filter.replace("{0}", username); - const query = { - scope: "sub", - attributes: [that.options.group_name_attribute], - filter: filter - }; - return this.ldapClient.searchAsync(that.options.groups_dn, query) + return this.createGroupsFilter(this.options.groups_filter, username) + .then(function (groupsFilter: string) { + that.logger.debug("Computed groups filter is %s", groupsFilter); + const query = { + scope: "sub", + attributes: [that.options.group_name_attribute], + filter: groupsFilter + }; + return that.ldapClient.searchAsync(that.options.groups_dn, query); + }) .then(function (docs: { cn: string }[]) { const groups = docs.map((doc: any) => { return doc.cn; }); - that.logger.debug("LDAP: groups of user %s are %s", username, groups); + that.logger.debug("LDAP: groups of user %s are [%s]", username, groups.join(",")); return BluebirdPromise.resolve(groups); }); } @@ -66,6 +82,7 @@ export class Client implements IClient { searchUserDn(username: 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, diff --git a/server/src/lib/routes/verify/get.ts b/server/src/lib/routes/verify/get.ts index ccf21be0c..87c40f891 100644 --- a/server/src/lib/routes/verify/get.ts +++ b/server/src/lib/routes/verify/get.ts @@ -45,7 +45,7 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro const isAllowed = accessController.isAccessAllowed(domain, path, username, groups); if (!isAllowed) return BluebirdPromise.reject( - new exceptions.DomainAccessDenied(Util.format("User '%s' does not have access to '%'", + new exceptions.DomainAccessDenied(Util.format("User '%s' does not have access to '%s'", username, domain))); if (authenticationMethod == "two_factor" && !authSession.second_factor) diff --git a/server/test/configuration/LdapConfigurationAdaptation.test.ts b/server/test/configuration/LdapConfigurationAdaptation.test.ts index 59aab2537..2d91a4ba9 100644 --- a/server/test/configuration/LdapConfigurationAdaptation.test.ts +++ b/server/test/configuration/LdapConfigurationAdaptation.test.ts @@ -56,7 +56,7 @@ describe("test ldap configuration adaptation", function () { users_dn: "dc=example,dc=com", users_filter: "cn={0}", groups_dn: "dc=example,dc=com", - groups_filter: "member=cn={0},dc=example,dc=com", + groups_filter: "member={dn}", group_name_attribute: "cn", mail_attribute: "mail", user: "admin", diff --git a/server/test/ldap/Client.test.ts b/server/test/ldap/Client.test.ts index d69cec019..d2f9c4b1e 100644 --- a/server/test/ldap/Client.test.ts +++ b/server/test/ldap/Client.test.ts @@ -31,9 +31,9 @@ describe("test authelia ldap client", function () { const ldapClient = new LdapClientStub(); factory.createStub.returns(ldapClient); - ldapClient.searchAsyncStub.returns(BluebirdPromise.resolve([ - "group1" - ])); + ldapClient.searchAsyncStub.returns(BluebirdPromise.resolve([{ + cn: "group1" + }])); const client = new Client(ADMIN_USER_DN, ADMIN_PASSWORD, options, factory, Dovehash, Winston); return client.searchGroups("user1") @@ -42,4 +42,49 @@ describe("test authelia ldap client", function () { "member=cn=user1,ou=users,dc=example,dc=com"); }); }); + + it("should replace {dn} by user DN when searching for groups in LDAP", function () { + const USER_DN = "cn=user1,ou=users,dc=example,dc=com"; + const options: LdapConfiguration = { + url: "ldap://ldap", + users_dn: "ou=users,dc=example,dc=com", + users_filter: "cn={0}", + groups_dn: "ou=groups,dc=example,dc=com", + groups_filter: "member={dn}", + group_name_attribute: "cn", + mail_attribute: "mail", + user: "cn=admin,dc=example,dc=com", + password: "password" + }; + const factory = new LdapClientFactoryStub(); + const ldapClient = new LdapClientStub(); + + factory.createStub.returns(ldapClient); + + // Retrieve user DN + ldapClient.searchAsyncStub.withArgs("ou=users,dc=example,dc=com", { + scope: "sub", + sizeLimit: 1, + attributes: ["dn"], + filter: "cn=user1" + }).returns(BluebirdPromise.resolve([{ + dn: USER_DN + }])); + + // Retrieve groups + ldapClient.searchAsyncStub.withArgs("ou=groups,dc=example,dc=com", { + scope: "sub", + attributes: ["cn"], + filter: "member=" + USER_DN + }).returns(BluebirdPromise.resolve([{ + cn: "group1" + }])); + + const client = new Client(ADMIN_USER_DN, ADMIN_PASSWORD, options, factory, Dovehash, Winston); + + return client.searchGroups("user1") + .then(function (groups: string[]) { + Assert.deepEqual(groups, ["group1"]); + }); + }); }); \ No newline at end of file