Use username matcher instead of user dn in group filter
Previously, string "{0}" was replaced by the user dn in the groups_filter attributes of the LDAP configuration. However, if the groups children only have a memberUid attribute, one would like to use the username instead of the user dn. Since the user dn can be built from the username, "{0}" is now replaced by the username instead of the user dn so that an LDAP relying on attribute 'memberUid' can be used.pull/106/head
parent
be81f04248
commit
66449eedb0
|
@ -23,18 +23,18 @@ ldap:
|
||||||
# An additional dn to define the scope to all users
|
# An additional dn to define the scope to all users
|
||||||
additional_users_dn: ou=users
|
additional_users_dn: ou=users
|
||||||
|
|
||||||
# The users filter.
|
# The users filter used to find the user DN
|
||||||
# {0} is the matcher replaced by username.
|
# {0} is a matcher replaced by username.
|
||||||
# 'cn={0}' by default.
|
# 'cn={0}' by default.
|
||||||
users_filter: cn={0}
|
users_filter: cn={0}
|
||||||
|
|
||||||
# An additional dn to define the scope of groups
|
# An additional dn to define the scope of groups
|
||||||
additional_groups_dn: ou=groups
|
additional_groups_dn: ou=groups
|
||||||
|
|
||||||
# The groups filter.
|
# The groups filter used for retrieving groups of a given user.
|
||||||
# {0} is the matcher replaced by user dn.
|
# {0} is a matcher replaced by username.
|
||||||
# 'member={0}' by default.
|
# 'member=cn={0},<additional_users_dn>,<base_dn>' by default.
|
||||||
groups_filter: (&(member={0})(objectclass=groupOfNames))
|
groups_filter: (&(member=cn={0},ou=users,dc=example,dc=com)(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
|
||||||
|
|
|
@ -31,10 +31,10 @@ ldap:
|
||||||
# An additional dn to define the scope of groups
|
# An additional dn to define the scope of groups
|
||||||
additional_groups_dn: ou=groups
|
additional_groups_dn: ou=groups
|
||||||
|
|
||||||
# The groups filter.
|
# The groups filter used for retrieving groups of a given user.
|
||||||
# {0} is the matcher replaced by user dn.
|
# {0} is a matcher replaced by username.
|
||||||
# 'member={0}' by default.
|
# 'member=cn={0},<additional_users_dn>,<base_dn>' by default.
|
||||||
groups_filter: (&(member={0})(objectclass=groupOfNames))
|
groups_filter: (&(member=cn={0},ou=users,dc=example,dc=com)(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
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
|
||||||
import winston = require("winston");
|
import winston = require("winston");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import U2F = require("u2f");
|
||||||
|
|
||||||
import { IAuthenticator } from "./ldap/IAuthenticator";
|
import { IAuthenticator } from "./ldap/IAuthenticator";
|
||||||
import { IPasswordUpdater } from "./ldap/IPasswordUpdater";
|
import { IPasswordUpdater } from "./ldap/IPasswordUpdater";
|
||||||
import { IEmailsRetriever } from "./ldap/IEmailsRetriever";
|
import { IEmailsRetriever } from "./ldap/IEmailsRetriever";
|
||||||
|
@ -8,10 +10,10 @@ import { Authenticator } from "./ldap/Authenticator";
|
||||||
import { PasswordUpdater } from "./ldap/PasswordUpdater";
|
import { PasswordUpdater } from "./ldap/PasswordUpdater";
|
||||||
import { EmailsRetriever } from "./ldap/EmailsRetriever";
|
import { EmailsRetriever } from "./ldap/EmailsRetriever";
|
||||||
import { ClientFactory } from "./ldap/ClientFactory";
|
import { ClientFactory } from "./ldap/ClientFactory";
|
||||||
|
import { LdapClientFactory } from "./ldap/LdapClientFactory";
|
||||||
|
|
||||||
import { TOTPValidator } from "./TOTPValidator";
|
import { TOTPValidator } from "./TOTPValidator";
|
||||||
import { TOTPGenerator } from "./TOTPGenerator";
|
import { TOTPGenerator } from "./TOTPGenerator";
|
||||||
import U2F = require("u2f");
|
|
||||||
import { IUserDataStore } from "./storage/IUserDataStore";
|
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||||
import { UserDataStore } from "./storage/UserDataStore";
|
import { UserDataStore } from "./storage/UserDataStore";
|
||||||
import { INotifier } from "./notifiers/INotifier";
|
import { INotifier } from "./notifiers/INotifier";
|
||||||
|
@ -73,11 +75,12 @@ class UserDataStoreFactory {
|
||||||
export class ServerVariablesHandler {
|
export class ServerVariablesHandler {
|
||||||
static initialize(app: express.Application, config: Configuration.AppConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
static initialize(app: express.Application, config: Configuration.AppConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||||
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
|
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
|
||||||
const ldapClientFactory = new ClientFactory(config.ldap, deps.ldapjs, deps.dovehash, deps.winston);
|
const ldapClientFactory = new LdapClientFactory(config.ldap, deps.ldapjs);
|
||||||
|
const clientFactory = new ClientFactory(config.ldap, ldapClientFactory, deps.dovehash, deps.winston);
|
||||||
|
|
||||||
const ldapAuthenticator = new Authenticator(config.ldap, ldapClientFactory);
|
const ldapAuthenticator = new Authenticator(config.ldap, clientFactory);
|
||||||
const ldapPasswordUpdater = new PasswordUpdater(config.ldap, ldapClientFactory);
|
const ldapPasswordUpdater = new PasswordUpdater(config.ldap, clientFactory);
|
||||||
const ldapEmailsRetriever = new EmailsRetriever(config.ldap, ldapClientFactory);
|
const ldapEmailsRetriever = new EmailsRetriever(config.ldap, clientFactory);
|
||||||
const accessController = new AccessController(config.access_control, deps.winston);
|
const accessController = new AccessController(config.access_control, deps.winston);
|
||||||
const totpValidator = new TOTPValidator(deps.speakeasy);
|
const totpValidator = new TOTPValidator(deps.speakeasy);
|
||||||
const totpGenerator = new TOTPGenerator(deps.speakeasy);
|
const totpGenerator = new TOTPGenerator(deps.speakeasy);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
MongoStorageConfiguration, LocalStorageConfiguration,
|
MongoStorageConfiguration, LocalStorageConfiguration,
|
||||||
UserLdapConfiguration
|
UserLdapConfiguration
|
||||||
} from "./Configuration";
|
} from "./Configuration";
|
||||||
|
import Util = require("util");
|
||||||
|
|
||||||
const LDAP_URL_ENV_VARIABLE = "LDAP_URL";
|
const LDAP_URL_ENV_VARIABLE = "LDAP_URL";
|
||||||
|
|
||||||
|
@ -26,7 +27,10 @@ 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 = "member={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_GROUP_NAME_ATTRIBUTE = "cn";
|
const DEFAULT_GROUP_NAME_ATTRIBUTE = "cn";
|
||||||
const DEFAULT_MAIL_ATTRIBUTE = "mail";
|
const DEFAULT_MAIL_ATTRIBUTE = "mail";
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { GroupsAndEmails } from "./IClient";
|
||||||
|
|
||||||
import { IAuthenticator } from "./IAuthenticator";
|
import { IAuthenticator } from "./IAuthenticator";
|
||||||
import { LdapConfiguration } from "../configuration/Configuration";
|
import { LdapConfiguration } from "../configuration/Configuration";
|
||||||
import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies";
|
import { EmailsAndGroupsRetriever } from "./EmailsAndGroupsRetriever";
|
||||||
|
|
||||||
|
|
||||||
export class Authenticator implements IAuthenticator {
|
export class Authenticator implements IAuthenticator {
|
||||||
|
@ -23,7 +23,7 @@ export class Authenticator implements IAuthenticator {
|
||||||
const that = this;
|
const that = this;
|
||||||
let userClient: IClient;
|
let userClient: IClient;
|
||||||
const adminClient = this.clientFactory.create(this.options.user, this.options.password);
|
const adminClient = this.clientFactory.create(this.options.user, this.options.password);
|
||||||
let groupsAndEmails: GroupsAndEmails;
|
const emailsAndGroupsRetriever = new EmailsAndGroupsRetriever(this.options, this.clientFactory);
|
||||||
|
|
||||||
return adminClient.open()
|
return adminClient.open()
|
||||||
.then(function () {
|
.then(function () {
|
||||||
|
@ -37,16 +37,9 @@ export class Authenticator implements IAuthenticator {
|
||||||
return userClient.close();
|
return userClient.close();
|
||||||
})
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return adminClient.open();
|
return emailsAndGroupsRetriever.retrieve(username);
|
||||||
})
|
})
|
||||||
.then(function () {
|
.then(function (groupsAndEmails: GroupsAndEmails) {
|
||||||
return adminClient.searchEmailsAndGroups(username);
|
|
||||||
})
|
|
||||||
.then(function (gae: GroupsAndEmails) {
|
|
||||||
groupsAndEmails = gae;
|
|
||||||
return adminClient.close();
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
return BluebirdPromise.resolve(groupsAndEmails);
|
return BluebirdPromise.resolve(groupsAndEmails);
|
||||||
})
|
})
|
||||||
.error(function (err: Error) {
|
.error(function (err: Error) {
|
||||||
|
|
|
@ -2,63 +2,37 @@
|
||||||
import util = require("util");
|
import util = require("util");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import exceptions = require("../Exceptions");
|
import exceptions = require("../Exceptions");
|
||||||
import Ldapjs = require("ldapjs");
|
|
||||||
import Dovehash = require("dovehash");
|
import Dovehash = require("dovehash");
|
||||||
|
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import { IClient, GroupsAndEmails } from "./IClient";
|
import { IClient, GroupsAndEmails } from "./IClient";
|
||||||
|
import { ILdapClient } from "./ILdapClient";
|
||||||
|
import { ILdapClientFactory } from "./ILdapClientFactory";
|
||||||
import { LdapConfiguration } from "../configuration/Configuration";
|
import { LdapConfiguration } from "../configuration/Configuration";
|
||||||
import { Winston } from "../../../types/Dependencies";
|
import { Winston } from "../../../types/Dependencies";
|
||||||
|
|
||||||
interface SearchEntry {
|
|
||||||
object: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "ldapjs" {
|
|
||||||
export interface ClientAsync {
|
|
||||||
on(event: string, callback: (data?: any) => void): void;
|
|
||||||
bindAsync(username: string, password: string): BluebirdPromise<void>;
|
|
||||||
unbindAsync(): BluebirdPromise<void>;
|
|
||||||
searchAsync(base: string, query: Ldapjs.SearchOptions): BluebirdPromise<EventEmitter>;
|
|
||||||
modifyAsync(userdn: string, change: Ldapjs.Change): BluebirdPromise<void>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Client implements IClient {
|
export class Client implements IClient {
|
||||||
private userDN: string;
|
private userDN: string;
|
||||||
private password: string;
|
private password: string;
|
||||||
private client: Ldapjs.ClientAsync;
|
private ldapClient: ILdapClient;
|
||||||
|
|
||||||
private ldapjs: typeof Ldapjs;
|
|
||||||
private logger: Winston;
|
private logger: Winston;
|
||||||
private dovehash: typeof Dovehash;
|
private dovehash: typeof Dovehash;
|
||||||
private options: LdapConfiguration;
|
private options: LdapConfiguration;
|
||||||
|
|
||||||
constructor(userDN: string, password: string, options: LdapConfiguration,
|
constructor(userDN: string, password: string, options: LdapConfiguration,
|
||||||
ldapjs: typeof Ldapjs, dovehash: typeof Dovehash, logger: Winston) {
|
ldapClientFactory: ILdapClientFactory, dovehash: typeof Dovehash, logger: Winston) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.ldapjs = ldapjs;
|
|
||||||
this.dovehash = dovehash;
|
this.dovehash = dovehash;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.userDN = userDN;
|
this.userDN = userDN;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
|
this.ldapClient = ldapClientFactory.create();
|
||||||
const ldapClient = ldapjs.createClient({
|
|
||||||
url: this.options.url,
|
|
||||||
reconnect: true
|
|
||||||
});
|
|
||||||
|
|
||||||
/*const clientLogger = (ldapClient as any).log;
|
|
||||||
if (clientLogger) {
|
|
||||||
clientLogger.level("trace");
|
|
||||||
}*/
|
|
||||||
|
|
||||||
this.client = BluebirdPromise.promisifyAll(ldapClient) as Ldapjs.ClientAsync;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open(): BluebirdPromise<void> {
|
open(): BluebirdPromise<void> {
|
||||||
this.logger.debug("LDAP: Bind user '%s'", this.userDN);
|
this.logger.debug("LDAP: Bind user '%s'", this.userDN);
|
||||||
return this.client.bindAsync(this.userDN, this.password)
|
return this.ldapClient.bindAsync(this.userDN, this.password)
|
||||||
.error(function (err: Error) {
|
.error(function (err: Error) {
|
||||||
return BluebirdPromise.reject(new exceptions.LdapBindError(err.message));
|
return BluebirdPromise.reject(new exceptions.LdapBindError(err.message));
|
||||||
});
|
});
|
||||||
|
@ -66,61 +40,24 @@ export class Client implements IClient {
|
||||||
|
|
||||||
close(): BluebirdPromise<void> {
|
close(): BluebirdPromise<void> {
|
||||||
this.logger.debug("LDAP: Unbind user '%s'", this.userDN);
|
this.logger.debug("LDAP: Unbind user '%s'", this.userDN);
|
||||||
return this.client.unbindAsync()
|
return this.ldapClient.unbindAsync()
|
||||||
.error(function (err: Error) {
|
.error(function (err: Error) {
|
||||||
return BluebirdPromise.reject(new exceptions.LdapBindError(err.message));
|
return BluebirdPromise.reject(new exceptions.LdapBindError(err.message));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private search(base: string, query: Ldapjs.SearchOptions): BluebirdPromise<any> {
|
searchGroups(username: string): BluebirdPromise<string[]> {
|
||||||
const that = this;
|
const that = this;
|
||||||
|
const filter = that.options.groups_filter.replace("{0}", username);
|
||||||
that.logger.debug("LDAP: Search for '%s' in '%s'", JSON.stringify(query), base);
|
|
||||||
return that.client.searchAsync(base, query)
|
|
||||||
.then(function (res: EventEmitter) {
|
|
||||||
const doc: SearchEntry[] = [];
|
|
||||||
|
|
||||||
return new BluebirdPromise((resolve, reject) => {
|
|
||||||
res.on("searchEntry", function (entry: SearchEntry) {
|
|
||||||
that.logger.debug("Entry retrieved from LDAP is '%s'", JSON.stringify(entry.object));
|
|
||||||
doc.push(entry.object);
|
|
||||||
});
|
|
||||||
res.on("error", function (err: Error) {
|
|
||||||
that.logger.error("LDAP: Error received during search '%s'.", JSON.stringify(err));
|
|
||||||
reject(new exceptions.LdapSearchError(err.message));
|
|
||||||
});
|
|
||||||
res.on("end", function () {
|
|
||||||
that.logger.debug("LDAP: Search ended and results are '%s'.", JSON.stringify(doc));
|
|
||||||
resolve(doc);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(function (err: Error) {
|
|
||||||
return BluebirdPromise.reject(new exceptions.LdapSearchError(err.message));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private searchGroups(username: string): BluebirdPromise<string[]> {
|
|
||||||
const that = this;
|
|
||||||
|
|
||||||
const groups: string[] = [];
|
|
||||||
return that.searchUserDn(username)
|
|
||||||
.then(function (userDN: string) {
|
|
||||||
const filter = that.options.groups_filter.replace("{0}", userDN);
|
|
||||||
const query = {
|
const query = {
|
||||||
scope: "sub",
|
scope: "sub",
|
||||||
attributes: [that.options.group_name_attribute],
|
attributes: [that.options.group_name_attribute],
|
||||||
filter: filter
|
filter: filter
|
||||||
};
|
};
|
||||||
return that.search(that.options.groups_dn, query);
|
return this.ldapClient.searchAsync(that.options.groups_dn, query)
|
||||||
})
|
.then(function (docs: { cn: string }[]) {
|
||||||
.then(function (docs) {
|
const groups = docs.map((doc: any) => { return doc.cn; });
|
||||||
for (let i = 0; i < docs.length; ++i) {
|
|
||||||
groups.push(docs[i].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);
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
return BluebirdPromise.resolve(groups);
|
return BluebirdPromise.resolve(groups);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -136,7 +73,7 @@ export class Client implements IClient {
|
||||||
};
|
};
|
||||||
|
|
||||||
that.logger.debug("LDAP: searching for user dn of %s", username);
|
that.logger.debug("LDAP: searching for user dn of %s", username);
|
||||||
return that.search(this.options.users_dn, query)
|
return that.ldapClient.searchAsync(this.options.users_dn, query)
|
||||||
.then(function (users: { dn: string }[]) {
|
.then(function (users: { dn: string }[]) {
|
||||||
that.logger.debug("LDAP: retrieved user dn is %s", users[0].dn);
|
that.logger.debug("LDAP: retrieved user dn is %s", users[0].dn);
|
||||||
return BluebirdPromise.resolve(users[0].dn);
|
return BluebirdPromise.resolve(users[0].dn);
|
||||||
|
@ -153,35 +90,17 @@ export class Client implements IClient {
|
||||||
|
|
||||||
return this.searchUserDn(username)
|
return this.searchUserDn(username)
|
||||||
.then(function (userDN) {
|
.then(function (userDN) {
|
||||||
return that.search(userDN, query);
|
return that.ldapClient.searchAsync(userDN, query);
|
||||||
})
|
})
|
||||||
.then(function (docs: { mail: string }[]) {
|
.then(function (docs: { mail: string }[]) {
|
||||||
const emails: string[] = [];
|
const emails: string[] = docs
|
||||||
if (typeof docs[0].mail === "string")
|
.filter((d) => { return typeof d.mail === "string"; })
|
||||||
emails.push(docs[0].mail);
|
.map((d) => { return d.mail; });
|
||||||
else {
|
|
||||||
emails.concat(docs[0].mail);
|
|
||||||
}
|
|
||||||
that.logger.debug("LDAP: emails of user '%s' are %s", username, emails);
|
that.logger.debug("LDAP: emails of user '%s' are %s", username, emails);
|
||||||
return BluebirdPromise.resolve(emails);
|
return BluebirdPromise.resolve(emails);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
searchEmailsAndGroups(username: string): BluebirdPromise<GroupsAndEmails> {
|
|
||||||
const that = this;
|
|
||||||
let retrievedEmails: string[], retrievedGroups: string[];
|
|
||||||
|
|
||||||
return this.searchEmails(username)
|
|
||||||
.then(function (emails: string[]) {
|
|
||||||
retrievedEmails = emails;
|
|
||||||
return that.searchGroups(username);
|
|
||||||
})
|
})
|
||||||
.then(function (groups: string[]) {
|
.catch(function (err: Error) {
|
||||||
retrievedGroups = groups;
|
return BluebirdPromise.reject(new exceptions.LdapError("Error while searching emails. " + err.stack));
|
||||||
return BluebirdPromise.resolve({
|
|
||||||
emails: retrievedEmails,
|
|
||||||
groups: retrievedGroups
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,10 +117,10 @@ export class Client implements IClient {
|
||||||
this.logger.debug("LDAP: update password of user '%s'", username);
|
this.logger.debug("LDAP: update password of user '%s'", username);
|
||||||
return this.searchUserDn(username)
|
return this.searchUserDn(username)
|
||||||
.then(function (userDN: string) {
|
.then(function (userDN: string) {
|
||||||
that.client.modifyAsync(userDN, change);
|
that.ldapClient.modifyAsync(userDN, change);
|
||||||
})
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return that.client.unbindAsync();
|
return that.ldapClient.unbindAsync();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { IClientFactory } from "./IClientFactory";
|
import { IClientFactory } from "./IClientFactory";
|
||||||
import { IClient } from "./IClient";
|
import { IClient } from "./IClient";
|
||||||
import { Client } from "./Client";
|
import { Client } from "./Client";
|
||||||
|
import { ILdapClientFactory } from "./ILdapClientFactory";
|
||||||
import { LdapConfiguration } from "../configuration/Configuration";
|
import { LdapConfiguration } from "../configuration/Configuration";
|
||||||
|
|
||||||
import Ldapjs = require("ldapjs");
|
import Ldapjs = require("ldapjs");
|
||||||
|
@ -9,19 +10,20 @@ import Winston = require("winston");
|
||||||
|
|
||||||
export class ClientFactory implements IClientFactory {
|
export class ClientFactory implements IClientFactory {
|
||||||
private config: LdapConfiguration;
|
private config: LdapConfiguration;
|
||||||
private ldapjs: typeof Ldapjs;
|
private ldapClientFactory: ILdapClientFactory;
|
||||||
private dovehash: typeof Dovehash;
|
private dovehash: typeof Dovehash;
|
||||||
private logger: typeof Winston;
|
private logger: typeof Winston;
|
||||||
|
|
||||||
constructor(ldapConfiguration: LdapConfiguration, ldapjs: typeof Ldapjs,
|
constructor(ldapConfiguration: LdapConfiguration, ldapClientFactory: ILdapClientFactory,
|
||||||
dovehash: typeof Dovehash, logger: typeof Winston) {
|
dovehash: typeof Dovehash, logger: typeof Winston) {
|
||||||
this.config = ldapConfiguration;
|
this.config = ldapConfiguration;
|
||||||
this.ldapjs = ldapjs;
|
this.ldapClientFactory = ldapClientFactory;
|
||||||
this.dovehash = dovehash;
|
this.dovehash = dovehash;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
create(userDN: string, password: string): IClient {
|
create(userDN: string, password: string): IClient {
|
||||||
return new Client(userDN, password, this.config, this.ldapjs, this.dovehash, this.logger);
|
return new Client(userDN, password, this.config, this.ldapClientFactory,
|
||||||
|
this.dovehash, this.logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import exceptions = require("../Exceptions");
|
||||||
|
import ldapjs = require("ldapjs");
|
||||||
|
import { Client } from "./Client";
|
||||||
|
import { IClientFactory } from "./IClientFactory";
|
||||||
|
import { LdapConfiguration } from "../configuration/Configuration";
|
||||||
|
import { GroupsAndEmails } from "./IClient";
|
||||||
|
|
||||||
|
|
||||||
|
export class EmailsAndGroupsRetriever {
|
||||||
|
private options: LdapConfiguration;
|
||||||
|
private clientFactory: IClientFactory;
|
||||||
|
|
||||||
|
constructor(options: LdapConfiguration, clientFactory: IClientFactory) {
|
||||||
|
this.options = options;
|
||||||
|
this.clientFactory = clientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieve(username: string): BluebirdPromise<GroupsAndEmails> {
|
||||||
|
const adminClient = this.clientFactory.create(this.options.user, this.options.password);
|
||||||
|
let emails: string[];
|
||||||
|
let groups: string[];
|
||||||
|
|
||||||
|
return adminClient.open()
|
||||||
|
.then(function () {
|
||||||
|
return adminClient.searchEmails(username);
|
||||||
|
})
|
||||||
|
.then(function (emails_: string[]) {
|
||||||
|
emails = emails_;
|
||||||
|
return adminClient.searchGroups(username);
|
||||||
|
})
|
||||||
|
.then(function (groups_: string[]) {
|
||||||
|
groups = groups_;
|
||||||
|
return adminClient.close();
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return BluebirdPromise.resolve({
|
||||||
|
emails: emails,
|
||||||
|
groups: groups
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.error(function (err: Error) {
|
||||||
|
return BluebirdPromise.reject(new exceptions.LdapError("Failed during emails and groups retrieval: " + err.message));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,8 +32,8 @@ export class EmailsRetriever implements IEmailsRetriever {
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return BluebirdPromise.resolve(emails);
|
return BluebirdPromise.resolve(emails);
|
||||||
})
|
})
|
||||||
.error(function (err: Error) {
|
.catch(function (err: Error) {
|
||||||
return BluebirdPromise.reject(new exceptions.LdapError("Failed during password update: " + err.message));
|
return BluebirdPromise.reject(new exceptions.LdapError("Failed during email retrieval: " + err.message));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import exceptions = require("../Exceptions");
|
||||||
|
import ldapjs = require("ldapjs");
|
||||||
|
import { IClient } from "./IClient";
|
||||||
|
|
||||||
|
import { IClientFactory } from "./IClientFactory";
|
||||||
|
import { IGroupsRetriever } from "./IGroupsRetriever";
|
||||||
|
import { LdapConfiguration } from "../configuration/Configuration";
|
||||||
|
|
||||||
|
|
||||||
|
export class GroupsRetriever implements IGroupsRetriever {
|
||||||
|
private options: LdapConfiguration;
|
||||||
|
private clientFactory: IClientFactory;
|
||||||
|
|
||||||
|
constructor(options: LdapConfiguration, clientFactory: IClientFactory) {
|
||||||
|
this.options = options;
|
||||||
|
this.clientFactory = clientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieve(username: string, client?: IClient): BluebirdPromise<string[]> {
|
||||||
|
client = this.clientFactory.create(this.options.user, this.options.password);
|
||||||
|
let groups: string[];
|
||||||
|
|
||||||
|
return client.open()
|
||||||
|
.then(function () {
|
||||||
|
return client.searchGroups(username);
|
||||||
|
})
|
||||||
|
.then(function (groups_: string[]) {
|
||||||
|
groups = groups_;
|
||||||
|
return client.close();
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return BluebirdPromise.resolve(groups);
|
||||||
|
})
|
||||||
|
.catch(function (err: Error) {
|
||||||
|
return BluebirdPromise.reject(new exceptions.LdapError("Failed during groups retrieval: " + err.message));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,6 @@ export interface IClient {
|
||||||
close(): BluebirdPromise<void>;
|
close(): BluebirdPromise<void>;
|
||||||
searchUserDn(username: string): BluebirdPromise<string>;
|
searchUserDn(username: string): BluebirdPromise<string>;
|
||||||
searchEmails(username: string): BluebirdPromise<string[]>;
|
searchEmails(username: string): BluebirdPromise<string[]>;
|
||||||
searchEmailsAndGroups(username: string): BluebirdPromise<GroupsAndEmails>;
|
searchGroups(username: string): BluebirdPromise<string[]>;
|
||||||
modifyPassword(username: string, newPassword: string): BluebirdPromise<void>;
|
modifyPassword(username: string, newPassword: string): BluebirdPromise<void>;
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import { IClient } from "./IClient";
|
||||||
|
|
||||||
export interface IEmailsRetriever {
|
export interface IEmailsRetriever {
|
||||||
retrieve(username: string): BluebirdPromise<string[]>;
|
retrieve(username: string, client?: IClient): BluebirdPromise<string[]>;
|
||||||
}
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import { IClient } from "./IClient";
|
||||||
|
|
||||||
|
export interface IGroupsRetriever {
|
||||||
|
retrieve(username: string): BluebirdPromise<string[]>;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import EventEmitter = require("events");
|
||||||
|
|
||||||
|
export interface ILdapClient {
|
||||||
|
bindAsync(username: string, password: string): BluebirdPromise<void>;
|
||||||
|
unbindAsync(): BluebirdPromise<void>;
|
||||||
|
searchAsync(base: string, query: any): BluebirdPromise<any[]>;
|
||||||
|
modifyAsync(dn: string, changeRequest: any): BluebirdPromise<void>;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
import { ILdapClient } from "./ILdapClient";
|
||||||
|
|
||||||
|
export interface ILdapClientFactory {
|
||||||
|
create(): ILdapClient;
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
import LdapJs = require("ldapjs");
|
||||||
|
import EventEmitter = require("events");
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import { ILdapClient } from "./ILdapClient";
|
||||||
|
import Exceptions = require("../Exceptions");
|
||||||
|
|
||||||
|
declare module "ldapjs" {
|
||||||
|
export interface ClientAsync {
|
||||||
|
on(event: string, callback: (data?: any) => void): void;
|
||||||
|
bindAsync(username: string, password: string): BluebirdPromise<void>;
|
||||||
|
unbindAsync(): BluebirdPromise<void>;
|
||||||
|
searchAsync(base: string, query: LdapJs.SearchOptions): BluebirdPromise<EventEmitter>;
|
||||||
|
modifyAsync(userdn: string, change: LdapJs.Change): BluebirdPromise<void>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SearchEntry {
|
||||||
|
object: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LdapClient implements ILdapClient {
|
||||||
|
private client: LdapJs.ClientAsync;
|
||||||
|
|
||||||
|
constructor(url: string, ldapjs: typeof LdapJs) {
|
||||||
|
const ldapClient = ldapjs.createClient({
|
||||||
|
url: url,
|
||||||
|
reconnect: true
|
||||||
|
});
|
||||||
|
|
||||||
|
/*const clientLogger = (ldapClient as any).log;
|
||||||
|
if (clientLogger) {
|
||||||
|
clientLogger.level("trace");
|
||||||
|
}*/
|
||||||
|
|
||||||
|
this.client = BluebirdPromise.promisifyAll(ldapClient) as LdapJs.ClientAsync;
|
||||||
|
}
|
||||||
|
|
||||||
|
bindAsync(username: string, password: string): BluebirdPromise<void> {
|
||||||
|
return this.client.bindAsync(username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
unbindAsync(): BluebirdPromise<void> {
|
||||||
|
return this.client.unbindAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
searchAsync(base: string, query: any): BluebirdPromise<any[]> {
|
||||||
|
const that = this;
|
||||||
|
return this.client.searchAsync(base, query)
|
||||||
|
.then(function (res: EventEmitter) {
|
||||||
|
const doc: SearchEntry[] = [];
|
||||||
|
return new BluebirdPromise<any[]>((resolve, reject) => {
|
||||||
|
res.on("searchEntry", function (entry: SearchEntry) {
|
||||||
|
doc.push(entry.object);
|
||||||
|
});
|
||||||
|
res.on("error", function (err: Error) {
|
||||||
|
reject(new Exceptions.LdapSearchError(err.message));
|
||||||
|
});
|
||||||
|
res.on("end", function () {
|
||||||
|
resolve(doc);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function (err: Error) {
|
||||||
|
return BluebirdPromise.reject(new Exceptions.LdapSearchError(err.message));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyAsync(dn: string, changeRequest: any): BluebirdPromise<void> {
|
||||||
|
return this.client.modifyAsync(dn, changeRequest);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { ILdapClientFactory } from "./ILdapClientFactory";
|
||||||
|
import { ILdapClient } from "./ILdapClient";
|
||||||
|
import { LdapClient } from "./LdapClient";
|
||||||
|
import { LdapConfiguration } from "../configuration/Configuration";
|
||||||
|
|
||||||
|
import Ldapjs = require("ldapjs");
|
||||||
|
|
||||||
|
export class LdapClientFactory implements ILdapClientFactory {
|
||||||
|
private config: LdapConfiguration;
|
||||||
|
private ldapjs: typeof Ldapjs;
|
||||||
|
|
||||||
|
constructor(ldapConfiguration: LdapConfiguration, ldapjs: typeof Ldapjs) {
|
||||||
|
this.config = ldapConfiguration;
|
||||||
|
this.ldapjs = ldapjs;
|
||||||
|
}
|
||||||
|
|
||||||
|
create(): ILdapClient {
|
||||||
|
return new LdapClient(this.config.url, this.ldapjs);
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,7 +55,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={0}",
|
groups_filter: "member=cn={0},dc=example,dc=com",
|
||||||
group_name_attribute: "cn",
|
group_name_attribute: "cn",
|
||||||
mail_attribute: "mail",
|
mail_attribute: "mail",
|
||||||
user: "admin",
|
user: "admin",
|
||||||
|
|
|
@ -64,10 +64,8 @@ describe("test ldap authentication", function () {
|
||||||
userClientStub.closeStub.returns(BluebirdPromise.resolve());
|
userClientStub.closeStub.returns(BluebirdPromise.resolve());
|
||||||
|
|
||||||
// admin retrieves emails and groups of user
|
// admin retrieves emails and groups of user
|
||||||
adminClientStub.searchEmailsAndGroupsStub.returns(BluebirdPromise.resolve({
|
adminClientStub.searchEmailsStub.returns(BluebirdPromise.resolve(["group1"]));
|
||||||
groups: ["group1"],
|
adminClientStub.searchGroupsStub.returns(BluebirdPromise.resolve(["user@example.com"]));
|
||||||
emails: ["user@example.com"]
|
|
||||||
}));
|
|
||||||
|
|
||||||
return authenticator.authenticate(USERNAME, PASSWORD);
|
return authenticator.authenticate(USERNAME, PASSWORD);
|
||||||
});
|
});
|
||||||
|
@ -117,8 +115,9 @@ describe("test ldap authentication", function () {
|
||||||
userClientStub.openStub.returns(BluebirdPromise.resolve());
|
userClientStub.openStub.returns(BluebirdPromise.resolve());
|
||||||
userClientStub.closeStub.returns(BluebirdPromise.resolve());
|
userClientStub.closeStub.returns(BluebirdPromise.resolve());
|
||||||
|
|
||||||
|
adminClientStub.searchEmailsStub.returns(BluebirdPromise.resolve(["group1"]));
|
||||||
// admin retrieves emails and groups of user
|
// admin retrieves emails and groups of user
|
||||||
adminClientStub.searchEmailsAndGroupsStub
|
adminClientStub.searchGroupsStub
|
||||||
.returns(BluebirdPromise.reject(new Error("Error while retrieving emails and groups")));
|
.returns(BluebirdPromise.reject(new Error("Error while retrieving emails and groups")));
|
||||||
|
|
||||||
return authenticator.authenticate(USERNAME, PASSWORD)
|
return authenticator.authenticate(USERNAME, PASSWORD)
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
import { LdapConfiguration } from "../../src/lib/configuration/Configuration";
|
||||||
|
import { Client } from "../../src/lib/ldap/Client";
|
||||||
|
import { LdapClientFactoryStub } from "../mocks/ldap/LdapClientFactoryStub";
|
||||||
|
import { LdapClientStub } from "../mocks/ldap/LdapClientStub";
|
||||||
|
|
||||||
|
import Sinon = require("sinon");
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import Assert = require("assert");
|
||||||
|
import Dovehash = require("dovehash");
|
||||||
|
import Winston = require("winston");
|
||||||
|
|
||||||
|
describe("test authelia ldap client", function () {
|
||||||
|
const USERNAME = "username";
|
||||||
|
const ADMIN_USER_DN = "cn=admin,dc=example,dc=com";
|
||||||
|
const ADMIN_PASSWORD = "password";
|
||||||
|
|
||||||
|
it("should replace {0} by username when searching for groups in LDAP", function () {
|
||||||
|
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=cn={0},ou=users,dc=example,dc=com",
|
||||||
|
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);
|
||||||
|
ldapClient.searchAsyncStub.returns(BluebirdPromise.resolve([
|
||||||
|
"group1"
|
||||||
|
]));
|
||||||
|
const client = new Client(ADMIN_USER_DN, ADMIN_PASSWORD, options, factory, Dovehash, Winston);
|
||||||
|
|
||||||
|
return client.searchGroups("user1")
|
||||||
|
.then(function () {
|
||||||
|
Assert.equal(ldapClient.searchAsyncStub.getCall(0).args[1].filter,
|
||||||
|
"member=cn=user1,ou=users,dc=example,dc=com");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,75 @@
|
||||||
|
|
||||||
|
import { GroupsRetriever } from "../../src/lib/ldap/GroupsRetriever";
|
||||||
|
import { LdapConfiguration } from "../../src/lib/configuration/Configuration";
|
||||||
|
|
||||||
|
import Sinon = require("sinon");
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import Assert = require("assert");
|
||||||
|
|
||||||
|
import { ClientFactoryStub } from "../mocks/ldap/ClientFactoryStub";
|
||||||
|
import { ClientStub } from "../mocks/ldap/ClientStub";
|
||||||
|
|
||||||
|
describe("test groups retriever", function () {
|
||||||
|
const USERNAME = "username";
|
||||||
|
const ADMIN_USER_DN = "cn=admin,dc=example,dc=com";
|
||||||
|
const ADMIN_PASSWORD = "password";
|
||||||
|
|
||||||
|
let clientFactoryStub: ClientFactoryStub;
|
||||||
|
let adminClientStub: ClientStub;
|
||||||
|
|
||||||
|
let groupsRetriever: GroupsRetriever;
|
||||||
|
let ldapConfig: LdapConfiguration;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
clientFactoryStub = new ClientFactoryStub();
|
||||||
|
adminClientStub = new ClientStub();
|
||||||
|
|
||||||
|
ldapConfig = {
|
||||||
|
url: "http://ldap",
|
||||||
|
user: ADMIN_USER_DN,
|
||||||
|
password: ADMIN_PASSWORD,
|
||||||
|
users_dn: "ou=users,dc=example,dc=com",
|
||||||
|
groups_dn: "ou=groups,dc=example,dc=com",
|
||||||
|
group_name_attribute: "cn",
|
||||||
|
groups_filter: "member=cn={0},ou=users,dc=example,dc=com",
|
||||||
|
mail_attribute: "mail",
|
||||||
|
users_filter: "cn={0}"
|
||||||
|
};
|
||||||
|
|
||||||
|
groupsRetriever = new GroupsRetriever(ldapConfig, clientFactoryStub);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("success", function () {
|
||||||
|
it("should retrieve groups successfully", function () {
|
||||||
|
clientFactoryStub.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD)
|
||||||
|
.returns(adminClientStub);
|
||||||
|
|
||||||
|
// admin connects successfully
|
||||||
|
adminClientStub.openStub.returns(BluebirdPromise.resolve());
|
||||||
|
adminClientStub.closeStub.returns(BluebirdPromise.resolve());
|
||||||
|
|
||||||
|
adminClientStub.searchGroupsStub.withArgs(USERNAME)
|
||||||
|
.returns(BluebirdPromise.resolve(["user@example.com"]));
|
||||||
|
|
||||||
|
return groupsRetriever.retrieve(USERNAME);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("failure", function () {
|
||||||
|
it("should fail retrieving groups when search operation fails", function () {
|
||||||
|
clientFactoryStub.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD)
|
||||||
|
.returns(adminClientStub);
|
||||||
|
|
||||||
|
// admin connects successfully
|
||||||
|
adminClientStub.openStub.returns(BluebirdPromise.resolve());
|
||||||
|
adminClientStub.closeStub.returns(BluebirdPromise.resolve());
|
||||||
|
|
||||||
|
adminClientStub.searchGroupsStub.withArgs(USERNAME)
|
||||||
|
.returns(BluebirdPromise.reject(new Error("Error while searching groups")));
|
||||||
|
|
||||||
|
return groupsRetriever.retrieve(USERNAME)
|
||||||
|
.then(function () { return BluebirdPromise.reject(new Error("Should not be here")); })
|
||||||
|
.catch(function () { return BluebirdPromise.resolve(); });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -8,7 +8,7 @@ export class ClientStub implements IClient {
|
||||||
closeStub: Sinon.SinonStub;
|
closeStub: Sinon.SinonStub;
|
||||||
searchUserDnStub: Sinon.SinonStub;
|
searchUserDnStub: Sinon.SinonStub;
|
||||||
searchEmailsStub: Sinon.SinonStub;
|
searchEmailsStub: Sinon.SinonStub;
|
||||||
searchEmailsAndGroupsStub: Sinon.SinonStub;
|
searchGroupsStub: Sinon.SinonStub;
|
||||||
modifyPasswordStub: Sinon.SinonStub;
|
modifyPasswordStub: Sinon.SinonStub;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -16,7 +16,7 @@ export class ClientStub implements IClient {
|
||||||
this.closeStub = Sinon.stub();
|
this.closeStub = Sinon.stub();
|
||||||
this.searchUserDnStub = Sinon.stub();
|
this.searchUserDnStub = Sinon.stub();
|
||||||
this.searchEmailsStub = Sinon.stub();
|
this.searchEmailsStub = Sinon.stub();
|
||||||
this.searchEmailsAndGroupsStub = Sinon.stub();
|
this.searchGroupsStub = Sinon.stub();
|
||||||
this.modifyPasswordStub = Sinon.stub();
|
this.modifyPasswordStub = Sinon.stub();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ export class ClientStub implements IClient {
|
||||||
return this.searchEmailsStub(username);
|
return this.searchEmailsStub(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchEmailsAndGroups(username: string): BluebirdPromise<GroupsAndEmails> {
|
searchGroups(username: string): BluebirdPromise<string[]> {
|
||||||
return this.searchEmailsAndGroupsStub(username);
|
return this.searchGroupsStub(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
modifyPassword(username: string, newPassword: string): BluebirdPromise<void> {
|
modifyPassword(username: string, newPassword: string): BluebirdPromise<void> {
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import Sinon = require("sinon");
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import { ILdapClientFactory } from "../../../src/lib/ldap/ILdapClientFactory";
|
||||||
|
import { ILdapClient } from "../../../src/lib/ldap/ILdapClient";
|
||||||
|
|
||||||
|
export class LdapClientFactoryStub implements ILdapClientFactory {
|
||||||
|
createStub: Sinon.SinonStub;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.createStub = Sinon.stub();
|
||||||
|
}
|
||||||
|
|
||||||
|
create(): ILdapClient {
|
||||||
|
return this.createStub();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import Sinon = require("sinon");
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import { ILdapClient } from "../../../src/lib/ldap/ILdapClient";
|
||||||
|
|
||||||
|
export class LdapClientStub implements ILdapClient {
|
||||||
|
bindAsyncStub: Sinon.SinonStub;
|
||||||
|
unbindAsyncStub: Sinon.SinonStub;
|
||||||
|
searchAsyncStub: Sinon.SinonStub;
|
||||||
|
modifyAsyncStub: Sinon.SinonStub;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.bindAsyncStub = Sinon.stub();
|
||||||
|
this.unbindAsyncStub = Sinon.stub();
|
||||||
|
this.searchAsyncStub = Sinon.stub();
|
||||||
|
this.modifyAsyncStub = Sinon.stub();
|
||||||
|
}
|
||||||
|
|
||||||
|
bindAsync(username: string, password: string): BluebirdPromise<void> {
|
||||||
|
return this.bindAsyncStub(username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
unbindAsync(): BluebirdPromise<void> {
|
||||||
|
return this.unbindAsyncStub();
|
||||||
|
}
|
||||||
|
|
||||||
|
searchAsync(base: string, query: any): BluebirdPromise<any[]> {
|
||||||
|
return this.searchAsyncStub(base, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyAsync(dn: string, changeRequest: any): BluebirdPromise<void> {
|
||||||
|
return this.modifyAsyncStub(dn, changeRequest);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,14 +18,16 @@ Feature: User validate first factor
|
||||||
Given I visit "https://auth.test.local:8080/"
|
Given I visit "https://auth.test.local:8080/"
|
||||||
And I login with user "john" and password "password"
|
And I login with user "john" and password "password"
|
||||||
And I register a TOTP secret called "Sec0"
|
And I register a TOTP secret called "Sec0"
|
||||||
When I visit "https://admin.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
|
When I visit "https://admin.test.local:8080/secret.html"
|
||||||
|
And I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
|
||||||
And I login with user "john" and password "password"
|
And I login with user "john" and password "password"
|
||||||
And I use "Sec0" as TOTP token handle
|
And I use "Sec0" as TOTP token handle
|
||||||
And I click on "TOTP"
|
And I click on "TOTP"
|
||||||
Then I'm redirected to "https://admin.test.local:8080/secret.html"
|
Then I'm redirected to "https://admin.test.local:8080/secret.html"
|
||||||
|
|
||||||
Scenario: User fails TOTP second factor
|
Scenario: User fails TOTP second factor
|
||||||
When I visit "https://admin.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
|
When I visit "https://admin.test.local:8080/secret.html"
|
||||||
|
And I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
|
||||||
And I login with user "john" and password "password"
|
And I login with user "john" and password "password"
|
||||||
And I use "BADTOKEN" as TOTP token
|
And I use "BADTOKEN" as TOTP token
|
||||||
And I click on "TOTP"
|
And I click on "TOTP"
|
||||||
|
|
Loading…
Reference in New Issue