Add {dn} as an available matcher in LDAP groups filter
Sometimes, LDAP organization is such that groups membership cannot be computed with username only. User DN is required to retrieve groups. e.g. user Joe has a username joe and a cn of Joe Blogs, resulting in a dn of cn=Joe Blogs,ou=users,dc=example,dc=com which is needed to retrieve groups but cannot be computed from joe only. Issue was reported in issue #146pull/147/head
parent
15fa6286ad
commit
ce264ff4d3
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
const query = {
|
.then(function (groupsFilter: string) {
|
||||||
scope: "sub",
|
that.logger.debug("Computed groups filter is %s", groupsFilter);
|
||||||
attributes: [that.options.group_name_attribute],
|
const query = {
|
||||||
filter: filter
|
scope: "sub",
|
||||||
};
|
attributes: [that.options.group_name_attribute],
|
||||||
return this.ldapClient.searchAsync(that.options.groups_dn, query)
|
filter: groupsFilter
|
||||||
|
};
|
||||||
|
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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
Loading…
Reference in New Issue