Merge pull request #147 from clems4ever/userdn-ldap-filter

Add {dn} as an available matcher in LDAP groups filter
This commit is contained in:
Clément Michaud 2017-10-15 15:02:46 +02:00 committed by GitHub
commit 4b51ae30cc
7 changed files with 82 additions and 21 deletions

View File

@ -33,8 +33,9 @@ ldap:
# The groups filter used for retrieving groups of a given user. # The groups filter used for retrieving groups of a given user.
# {0} is a matcher replaced by username. # {0} is a matcher replaced by username.
# 'member=cn={0},<additional_users_dn>,<base_dn>' by default. # {dn} is a matcher replaced by user DN.
groups_filter: (&(member=cn={0},ou=users,dc=example,dc=com)(objectclass=groupOfNames)) # 'member={dn}' by default.
groups_filter: (&(member={dn})(objectclass=groupOfNames))
# The attribute holding the name of the group # The attribute holding the name of the group
group_name_attribute: cn group_name_attribute: cn

View File

@ -33,8 +33,9 @@ ldap:
# The groups filter used for retrieving groups of a given user. # The groups filter used for retrieving groups of a given user.
# {0} is a matcher replaced by username. # {0} is a matcher replaced by username.
# 'member=cn={0},<additional_users_dn>,<base_dn>' by default. # {dn} is a matcher replaced by user DN.
groups_filter: (&(member=cn={0},ou=users,dc=example,dc=com)(objectclass=groupOfNames)) # 'member={dn}' by default.
groups_filter: (&(member={dn})(objectclass=groupOfNames))
# The attribute holding the name of the group # The attribute holding the name of the group
group_name_attribute: cn group_name_attribute: cn

View File

@ -29,10 +29,7 @@ function ensure_key_existence(config: object, path: string): void {
function adaptLdapConfiguration(userConfig: UserLdapConfiguration): LdapConfiguration { function adaptLdapConfiguration(userConfig: UserLdapConfiguration): LdapConfiguration {
const DEFAULT_USERS_FILTER = "cn={0}"; const DEFAULT_USERS_FILTER = "cn={0}";
const DEFAULT_GROUPS_FILTER = const DEFAULT_GROUPS_FILTER = "member={dn}";
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_GROUP_NAME_ATTRIBUTE = "cn"; const DEFAULT_GROUP_NAME_ATTRIBUTE = "cn";
const DEFAULT_MAIL_ATTRIBUTE = "mail"; const DEFAULT_MAIL_ATTRIBUTE = "mail";

View File

@ -47,18 +47,34 @@ export class Client implements IClient {
}); });
} }
private createGroupsFilter(userGroupsFilter: string, username: string): BluebirdPromise<string> {
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<string[]> { searchGroups(username: string): BluebirdPromise<string[]> {
const that = this; const that = this;
const filter = that.options.groups_filter.replace("{0}", username); return this.createGroupsFilter(this.options.groups_filter, username)
.then(function (groupsFilter: string) {
that.logger.debug("Computed groups filter is %s", groupsFilter);
const query = { const query = {
scope: "sub", scope: "sub",
attributes: [that.options.group_name_attribute], attributes: [that.options.group_name_attribute],
filter: filter filter: groupsFilter
}; };
return this.ldapClient.searchAsync(that.options.groups_dn, query) return that.ldapClient.searchAsync(that.options.groups_dn, query);
})
.then(function (docs: { cn: string }[]) { .then(function (docs: { cn: string }[]) {
const groups = docs.map((doc: any) => { return doc.cn; }); 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); return BluebirdPromise.resolve(groups);
}); });
} }
@ -66,6 +82,7 @@ export class Client implements IClient {
searchUserDn(username: string): BluebirdPromise<string> { searchUserDn(username: string): BluebirdPromise<string> {
const that = this; const that = this;
const filter = this.options.users_filter.replace("{0}", username); const filter = this.options.users_filter.replace("{0}", username);
this.logger.debug("Computed users filter is %s", filter);
const query = { const query = {
scope: "sub", scope: "sub",
sizeLimit: 1, sizeLimit: 1,

View File

@ -45,7 +45,7 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro
const isAllowed = accessController.isAccessAllowed(domain, path, username, groups); const isAllowed = accessController.isAccessAllowed(domain, path, username, groups);
if (!isAllowed) return BluebirdPromise.reject( 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))); username, domain)));
if (authenticationMethod == "two_factor" && !authSession.second_factor) if (authenticationMethod == "two_factor" && !authSession.second_factor)

View File

@ -56,7 +56,7 @@ describe("test ldap configuration adaptation", function () {
users_dn: "dc=example,dc=com", users_dn: "dc=example,dc=com",
users_filter: "cn={0}", users_filter: "cn={0}",
groups_dn: "dc=example,dc=com", groups_dn: "dc=example,dc=com",
groups_filter: "member=cn={0},dc=example,dc=com", groups_filter: "member={dn}",
group_name_attribute: "cn", group_name_attribute: "cn",
mail_attribute: "mail", mail_attribute: "mail",
user: "admin", user: "admin",

View File

@ -31,9 +31,9 @@ describe("test authelia ldap client", function () {
const ldapClient = new LdapClientStub(); const ldapClient = new LdapClientStub();
factory.createStub.returns(ldapClient); factory.createStub.returns(ldapClient);
ldapClient.searchAsyncStub.returns(BluebirdPromise.resolve([ ldapClient.searchAsyncStub.returns(BluebirdPromise.resolve([{
"group1" cn: "group1"
])); }]));
const client = new Client(ADMIN_USER_DN, ADMIN_PASSWORD, options, factory, Dovehash, Winston); const client = new Client(ADMIN_USER_DN, ADMIN_PASSWORD, options, factory, Dovehash, Winston);
return client.searchGroups("user1") return client.searchGroups("user1")
@ -42,4 +42,49 @@ describe("test authelia ldap client", function () {
"member=cn=user1,ou=users,dc=example,dc=com"); "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"]);
});
});
}); });