mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Fix ECONNRESET when LDAP queries fail. (#261)
This commit should fix #225. In order to avoid stalling LDAP connections, Authelia creates new sessions for each set of queries bound to one authentication, i.e., one session for authentication, emails retrieval and groups retrieval. Before this commit, a failing query was preventing the session to be closed (unbind was not called). Now, unbind is always called whatever the outcome of the query. I took the opportunity of this commit to refactor LDAP client in order to prepare the work on users database stored in a file. (#233)
This commit is contained in:
parent
e50b798edc
commit
6438a5e48f
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -958,9 +958,9 @@
|
|||
}
|
||||
},
|
||||
"bluebird": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
|
||||
"integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA=="
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz",
|
||||
"integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw="
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "4.11.8",
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
"@types/helmet": "0.0.37",
|
||||
"@types/jquery": "^3.3.1",
|
||||
"@types/jsdom": "^11.0.4",
|
||||
"@types/ldapjs": "^1.0.2",
|
||||
"@types/ldapjs": "^1.0.3",
|
||||
"@types/mocha": "^5.0.0",
|
||||
"@types/mockdate": "^2.0.0",
|
||||
"@types/mongodb": "^3.0.9",
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import { IRequestLogger } from "./logging/IRequestLogger";
|
||||
import { IAuthenticator } from "./ldap/IAuthenticator";
|
||||
import { IPasswordUpdater } from "./ldap/IPasswordUpdater";
|
||||
import { IEmailsRetriever } from "./ldap/IEmailsRetriever";
|
||||
import { ITotpHandler } from "./authentication/totp/ITotpHandler";
|
||||
import { IU2fHandler } from "./authentication/u2f/IU2fHandler";
|
||||
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||
|
@ -9,12 +6,11 @@ import { INotifier } from "./notifiers/INotifier";
|
|||
import { IRegulator } from "./regulation/IRegulator";
|
||||
import { Configuration } from "./configuration/schema/Configuration";
|
||||
import { IAccessController } from "./access_control/IAccessController";
|
||||
import { IUsersDatabase } from "./ldap/IUsersDatabase";
|
||||
|
||||
export interface ServerVariables {
|
||||
logger: IRequestLogger;
|
||||
ldapAuthenticator: IAuthenticator;
|
||||
ldapPasswordUpdater: IPasswordUpdater;
|
||||
ldapEmailsRetriever: IEmailsRetriever;
|
||||
usersDatabase: IUsersDatabase;
|
||||
totpHandler: ITotpHandler;
|
||||
u2f: IU2fHandler;
|
||||
userDataStore: IUserDataStore;
|
||||
|
|
|
@ -7,19 +7,12 @@ import Nodemailer = require("nodemailer");
|
|||
import { IRequestLogger } from "./logging/IRequestLogger";
|
||||
import { RequestLogger } from "./logging/RequestLogger";
|
||||
|
||||
import { IAuthenticator } from "./ldap/IAuthenticator";
|
||||
import { IPasswordUpdater } from "./ldap/IPasswordUpdater";
|
||||
import { IEmailsRetriever } from "./ldap/IEmailsRetriever";
|
||||
import { Authenticator } from "./ldap/Authenticator";
|
||||
import { PasswordUpdater } from "./ldap/PasswordUpdater";
|
||||
import { EmailsRetriever } from "./ldap/EmailsRetriever";
|
||||
import { ClientFactory } from "./ldap/ClientFactory";
|
||||
import { LdapClientFactory } from "./ldap/LdapClientFactory";
|
||||
|
||||
import { TotpHandler } from "./authentication/totp/TotpHandler";
|
||||
import { ITotpHandler } from "./authentication/totp/ITotpHandler";
|
||||
import { NotifierFactory } from "./notifiers/NotifierFactory";
|
||||
import { MailSenderBuilder } from "./notifiers/MailSenderBuilder";
|
||||
import { LdapUsersDatabase } from "./ldap/LdapUsersDatabase";
|
||||
import { ConnectorFactory } from "./ldap/connector/ConnectorFactory";
|
||||
|
||||
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||
import { UserDataStore } from "./storage/UserDataStore";
|
||||
|
@ -39,6 +32,7 @@ import { ServerVariables } from "./ServerVariables";
|
|||
import { MethodCalculator } from "./authentication/MethodCalculator";
|
||||
import { MongoClient } from "./connectors/mongo/MongoClient";
|
||||
import { IGlobalLogger } from "./logging/IGlobalLogger";
|
||||
import { SessionFactory } from "./ldap/SessionFactory";
|
||||
|
||||
class UserDataStoreFactory {
|
||||
static create(config: Configuration.Configuration, globalLogger: IGlobalLogger): BluebirdPromise<UserDataStore> {
|
||||
|
@ -72,13 +66,14 @@ export class ServerVariablesInitializer {
|
|||
|
||||
const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
|
||||
const notifier = NotifierFactory.build(config.notifier, mailSenderBuilder);
|
||||
const ldapClientFactory = new LdapClientFactory(config.ldap, deps.ldapjs);
|
||||
const clientFactory = new ClientFactory(config.ldap, ldapClientFactory,
|
||||
deps.winston);
|
||||
|
||||
const ldapAuthenticator = new Authenticator(config.ldap, clientFactory);
|
||||
const ldapPasswordUpdater = new PasswordUpdater(config.ldap, clientFactory);
|
||||
const ldapEmailsRetriever = new EmailsRetriever(config.ldap, clientFactory);
|
||||
const ldapUsersDatabase = new LdapUsersDatabase(
|
||||
new SessionFactory(
|
||||
config.ldap,
|
||||
new ConnectorFactory(config.ldap, deps.ldapjs),
|
||||
deps.winston
|
||||
),
|
||||
config.ldap
|
||||
);
|
||||
const accessController = new AccessController(config.access_control, deps.winston);
|
||||
const totpHandler = new TotpHandler(deps.speakeasy);
|
||||
|
||||
|
@ -90,9 +85,7 @@ export class ServerVariablesInitializer {
|
|||
const variables: ServerVariables = {
|
||||
accessController: accessController,
|
||||
config: config,
|
||||
ldapAuthenticator: ldapAuthenticator,
|
||||
ldapPasswordUpdater: ldapPasswordUpdater,
|
||||
ldapEmailsRetriever: ldapEmailsRetriever,
|
||||
usersDatabase: ldapUsersDatabase,
|
||||
logger: requestLogger,
|
||||
notifier: notifier,
|
||||
regulator: regulator,
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { ServerVariables } from "./ServerVariables";
|
||||
|
||||
import { Configuration } from "./configuration/schema/Configuration";
|
||||
import { AuthenticatorStub } from "./ldap/AuthenticatorStub.spec";
|
||||
import { EmailsRetrieverStub } from "./ldap/EmailsRetrieverStub.spec";
|
||||
import { PasswordUpdaterStub } from "./ldap/PasswordUpdaterStub.spec";
|
||||
import { UsersDatabaseStub } from "./ldap/UsersDatabaseStub.spec";
|
||||
import { AccessControllerStub } from "./access_control/AccessControllerStub.spec";
|
||||
import { RequestLoggerStub } from "./logging/RequestLoggerStub.spec";
|
||||
import { NotifierStub } from "./notifiers/NotifierStub.spec";
|
||||
|
@ -15,9 +13,7 @@ import { U2fHandlerStub } from "./authentication/u2f/U2fHandlerStub.spec";
|
|||
export interface ServerVariablesMock {
|
||||
accessController: AccessControllerStub;
|
||||
config: Configuration;
|
||||
ldapAuthenticator: AuthenticatorStub;
|
||||
ldapEmailsRetriever: EmailsRetrieverStub;
|
||||
ldapPasswordUpdater: PasswordUpdaterStub;
|
||||
usersDatabase: UsersDatabaseStub;
|
||||
logger: RequestLoggerStub;
|
||||
notifier: NotifierStub;
|
||||
regulator: RegulatorStub;
|
||||
|
@ -64,9 +60,7 @@ export class ServerVariablesMockBuilder {
|
|||
},
|
||||
storage: {}
|
||||
},
|
||||
ldapAuthenticator: new AuthenticatorStub(),
|
||||
ldapEmailsRetriever: new EmailsRetrieverStub(),
|
||||
ldapPasswordUpdater: new PasswordUpdaterStub(),
|
||||
usersDatabase: new UsersDatabaseStub(),
|
||||
logger: new RequestLoggerStub(enableLogging),
|
||||
notifier: new NotifierStub(),
|
||||
regulator: new RegulatorStub(),
|
||||
|
@ -77,9 +71,7 @@ export class ServerVariablesMockBuilder {
|
|||
const vars: ServerVariables = {
|
||||
accessController: mocks.accessController,
|
||||
config: mocks.config,
|
||||
ldapAuthenticator: mocks.ldapAuthenticator,
|
||||
ldapEmailsRetriever: mocks.ldapEmailsRetriever,
|
||||
ldapPasswordUpdater: mocks.ldapPasswordUpdater,
|
||||
usersDatabase: mocks.usersDatabase,
|
||||
logger: mocks.logger,
|
||||
notifier: mocks.notifier,
|
||||
regulator: mocks.regulator,
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
|
||||
import { Authenticator } from "./Authenticator";
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
|
||||
import Sinon = require("sinon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Assert = require("assert");
|
||||
|
||||
import { ClientFactoryStub } from "./ClientFactoryStub.spec";
|
||||
import { ClientStub } from "./ClientStub.spec";
|
||||
|
||||
|
||||
describe("ldap/Authenticator", function () {
|
||||
const USERNAME = "username";
|
||||
const PASSWORD = "password";
|
||||
|
||||
const ADMIN_USER_DN = "cn=admin,dc=example,dc=com";
|
||||
const ADMIN_PASSWORD = "admin_password";
|
||||
|
||||
let clientFactoryStub: ClientFactoryStub;
|
||||
let adminClientStub: ClientStub;
|
||||
let userClientStub: ClientStub;
|
||||
|
||||
let authenticator: Authenticator;
|
||||
let ldapConfig: LdapConfiguration;
|
||||
|
||||
beforeEach(function () {
|
||||
clientFactoryStub = new ClientFactoryStub();
|
||||
adminClientStub = new ClientStub();
|
||||
userClientStub = new ClientStub();
|
||||
|
||||
ldapConfig = {
|
||||
url: "http://localhost:324",
|
||||
additional_users_dn: "ou=users",
|
||||
additional_groups_dn: "ou=groups",
|
||||
base_dn: "dc=example,dc=com",
|
||||
users_filter: "cn={0}",
|
||||
groups_filter: "member={0}",
|
||||
mail_attribute: "mail",
|
||||
group_name_attribute: "cn",
|
||||
user: ADMIN_USER_DN,
|
||||
password: ADMIN_PASSWORD
|
||||
};
|
||||
|
||||
authenticator = new Authenticator(ldapConfig, clientFactoryStub);
|
||||
});
|
||||
|
||||
describe("success", function () {
|
||||
it("should bind the user if good credentials provided", function () {
|
||||
clientFactoryStub.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD)
|
||||
.returns(adminClientStub);
|
||||
clientFactoryStub.createStub.withArgs("cn=" + USERNAME + ",ou=users,dc=example,dc=com", PASSWORD)
|
||||
.returns(userClientStub);
|
||||
|
||||
// admin connects successfully
|
||||
adminClientStub.openStub.returns(BluebirdPromise.resolve());
|
||||
adminClientStub.closeStub.returns(BluebirdPromise.resolve());
|
||||
|
||||
// admin search for user dn of user
|
||||
adminClientStub.searchUserDnStub.withArgs(USERNAME)
|
||||
.returns(BluebirdPromise.resolve("cn=" + USERNAME + ",ou=users,dc=example,dc=com"));
|
||||
|
||||
// user connects successfully
|
||||
userClientStub.openStub.returns(BluebirdPromise.resolve());
|
||||
userClientStub.closeStub.returns(BluebirdPromise.resolve());
|
||||
|
||||
// admin retrieves emails and groups of user
|
||||
adminClientStub.searchEmailsStub.returns(BluebirdPromise.resolve(["group1"]));
|
||||
adminClientStub.searchGroupsStub.returns(BluebirdPromise.resolve(["user@example.com"]));
|
||||
|
||||
return authenticator.authenticate(USERNAME, PASSWORD);
|
||||
});
|
||||
});
|
||||
|
||||
describe("failure", function () {
|
||||
it("should not bind the user if wrong credentials provided", function () {
|
||||
clientFactoryStub.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD)
|
||||
.returns(adminClientStub);
|
||||
clientFactoryStub.createStub.withArgs("cn=" + USERNAME + ",ou=users,dc=example,dc=com", PASSWORD)
|
||||
.returns(userClientStub);
|
||||
|
||||
// admin connects successfully
|
||||
adminClientStub.openStub.returns(BluebirdPromise.resolve());
|
||||
adminClientStub.closeStub.returns(BluebirdPromise.resolve());
|
||||
|
||||
// admin search for user dn of user
|
||||
adminClientStub.searchUserDnStub.withArgs(USERNAME)
|
||||
.returns(BluebirdPromise.resolve("cn=" + USERNAME + ",ou=users,dc=example,dc=com"));
|
||||
|
||||
// user connects successfully
|
||||
userClientStub.openStub.rejects(new Error("Error while binding"));
|
||||
userClientStub.closeStub.returns(BluebirdPromise.resolve());
|
||||
|
||||
return authenticator.authenticate(USERNAME, PASSWORD)
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject("Should not be here!");
|
||||
})
|
||||
.catch(function () {
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not bind the user if search of emails or group fails", function () {
|
||||
clientFactoryStub.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD)
|
||||
.returns(adminClientStub);
|
||||
clientFactoryStub.createStub.withArgs("cn=" + USERNAME + ",ou=users,dc=example,dc=com", PASSWORD)
|
||||
.returns(userClientStub);
|
||||
|
||||
// admin connects successfully
|
||||
adminClientStub.openStub.returns(BluebirdPromise.resolve());
|
||||
adminClientStub.closeStub.returns(BluebirdPromise.resolve());
|
||||
|
||||
// admin search for user dn of user
|
||||
adminClientStub.searchUserDnStub.withArgs(USERNAME)
|
||||
.returns(BluebirdPromise.resolve("cn=" + USERNAME + ",ou=users,dc=example,dc=com"));
|
||||
|
||||
// user connects successfully
|
||||
userClientStub.openStub.returns(BluebirdPromise.resolve());
|
||||
userClientStub.closeStub.returns(BluebirdPromise.resolve());
|
||||
|
||||
adminClientStub.searchEmailsStub.returns(BluebirdPromise.resolve(["group1"]));
|
||||
// admin retrieves emails and groups of user
|
||||
adminClientStub.searchGroupsStub
|
||||
.rejects(new Error("Error while retrieving emails and groups"));
|
||||
|
||||
return authenticator.authenticate(USERNAME, PASSWORD)
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject("Should not be here!");
|
||||
})
|
||||
.catch(function () {
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import exceptions = require("../Exceptions");
|
||||
import ldapjs = require("ldapjs");
|
||||
import { IClient } from "./IClient";
|
||||
import { IClientFactory } from "./IClientFactory";
|
||||
import { GroupsAndEmails } from "./IClient";
|
||||
|
||||
import { IAuthenticator } from "./IAuthenticator";
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
import { EmailsAndGroupsRetriever } from "./EmailsAndGroupsRetriever";
|
||||
|
||||
|
||||
export class Authenticator implements IAuthenticator {
|
||||
private options: LdapConfiguration;
|
||||
private clientFactory: IClientFactory;
|
||||
|
||||
constructor(options: LdapConfiguration, clientFactory: IClientFactory) {
|
||||
this.options = options;
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
authenticate(username: string, password: string): BluebirdPromise<GroupsAndEmails> {
|
||||
const that = this;
|
||||
let userClient: IClient;
|
||||
const adminClient = this.clientFactory.create(this.options.user, this.options.password);
|
||||
const emailsAndGroupsRetriever = new EmailsAndGroupsRetriever(this.options, this.clientFactory);
|
||||
|
||||
return adminClient.open()
|
||||
.then(function () {
|
||||
return adminClient.searchUserDn(username);
|
||||
})
|
||||
.then(function (userDN: string) {
|
||||
userClient = that.clientFactory.create(userDN, password);
|
||||
return userClient.open();
|
||||
})
|
||||
.then(function () {
|
||||
return userClient.close();
|
||||
})
|
||||
.then(function () {
|
||||
return emailsAndGroupsRetriever.retrieve(username);
|
||||
})
|
||||
.then(function (groupsAndEmails: GroupsAndEmails) {
|
||||
return BluebirdPromise.resolve(groupsAndEmails);
|
||||
})
|
||||
.error(function (err: Error) {
|
||||
return BluebirdPromise.reject(new exceptions.LdapError(err.message));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { IAuthenticator } from "../../../src/lib/ldap/IAuthenticator";
|
||||
import { GroupsAndEmails } from "./IClient";
|
||||
import Sinon = require("sinon");
|
||||
|
||||
export class AuthenticatorStub implements IAuthenticator {
|
||||
authenticateStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.authenticateStub = Sinon.stub();
|
||||
}
|
||||
|
||||
authenticate(username: string, password: string): BluebirdPromise<GroupsAndEmails> {
|
||||
return this.authenticateStub(username, password);
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import { IClientFactory } from "./IClientFactory";
|
||||
import { IClient } from "./IClient";
|
||||
import { Client } from "./Client";
|
||||
import { SanitizedClient } from "./SanitizedClient";
|
||||
import { ILdapClientFactory } from "./ILdapClientFactory";
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
import Ldapjs = require("ldapjs");
|
||||
import Winston = require("winston");
|
||||
|
||||
export class ClientFactory implements IClientFactory {
|
||||
private config: LdapConfiguration;
|
||||
private ldapClientFactory: ILdapClientFactory;
|
||||
private logger: typeof Winston;
|
||||
|
||||
constructor(ldapConfiguration: LdapConfiguration,
|
||||
ldapClientFactory: ILdapClientFactory,
|
||||
logger: typeof Winston) {
|
||||
this.config = ldapConfiguration;
|
||||
this.ldapClientFactory = ldapClientFactory;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
create(userDN: string, password: string): IClient {
|
||||
return new SanitizedClient(new Client(userDN, password,
|
||||
this.config, this.ldapClientFactory, this.logger));
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
|
||||
import { IClient } from "../../../src/lib/ldap/IClient";
|
||||
import { IClientFactory } from "../../../src/lib/ldap/IClientFactory";
|
||||
import Sinon = require("sinon");
|
||||
|
||||
export class ClientFactoryStub implements IClientFactory {
|
||||
createStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.createStub = Sinon.stub();
|
||||
}
|
||||
|
||||
create(userDN: string, password: string): IClient {
|
||||
return this.createStub(userDN, password);
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import exceptions = require("../Exceptions");
|
||||
import ldapjs = require("ldapjs");
|
||||
import { Client } from "./Client";
|
||||
import { IClientFactory } from "./IClientFactory";
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
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));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
|
||||
import { EmailsRetriever } from "./EmailsRetriever";
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
|
||||
import Sinon = require("sinon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Assert = require("assert");
|
||||
|
||||
import { ClientFactoryStub } from "./ClientFactoryStub.spec";
|
||||
import { ClientStub } from "./ClientStub.spec";
|
||||
|
||||
describe("ldap/EmailsRetriever", 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 emailsRetriever: EmailsRetriever;
|
||||
let ldapConfig: LdapConfiguration;
|
||||
|
||||
beforeEach(function () {
|
||||
clientFactoryStub = new ClientFactoryStub();
|
||||
adminClientStub = new ClientStub();
|
||||
|
||||
ldapConfig = {
|
||||
url: "http://ldap",
|
||||
user: ADMIN_USER_DN,
|
||||
password: ADMIN_PASSWORD,
|
||||
additional_users_dn: "ou=users",
|
||||
additional_groups_dn: "ou=groups",
|
||||
base_dn: "dc=example,dc=com",
|
||||
group_name_attribute: "cn",
|
||||
groups_filter: "cn={0}",
|
||||
mail_attribute: "mail",
|
||||
users_filter: "cn={0}"
|
||||
};
|
||||
|
||||
emailsRetriever = new EmailsRetriever(ldapConfig, clientFactoryStub);
|
||||
});
|
||||
|
||||
describe("success", function () {
|
||||
it("should retrieve emails 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.searchEmailsStub.withArgs(USERNAME)
|
||||
.returns(BluebirdPromise.resolve(["user@example.com"]));
|
||||
|
||||
return emailsRetriever.retrieve(USERNAME);
|
||||
});
|
||||
});
|
||||
|
||||
describe("failure", function () {
|
||||
it("should fail retrieving emails 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.searchEmailsStub.withArgs(USERNAME)
|
||||
.rejects(new Error("Error while searching emails"));
|
||||
|
||||
return emailsRetriever.retrieve(USERNAME)
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject(new Error("Should not be here"));
|
||||
})
|
||||
.catch(function () {
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,39 +0,0 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import exceptions = require("../Exceptions");
|
||||
import ldapjs = require("ldapjs");
|
||||
import { Client } from "./Client";
|
||||
|
||||
import { IClientFactory } from "./IClientFactory";
|
||||
import { IEmailsRetriever } from "./IEmailsRetriever";
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
|
||||
|
||||
export class EmailsRetriever implements IEmailsRetriever {
|
||||
private options: LdapConfiguration;
|
||||
private clientFactory: IClientFactory;
|
||||
|
||||
constructor(options: LdapConfiguration, clientFactory: IClientFactory) {
|
||||
this.options = options;
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
retrieve(username: string): BluebirdPromise<string[]> {
|
||||
const adminClient = this.clientFactory.create(this.options.user, this.options.password);
|
||||
let emails: string[];
|
||||
|
||||
return adminClient.open()
|
||||
.then(function () {
|
||||
return adminClient.searchEmails(username);
|
||||
})
|
||||
.then(function (emails_: string[]) {
|
||||
emails = emails_;
|
||||
return adminClient.close();
|
||||
})
|
||||
.then(function () {
|
||||
return BluebirdPromise.resolve(emails);
|
||||
})
|
||||
.catch(function (err: Error) {
|
||||
return BluebirdPromise.reject(new exceptions.LdapError("Failed during email retrieval: " + err.message));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { IClient } from "./IClient";
|
||||
import { IEmailsRetriever } from "./IEmailsRetriever";
|
||||
import Sinon = require("sinon");
|
||||
|
||||
export class EmailsRetrieverStub implements IEmailsRetriever {
|
||||
retrieveStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.retrieveStub = Sinon.stub();
|
||||
}
|
||||
|
||||
retrieve(username: string, client?: IClient): BluebirdPromise<string[]> {
|
||||
return this.retrieveStub(username, client);
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { GroupsAndEmails } from "./IClient";
|
||||
|
||||
export interface IAuthenticator {
|
||||
authenticate(username: string, password: string): BluebirdPromise<GroupsAndEmails>;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
import { IClient } from "./IClient";
|
||||
|
||||
export interface IClientFactory {
|
||||
create(userDN: string, password: string): IClient;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { IClient } from "./IClient";
|
||||
|
||||
export interface IEmailsRetriever {
|
||||
retrieve(username: string, client?: IClient): BluebirdPromise<string[]>;
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
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>;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
import { ILdapClient } from "./ILdapClient";
|
||||
|
||||
export interface ILdapClientFactory {
|
||||
create(): ILdapClient;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
export interface IPasswordUpdater {
|
||||
updatePassword(username: string, newPassword: string): BluebirdPromise<void>;
|
||||
}
|
|
@ -6,9 +6,10 @@ export interface GroupsAndEmails {
|
|||
emails: string[];
|
||||
}
|
||||
|
||||
export interface IClient {
|
||||
export interface ISession {
|
||||
open(): BluebirdPromise<void>;
|
||||
close(): BluebirdPromise<void>;
|
||||
|
||||
searchUserDn(username: string): BluebirdPromise<string>;
|
||||
searchEmails(username: string): BluebirdPromise<string[]>;
|
||||
searchGroups(username: string): BluebirdPromise<string[]>;
|
6
server/src/lib/ldap/ISessionFactory.ts
Normal file
6
server/src/lib/ldap/ISessionFactory.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
import { ISession } from "./ISession";
|
||||
|
||||
export interface ISessionFactory {
|
||||
create(userDN: string, password: string): ISession;
|
||||
}
|
9
server/src/lib/ldap/IUsersDatabase.ts
Normal file
9
server/src/lib/ldap/IUsersDatabase.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import { GroupsAndEmails } from "./ISession";
|
||||
|
||||
export interface IUsersDatabase {
|
||||
checkUserPassword(username: string, password: string): Bluebird<GroupsAndEmails>;
|
||||
getEmails(username: string): Bluebird<string[]>;
|
||||
getGroups(username: string): Bluebird<string[]>;
|
||||
updatePassword(username: string, newPassword: string): Bluebird<void>;
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
import Assert = require("assert");
|
||||
import { InputsSanitizer } from "./InputsSanitizer";
|
||||
|
||||
describe("ldap/InputsSanitizer", function () {
|
||||
it("should fail when special characters are used", function () {
|
||||
Assert.throws(() => { InputsSanitizer.sanitize("ab,c"); }, Error);
|
||||
Assert.throws(() => { InputsSanitizer.sanitize("a\\bc"); }, Error);
|
||||
Assert.throws(() => { InputsSanitizer.sanitize("a'bc"); }, Error);
|
||||
Assert.throws(() => { InputsSanitizer.sanitize("a#bc"); }, Error);
|
||||
Assert.throws(() => { InputsSanitizer.sanitize("a+bc"); }, Error);
|
||||
Assert.throws(() => { InputsSanitizer.sanitize("a<bc"); }, Error);
|
||||
Assert.throws(() => { InputsSanitizer.sanitize("a>bc"); }, Error);
|
||||
Assert.throws(() => { InputsSanitizer.sanitize("a;bc"); }, Error);
|
||||
Assert.throws(() => { InputsSanitizer.sanitize("a\"bc"); }, Error);
|
||||
Assert.throws(() => { InputsSanitizer.sanitize("a=bc"); }, Error);
|
||||
});
|
||||
|
||||
it("should return original string", function () {
|
||||
Assert.equal(InputsSanitizer.sanitize("abcdef"), "abcdef");
|
||||
});
|
||||
|
||||
it("should trim", function () {
|
||||
Assert.throws(() => { InputsSanitizer.sanitize(" abc "); }, Error);
|
||||
});
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
import { ILdapClientFactory } from "./ILdapClientFactory";
|
||||
import { ILdapClient } from "./ILdapClient";
|
||||
import { LdapClient } from "./LdapClient";
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
386
server/src/lib/ldap/LdapUsersDatabase.spec.ts
Normal file
386
server/src/lib/ldap/LdapUsersDatabase.spec.ts
Normal file
|
@ -0,0 +1,386 @@
|
|||
import Assert = require("assert");
|
||||
import Bluebird = require("bluebird");
|
||||
|
||||
import { LdapUsersDatabase } from "./LdapUsersDatabase";
|
||||
|
||||
import { SessionFactoryStub } from "./SessionFactoryStub.spec";
|
||||
import { SessionStub } from "./SessionStub.spec";
|
||||
|
||||
const ADMIN_USER_DN = "cn=admin,dc=example,dc=com";
|
||||
const ADMIN_PASSWORD = "password";
|
||||
|
||||
describe("ldap/connector/LdapUsersDatabase", function() {
|
||||
let sessionFactory: SessionFactoryStub;
|
||||
let usersDatabase: LdapUsersDatabase;
|
||||
|
||||
const USERNAME = "user";
|
||||
const PASSWORD = "pass";
|
||||
const NEW_PASSWORD = "pass2";
|
||||
|
||||
const LDAP_CONFIG = {
|
||||
url: "http://localhost:324",
|
||||
additional_users_dn: "ou=users",
|
||||
additional_groups_dn: "ou=groups",
|
||||
base_dn: "dc=example,dc=com",
|
||||
users_filter: "cn={0}",
|
||||
groups_filter: "member={0}",
|
||||
mail_attribute: "mail",
|
||||
group_name_attribute: "cn",
|
||||
user: ADMIN_USER_DN,
|
||||
password: ADMIN_PASSWORD
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
sessionFactory = new SessionFactoryStub();
|
||||
usersDatabase = new LdapUsersDatabase(sessionFactory, LDAP_CONFIG);
|
||||
})
|
||||
|
||||
describe("checkUserPassword", function() {
|
||||
it("should return groups and emails when user/password matches", function() {
|
||||
const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
|
||||
const emails = ["email1", "email2"];
|
||||
const groups = ["group1", "group2"];
|
||||
|
||||
const adminSession = new SessionStub();
|
||||
const userSession = new SessionStub();
|
||||
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
|
||||
sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
|
||||
|
||||
adminSession.openStub.returns(Bluebird.resolve());
|
||||
adminSession.closeStub.returns(Bluebird.resolve());
|
||||
adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
|
||||
adminSession.searchEmailsStub.withArgs(USERNAME).returns(Bluebird.resolve(emails));
|
||||
adminSession.searchGroupsStub.withArgs(USERNAME).returns(Bluebird.resolve(groups));
|
||||
|
||||
userSession.openStub.returns(Bluebird.resolve());
|
||||
userSession.closeStub.returns(Bluebird.resolve());
|
||||
|
||||
return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
|
||||
.then((groupsAndEmails) => {
|
||||
Assert.deepEqual(groupsAndEmails.groups, groups);
|
||||
Assert.deepEqual(groupsAndEmails.emails, emails);
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when username/password is wrong", function() {
|
||||
const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
|
||||
|
||||
const adminSession = new SessionStub();
|
||||
const userSession = new SessionStub();
|
||||
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
|
||||
sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
|
||||
|
||||
adminSession.openStub.returns(Bluebird.resolve());
|
||||
adminSession.closeStub.returns(Bluebird.resolve());
|
||||
adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
|
||||
|
||||
userSession.openStub.returns(Bluebird.reject(new Error("Failed binding")));
|
||||
userSession.closeStub.returns(Bluebird.resolve());
|
||||
|
||||
return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
|
||||
.then(() => Bluebird.reject(new Error("should not be here")))
|
||||
.catch((err) => {
|
||||
Assert(userSession.closeStub.called);
|
||||
Assert(adminSession.closeStub.called);
|
||||
return Bluebird.resolve();
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when admin binding fails", function() {
|
||||
const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
|
||||
|
||||
const adminSession = new SessionStub();
|
||||
const userSession = new SessionStub();
|
||||
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
|
||||
sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
|
||||
|
||||
adminSession.openStub.returns(Bluebird.reject(new Error("Failed binding")));
|
||||
adminSession.closeStub.returns(Bluebird.resolve());
|
||||
adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
|
||||
|
||||
return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
|
||||
.then(() => Bluebird.reject(new Error("should not be here")))
|
||||
.catch((err) => {
|
||||
Assert(userSession.closeStub.notCalled);
|
||||
Assert(adminSession.closeStub.called);
|
||||
return Bluebird.resolve();
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when search for user dn fails", function() {
|
||||
const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
|
||||
|
||||
const adminSession = new SessionStub();
|
||||
const userSession = new SessionStub();
|
||||
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
|
||||
sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
|
||||
|
||||
adminSession.openStub.returns(Bluebird.resolve());
|
||||
adminSession.closeStub.returns(Bluebird.resolve());
|
||||
adminSession.searchUserDnStub.returns(Bluebird.reject(new Error("Failed searching user dn")));
|
||||
|
||||
return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
|
||||
.then(() => Bluebird.reject(new Error("should not be here")))
|
||||
.catch((err) => {
|
||||
Assert(userSession.closeStub.notCalled);
|
||||
Assert(adminSession.closeStub.called);
|
||||
return Bluebird.resolve();
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when groups retrieval fails", function() {
|
||||
const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
|
||||
const emails = ["email1", "email2"];
|
||||
const groups = ["group1", "group2"];
|
||||
|
||||
const adminSession = new SessionStub();
|
||||
const userSession = new SessionStub();
|
||||
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
|
||||
sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
|
||||
|
||||
adminSession.openStub.returns(Bluebird.resolve());
|
||||
adminSession.closeStub.returns(Bluebird.resolve());
|
||||
adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
|
||||
adminSession.searchEmailsStub.withArgs(USERNAME)
|
||||
.returns(Bluebird.resolve(emails));
|
||||
adminSession.searchGroupsStub.withArgs(USERNAME)
|
||||
.returns(Bluebird.reject(new Error("Failed retrieving groups")));
|
||||
|
||||
userSession.openStub.returns(Bluebird.resolve());
|
||||
userSession.closeStub.returns(Bluebird.resolve());
|
||||
|
||||
return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
|
||||
.then((groupsAndEmails) => Bluebird.reject(new Error("should not be here")))
|
||||
.catch((err) => {
|
||||
Assert(userSession.closeStub.called);
|
||||
Assert(adminSession.closeStub.called);
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when emails retrieval fails", function() {
|
||||
const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
|
||||
const emails = ["email1", "email2"];
|
||||
const groups = ["group1", "group2"];
|
||||
|
||||
const adminSession = new SessionStub();
|
||||
const userSession = new SessionStub();
|
||||
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
|
||||
sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
|
||||
|
||||
adminSession.openStub.returns(Bluebird.resolve());
|
||||
adminSession.closeStub.returns(Bluebird.resolve());
|
||||
adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
|
||||
adminSession.searchEmailsStub.withArgs(USERNAME)
|
||||
.returns(Bluebird.reject(new Error("Emails retrieval failed")));
|
||||
adminSession.searchGroupsStub.withArgs(USERNAME)
|
||||
.returns(Bluebird.resolve(groups));
|
||||
|
||||
userSession.openStub.returns(Bluebird.resolve());
|
||||
userSession.closeStub.returns(Bluebird.resolve());
|
||||
|
||||
return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
|
||||
.then((groupsAndEmails) => Bluebird.reject(new Error("should not be here")))
|
||||
.catch((err) => {
|
||||
Assert(userSession.closeStub.called);
|
||||
Assert(adminSession.closeStub.called);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
describe("getEmails", function() {
|
||||
it("should succefully retrieves email", () => {
|
||||
const emails = ["email1", "email2"];
|
||||
const session = new SessionStub();
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
|
||||
|
||||
session.openStub.returns(Bluebird.resolve());
|
||||
session.closeStub.returns(Bluebird.resolve());
|
||||
session.searchEmailsStub.returns(Bluebird.resolve(emails));
|
||||
|
||||
return usersDatabase.getEmails(USERNAME)
|
||||
.then((foundEmails) => {
|
||||
Assert(session.closeStub.called);
|
||||
Assert.deepEqual(foundEmails, emails);
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when binding fails", () => {
|
||||
const emails = ["email1", "email2"];
|
||||
const session = new SessionStub();
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
|
||||
|
||||
session.openStub.returns(Bluebird.reject(new Error("Binding failed")));
|
||||
|
||||
return usersDatabase.getEmails(USERNAME)
|
||||
.then(() => Bluebird.reject(new Error("should not be here")))
|
||||
.catch((err) => {
|
||||
Assert(session.closeStub.called);
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when unbinding fails", () => {
|
||||
const emails = ["email1", "email2"];
|
||||
const session = new SessionStub();
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
|
||||
|
||||
session.openStub.returns(Bluebird.resolve());
|
||||
session.searchEmailsStub.returns(Bluebird.resolve(emails));
|
||||
session.closeStub.returns(Bluebird.reject(new Error("Unbinding failed")));
|
||||
|
||||
return usersDatabase.getEmails(USERNAME)
|
||||
.then(() => Bluebird.reject(new Error("should not be here")))
|
||||
.catch((err) => {
|
||||
Assert(session.closeStub.called);
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when search fails", () => {
|
||||
const emails = ["email1", "email2"];
|
||||
const session = new SessionStub();
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
|
||||
|
||||
session.openStub.returns(Bluebird.resolve());
|
||||
session.searchEmailsStub.returns(Bluebird.reject(new Error("Search failed")));
|
||||
session.closeStub.returns(Bluebird.resolve());
|
||||
|
||||
return usersDatabase.getEmails(USERNAME)
|
||||
.then(() => Bluebird.reject(new Error("should not be here")))
|
||||
.catch((err) => {
|
||||
Assert(session.closeStub.called);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("getGroups", function() {
|
||||
it("should succefully retrieves groups", () => {
|
||||
const groups = ["group1", "group2"];
|
||||
const session = new SessionStub();
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
|
||||
|
||||
session.openStub.returns(Bluebird.resolve());
|
||||
session.closeStub.returns(Bluebird.resolve());
|
||||
session.searchGroupsStub.returns(Bluebird.resolve(groups));
|
||||
|
||||
return usersDatabase.getGroups(USERNAME)
|
||||
.then((foundGroups) => {
|
||||
Assert(session.closeStub.called);
|
||||
Assert.deepEqual(foundGroups, groups);
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when binding fails", () => {
|
||||
const session = new SessionStub();
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
|
||||
|
||||
session.openStub.returns(Bluebird.reject(new Error("Binding failed")));
|
||||
|
||||
return usersDatabase.getGroups(USERNAME)
|
||||
.then(() => Bluebird.reject(new Error("should not be here")))
|
||||
.catch((err) => {
|
||||
Assert(session.closeStub.called);
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when unbinding fails", () => {
|
||||
const groups = ["group1", "group2"];
|
||||
const session = new SessionStub();
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
|
||||
|
||||
session.openStub.returns(Bluebird.resolve());
|
||||
session.searchGroupsStub.returns(Bluebird.resolve(groups));
|
||||
session.closeStub.returns(Bluebird.reject(new Error("Unbinding failed")));
|
||||
|
||||
return usersDatabase.getGroups(USERNAME)
|
||||
.then(() => Bluebird.reject(new Error("should not be here")))
|
||||
.catch((err) => {
|
||||
Assert(session.closeStub.called);
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when search fails", () => {
|
||||
const groups = ["group1", "group2"];
|
||||
const session = new SessionStub();
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
|
||||
|
||||
session.openStub.returns(Bluebird.resolve());
|
||||
session.searchGroupsStub.returns(Bluebird.reject(new Error("Search failed")));
|
||||
session.closeStub.returns(Bluebird.resolve());
|
||||
|
||||
return usersDatabase.getGroups(USERNAME)
|
||||
.then(() => Bluebird.reject(new Error("should not be here")))
|
||||
.catch((err) => {
|
||||
Assert(session.closeStub.called);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("updatePassword", function() {
|
||||
it("should successfully update password", () => {
|
||||
const session = new SessionStub();
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
|
||||
|
||||
session.openStub.returns(Bluebird.resolve());
|
||||
session.closeStub.returns(Bluebird.resolve());
|
||||
session.modifyPasswordStub.returns(Bluebird.resolve());
|
||||
|
||||
return usersDatabase.updatePassword(USERNAME, NEW_PASSWORD)
|
||||
.then(() => {
|
||||
Assert(session.modifyPasswordStub.calledWith(USERNAME, NEW_PASSWORD));
|
||||
Assert(session.closeStub.called);
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when binding fails", () => {
|
||||
const session = new SessionStub();
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
|
||||
|
||||
session.openStub.returns(Bluebird.reject(new Error("Binding failed")));
|
||||
session.closeStub.returns(Bluebird.resolve());
|
||||
session.modifyPasswordStub.returns(Bluebird.resolve());
|
||||
|
||||
return usersDatabase.updatePassword(USERNAME, NEW_PASSWORD)
|
||||
.then(() => Bluebird.reject(new Error("should not be here")))
|
||||
.catch(() => {
|
||||
Assert(session.closeStub.called);
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when update fails", () => {
|
||||
const session = new SessionStub();
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
|
||||
|
||||
session.openStub.returns(Bluebird.resolve());
|
||||
session.closeStub.returns(Bluebird.reject(new Error("Update failed")));
|
||||
session.modifyPasswordStub.returns(Bluebird.resolve());
|
||||
|
||||
return usersDatabase.updatePassword(USERNAME, NEW_PASSWORD)
|
||||
.then(() => Bluebird.reject(new Error("should not be here")))
|
||||
.catch(() => {
|
||||
Assert(session.closeStub.called);
|
||||
})
|
||||
});
|
||||
|
||||
it("should fail when unbind fails", () => {
|
||||
const session = new SessionStub();
|
||||
sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
|
||||
|
||||
session.openStub.returns(Bluebird.resolve());
|
||||
session.closeStub.returns(Bluebird.resolve());
|
||||
session.modifyPasswordStub.returns(Bluebird.reject(new Error("Unbind failed")));
|
||||
|
||||
return usersDatabase.updatePassword(USERNAME, NEW_PASSWORD)
|
||||
.then(() => Bluebird.reject(new Error("should not be here")))
|
||||
.catch(() => {
|
||||
Assert(session.closeStub.called);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
106
server/src/lib/ldap/LdapUsersDatabase.ts
Normal file
106
server/src/lib/ldap/LdapUsersDatabase.ts
Normal file
|
@ -0,0 +1,106 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import { IUsersDatabase } from "./IUsersDatabase";
|
||||
import { ISessionFactory } from "./ISessionFactory";
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
import { ISession, GroupsAndEmails } from "./ISession";
|
||||
import Exceptions = require("../Exceptions");
|
||||
|
||||
type SessionCallback<T> = (session: ISession) => Bluebird<T>;
|
||||
|
||||
export class LdapUsersDatabase implements IUsersDatabase {
|
||||
private sessionFactory: ISessionFactory;
|
||||
private configuration: LdapConfiguration;
|
||||
|
||||
constructor(
|
||||
sessionFactory: ISessionFactory,
|
||||
configuration: LdapConfiguration) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
private withSession<T>(
|
||||
username: string,
|
||||
password: string,
|
||||
cb: SessionCallback<T>): Bluebird<T> {
|
||||
const session = this.sessionFactory.create(username, password);
|
||||
return session.open()
|
||||
.then(() => cb(session))
|
||||
.finally(() => session.close());
|
||||
}
|
||||
|
||||
checkUserPassword(username: string, password: string): Bluebird<GroupsAndEmails> {
|
||||
const that = this;
|
||||
function verifyUserPassword(userDN: string) {
|
||||
return that.withSession<void>(
|
||||
userDN,
|
||||
password,
|
||||
(session) => Bluebird.resolve()
|
||||
);
|
||||
}
|
||||
|
||||
function getInfo(session: ISession) {
|
||||
return Bluebird.join(
|
||||
session.searchGroups(username),
|
||||
session.searchEmails(username)
|
||||
)
|
||||
.spread((groups: string[], emails: string[]) => {
|
||||
return { groups: groups, emails: emails };
|
||||
});
|
||||
}
|
||||
|
||||
return that.withSession(
|
||||
that.configuration.user,
|
||||
that.configuration.password,
|
||||
(session) => {
|
||||
return session.searchUserDn(username)
|
||||
.then(verifyUserPassword)
|
||||
.then(() => getInfo(session));
|
||||
})
|
||||
.catch((err) =>
|
||||
Bluebird.reject(new Exceptions.LdapError(err.message)));
|
||||
}
|
||||
|
||||
getEmails(username: string): Bluebird<string[]> {
|
||||
const that = this;
|
||||
return that.withSession(
|
||||
that.configuration.user,
|
||||
that.configuration.password,
|
||||
(session) => {
|
||||
return session.searchEmails(username);
|
||||
}
|
||||
)
|
||||
.catch((err) =>
|
||||
Bluebird.reject(new Exceptions.LdapError("Failed during email retrieval: " + err.message))
|
||||
);
|
||||
}
|
||||
|
||||
getGroups(username: string): Bluebird<string[]> {
|
||||
const that = this;
|
||||
return that.withSession(
|
||||
that.configuration.user,
|
||||
that.configuration.password,
|
||||
(session) => {
|
||||
return session.searchGroups(username);
|
||||
}
|
||||
)
|
||||
.catch((err) =>
|
||||
Bluebird.reject(new Exceptions.LdapError("Failed during email retrieval: " + err.message))
|
||||
);
|
||||
}
|
||||
|
||||
updatePassword(username: string, newPassword: string): Bluebird<void> {
|
||||
const that = this;
|
||||
return that.withSession(
|
||||
that.configuration.user,
|
||||
that.configuration.password,
|
||||
(session) => {
|
||||
return session.modifyPassword(username, newPassword);
|
||||
}
|
||||
)
|
||||
.catch(function (err: Error) {
|
||||
return Bluebird.reject(
|
||||
new Exceptions.LdapError(
|
||||
"Error while updating password: " + err.message));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
import Sinon = require("sinon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Assert = require("assert");
|
||||
import { PasswordUpdater } from "./PasswordUpdater";
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
import { ClientFactoryStub } from "./ClientFactoryStub.spec";
|
||||
import { ClientStub } from "./ClientStub.spec";
|
||||
import { HashGenerator } from "../utils/HashGenerator";
|
||||
|
||||
describe("ldap/PasswordUpdater", function () {
|
||||
const USERNAME = "username";
|
||||
const NEW_PASSWORD = "new-password";
|
||||
|
||||
const ADMIN_USER_DN = "cn=admin,dc=example,dc=com";
|
||||
const ADMIN_PASSWORD = "password";
|
||||
|
||||
let clientFactoryStub: ClientFactoryStub;
|
||||
let adminClientStub: ClientStub;
|
||||
let passwordUpdater: PasswordUpdater;
|
||||
let ldapConfig: LdapConfiguration;
|
||||
let ssha512HashGenerator: Sinon.SinonStub;
|
||||
|
||||
beforeEach(function () {
|
||||
clientFactoryStub = new ClientFactoryStub();
|
||||
adminClientStub = new ClientStub();
|
||||
|
||||
ldapConfig = {
|
||||
url: "http://ldap",
|
||||
user: ADMIN_USER_DN,
|
||||
password: ADMIN_PASSWORD,
|
||||
additional_users_dn: "ou=users",
|
||||
additional_groups_dn: "ou=groups",
|
||||
base_dn: "dc=example,dc=com",
|
||||
group_name_attribute: "cn",
|
||||
groups_filter: "cn={0}",
|
||||
mail_attribute: "mail",
|
||||
users_filter: "cn={0}"
|
||||
};
|
||||
|
||||
ssha512HashGenerator = Sinon.stub(HashGenerator, "ssha512");
|
||||
clientFactoryStub.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD)
|
||||
.returns(adminClientStub);
|
||||
|
||||
passwordUpdater = new PasswordUpdater(ldapConfig, clientFactoryStub);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
ssha512HashGenerator.restore();
|
||||
});
|
||||
|
||||
describe("success", function () {
|
||||
it("should update the password successfully", function () {
|
||||
ssha512HashGenerator
|
||||
.returns("{CRYPT}$6$abcdefghijklm$AQmxaKfobGY9HSQa6aDYkAWOgPGNhGYn");
|
||||
adminClientStub.modifyPasswordStub.withArgs(USERNAME, NEW_PASSWORD)
|
||||
.returns(BluebirdPromise.resolve());
|
||||
adminClientStub.openStub.returns(BluebirdPromise.resolve());
|
||||
adminClientStub.closeStub.returns(BluebirdPromise.resolve());
|
||||
|
||||
return passwordUpdater.updatePassword(USERNAME, NEW_PASSWORD);
|
||||
});
|
||||
});
|
||||
|
||||
describe("failure", function () {
|
||||
it("should fail updating password when modify operation fails",
|
||||
function () {
|
||||
ssha512HashGenerator
|
||||
.returns("{CRYPT}$6$abcdefghijklm$AQmxaKfobGY9HSQa6aDYkAWOgPGNhGYn");
|
||||
adminClientStub.modifyPasswordStub.withArgs(USERNAME, NEW_PASSWORD)
|
||||
.rejects(new Error("Error while updating password"));
|
||||
adminClientStub.openStub.returns(BluebirdPromise.resolve());
|
||||
adminClientStub.closeStub.returns(BluebirdPromise.resolve());
|
||||
|
||||
return passwordUpdater.updatePassword(USERNAME, NEW_PASSWORD)
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject(new Error("should not be here"));
|
||||
})
|
||||
.catch(function(err: Error) {
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import exceptions = require("../Exceptions");
|
||||
import ldapjs = require("ldapjs");
|
||||
import { Client } from "./Client";
|
||||
|
||||
import { IPasswordUpdater } from "./IPasswordUpdater";
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
import { IClientFactory } from "./IClientFactory";
|
||||
|
||||
|
||||
export class PasswordUpdater implements IPasswordUpdater {
|
||||
private options: LdapConfiguration;
|
||||
private clientFactory: IClientFactory;
|
||||
|
||||
constructor(options: LdapConfiguration, clientFactory: IClientFactory) {
|
||||
this.options = options;
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
updatePassword(username: string, newPassword: string)
|
||||
: BluebirdPromise<void> {
|
||||
const adminClient = this.clientFactory.create(this.options.user,
|
||||
this.options.password);
|
||||
|
||||
return adminClient.open()
|
||||
.then(function () {
|
||||
return adminClient.modifyPassword(username, newPassword);
|
||||
})
|
||||
.then(function () {
|
||||
return adminClient.close();
|
||||
})
|
||||
.catch(function (err: Error) {
|
||||
return BluebirdPromise.reject(
|
||||
new exceptions.LdapError(
|
||||
"Error while updating password: " + err.message));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { IClient } from "./IClient";
|
||||
import { IPasswordUpdater } from "./IPasswordUpdater";
|
||||
import Sinon = require("sinon");
|
||||
|
||||
export class PasswordUpdaterStub implements IPasswordUpdater {
|
||||
updatePasswordStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.updatePasswordStub = Sinon.stub();
|
||||
}
|
||||
|
||||
updatePassword(username: string, newPassword: string): BluebirdPromise<void> {
|
||||
return this.updatePasswordStub(username, newPassword);
|
||||
}
|
||||
}
|
|
@ -1,17 +1,17 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { ClientStub } from "./ClientStub.spec";
|
||||
import { SanitizedClient } from "./SanitizedClient";
|
||||
import { SessionStub } from "./SessionStub.spec";
|
||||
import { SafeSession } from "./SafeSession";
|
||||
|
||||
describe("ldap/SanitizedClient", function () {
|
||||
let client: SanitizedClient;
|
||||
let client: SafeSession;
|
||||
|
||||
beforeEach(function () {
|
||||
const clientStub = new ClientStub();
|
||||
const clientStub = new SessionStub();
|
||||
clientStub.searchUserDnStub.onCall(0).returns(BluebirdPromise.resolve());
|
||||
clientStub.searchGroupsStub.onCall(0).returns(BluebirdPromise.resolve());
|
||||
clientStub.searchEmailsStub.onCall(0).returns(BluebirdPromise.resolve());
|
||||
clientStub.modifyPasswordStub.onCall(0).returns(BluebirdPromise.resolve());
|
||||
client = new SanitizedClient(clientStub);
|
||||
client = new SafeSession(clientStub);
|
||||
});
|
||||
|
||||
describe("special chars are used", function () {
|
|
@ -1,30 +1,29 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { IClient, GroupsAndEmails } from "./IClient";
|
||||
import { Client } from "./Client";
|
||||
import { InputsSanitizer } from "./InputsSanitizer";
|
||||
import { ISession, GroupsAndEmails } from "./ISession";
|
||||
import { Sanitizer } from "./Sanitizer";
|
||||
|
||||
const SPECIAL_CHAR_USED_MESSAGE = "Special character used in LDAP query.";
|
||||
|
||||
|
||||
export class SanitizedClient implements IClient {
|
||||
private client: IClient;
|
||||
export class SafeSession implements ISession {
|
||||
private sesion: ISession;
|
||||
|
||||
constructor(client: IClient) {
|
||||
this.client = client;
|
||||
constructor(sesion: ISession) {
|
||||
this.sesion = sesion;
|
||||
}
|
||||
|
||||
open(): BluebirdPromise<void> {
|
||||
return this.client.open();
|
||||
return this.sesion.open();
|
||||
}
|
||||
|
||||
close(): BluebirdPromise<void> {
|
||||
return this.client.close();
|
||||
return this.sesion.close();
|
||||
}
|
||||
|
||||
searchGroups(username: string): BluebirdPromise<string[]> {
|
||||
try {
|
||||
const sanitizedUsername = InputsSanitizer.sanitize(username);
|
||||
return this.client.searchGroups(sanitizedUsername);
|
||||
const sanitizedUsername = Sanitizer.sanitize(username);
|
||||
return this.sesion.searchGroups(sanitizedUsername);
|
||||
}
|
||||
catch (e) {
|
||||
return BluebirdPromise.reject(new Error(SPECIAL_CHAR_USED_MESSAGE));
|
||||
|
@ -33,8 +32,8 @@ export class SanitizedClient implements IClient {
|
|||
|
||||
searchUserDn(username: string): BluebirdPromise<string> {
|
||||
try {
|
||||
const sanitizedUsername = InputsSanitizer.sanitize(username);
|
||||
return this.client.searchUserDn(sanitizedUsername);
|
||||
const sanitizedUsername = Sanitizer.sanitize(username);
|
||||
return this.sesion.searchUserDn(sanitizedUsername);
|
||||
}
|
||||
catch (e) {
|
||||
return BluebirdPromise.reject(new Error(SPECIAL_CHAR_USED_MESSAGE));
|
||||
|
@ -43,8 +42,8 @@ export class SanitizedClient implements IClient {
|
|||
|
||||
searchEmails(username: string): BluebirdPromise<string[]> {
|
||||
try {
|
||||
const sanitizedUsername = InputsSanitizer.sanitize(username);
|
||||
return this.client.searchEmails(sanitizedUsername);
|
||||
const sanitizedUsername = Sanitizer.sanitize(username);
|
||||
return this.sesion.searchEmails(sanitizedUsername);
|
||||
}
|
||||
catch (e) {
|
||||
return BluebirdPromise.reject(new Error(SPECIAL_CHAR_USED_MESSAGE));
|
||||
|
@ -53,8 +52,8 @@ export class SanitizedClient implements IClient {
|
|||
|
||||
modifyPassword(username: string, newPassword: string): BluebirdPromise<void> {
|
||||
try {
|
||||
const sanitizedUsername = InputsSanitizer.sanitize(username);
|
||||
return this.client.modifyPassword(sanitizedUsername, newPassword);
|
||||
const sanitizedUsername = Sanitizer.sanitize(username);
|
||||
return this.sesion.modifyPassword(sanitizedUsername, newPassword);
|
||||
}
|
||||
catch (e) {
|
||||
return BluebirdPromise.reject(new Error(SPECIAL_CHAR_USED_MESSAGE));
|
25
server/src/lib/ldap/Sanitizer.spec.ts
Normal file
25
server/src/lib/ldap/Sanitizer.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import Assert = require("assert");
|
||||
import { Sanitizer } from "./Sanitizer";
|
||||
|
||||
describe("ldap/InputsSanitizer", function () {
|
||||
it("should fail when special characters are used", function () {
|
||||
Assert.throws(() => { Sanitizer.sanitize("ab,c"); }, Error);
|
||||
Assert.throws(() => { Sanitizer.sanitize("a\\bc"); }, Error);
|
||||
Assert.throws(() => { Sanitizer.sanitize("a'bc"); }, Error);
|
||||
Assert.throws(() => { Sanitizer.sanitize("a#bc"); }, Error);
|
||||
Assert.throws(() => { Sanitizer.sanitize("a+bc"); }, Error);
|
||||
Assert.throws(() => { Sanitizer.sanitize("a<bc"); }, Error);
|
||||
Assert.throws(() => { Sanitizer.sanitize("a>bc"); }, Error);
|
||||
Assert.throws(() => { Sanitizer.sanitize("a;bc"); }, Error);
|
||||
Assert.throws(() => { Sanitizer.sanitize("a\"bc"); }, Error);
|
||||
Assert.throws(() => { Sanitizer.sanitize("a=bc"); }, Error);
|
||||
});
|
||||
|
||||
it("should return original string", function () {
|
||||
Assert.equal(Sanitizer.sanitize("abcdef"), "abcdef");
|
||||
});
|
||||
|
||||
it("should trim", function () {
|
||||
Assert.throws(() => { Sanitizer.sanitize(" abc "); }, Error);
|
||||
});
|
||||
});
|
|
@ -11,7 +11,7 @@ function containsOneOf(s: string, characters: string[]) {
|
|||
.reduce((acc: boolean, current: boolean) => { return acc || current; }, false);
|
||||
}
|
||||
|
||||
export class InputsSanitizer {
|
||||
export class Sanitizer {
|
||||
static sanitize(input: string): string {
|
||||
const forbiddenChars = [",", "\\", "'", "#", "+", "<", ">", ";", "\"", "="];
|
||||
if (containsOneOf(input, forbiddenChars))
|
|
@ -1,15 +1,15 @@
|
|||
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
import { Client } from "./Client";
|
||||
import { LdapClientFactoryStub } from "./LdapClientFactoryStub.spec";
|
||||
import { LdapClientStub } from "./LdapClientStub.spec";
|
||||
import { Session } from "./Session";
|
||||
import { ConnectorFactoryStub } from "./connector/ConnectorFactoryStub.spec";
|
||||
import { ConnectorStub } from "./connector/ConnectorStub.spec";
|
||||
|
||||
import Sinon = require("sinon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Assert = require("assert");
|
||||
import Winston = require("winston");
|
||||
|
||||
describe("ldap/Client", function () {
|
||||
describe("ldap/Session", function () {
|
||||
const USERNAME = "username";
|
||||
const ADMIN_USER_DN = "cn=admin,dc=example,dc=com";
|
||||
const ADMIN_PASSWORD = "password";
|
||||
|
@ -27,18 +27,15 @@ describe("ldap/Client", function () {
|
|||
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([{
|
||||
const connectorStub = new ConnectorStub();
|
||||
connectorStub.searchAsyncStub.returns(BluebirdPromise.resolve([{
|
||||
cn: "group1"
|
||||
}]));
|
||||
const client = new Client(ADMIN_USER_DN, ADMIN_PASSWORD, options, factory, Winston);
|
||||
const client = new Session(ADMIN_USER_DN, ADMIN_PASSWORD, options, connectorStub, Winston);
|
||||
|
||||
return client.searchGroups("user1")
|
||||
.then(function () {
|
||||
Assert.equal(ldapClient.searchAsyncStub.getCall(0).args[1].filter,
|
||||
Assert.equal(connectorStub.searchAsyncStub.getCall(0).args[1].filter,
|
||||
"member=cn=user1,ou=users,dc=example,dc=com");
|
||||
});
|
||||
});
|
||||
|
@ -57,10 +54,7 @@ describe("ldap/Client", function () {
|
|||
user: "cn=admin,dc=example,dc=com",
|
||||
password: "password"
|
||||
};
|
||||
const factory = new LdapClientFactoryStub();
|
||||
const ldapClient = new LdapClientStub();
|
||||
|
||||
factory.createStub.returns(ldapClient);
|
||||
const ldapClient = new ConnectorStub();
|
||||
|
||||
// Retrieve user DN
|
||||
ldapClient.searchAsyncStub.withArgs("ou=users,dc=example,dc=com", {
|
||||
|
@ -81,7 +75,7 @@ describe("ldap/Client", function () {
|
|||
cn: "group1"
|
||||
}]));
|
||||
|
||||
const client = new Client(ADMIN_USER_DN, ADMIN_PASSWORD, options, factory, Winston);
|
||||
const client = new Session(ADMIN_USER_DN, ADMIN_PASSWORD, options, ldapClient, Winston);
|
||||
|
||||
return client.searchGroups("user1")
|
||||
.then(function (groups: string[]) {
|
||||
|
@ -103,13 +97,9 @@ describe("ldap/Client", function () {
|
|||
user: "cn=admin,dc=example,dc=com",
|
||||
password: "password"
|
||||
};
|
||||
const factory = new LdapClientFactoryStub();
|
||||
const ldapClient = new LdapClientStub();
|
||||
|
||||
factory.createStub.returns(ldapClient);
|
||||
|
||||
const connector = new ConnectorStub();
|
||||
// Retrieve user DN
|
||||
ldapClient.searchAsyncStub.withArgs("ou=users,dc=example,dc=com", {
|
||||
connector.searchAsyncStub.withArgs("ou=users,dc=example,dc=com", {
|
||||
scope: "sub",
|
||||
sizeLimit: 1,
|
||||
attributes: ["dn"],
|
||||
|
@ -119,7 +109,7 @@ describe("ldap/Client", function () {
|
|||
}]));
|
||||
|
||||
// Retrieve email
|
||||
ldapClient.searchAsyncStub.withArgs("cn=user1,ou=users,dc=example,dc=com", {
|
||||
connector.searchAsyncStub.withArgs("cn=user1,ou=users,dc=example,dc=com", {
|
||||
scope: "base",
|
||||
sizeLimit: 1,
|
||||
attributes: ["custom_mail"],
|
||||
|
@ -127,7 +117,7 @@ describe("ldap/Client", function () {
|
|||
custom_mail: "user1@example.com"
|
||||
}]));
|
||||
|
||||
const client = new Client(ADMIN_USER_DN, ADMIN_PASSWORD, options, factory, Winston);
|
||||
const client = new Session(ADMIN_USER_DN, ADMIN_PASSWORD, options, connector, Winston);
|
||||
|
||||
return client.searchEmails("user1")
|
||||
.then(function (emails: string[]) {
|
|
@ -1,18 +1,17 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import exceptions = require("../Exceptions");
|
||||
import { EventEmitter } from "events";
|
||||
import { IClient, GroupsAndEmails } from "./IClient";
|
||||
import { ILdapClient } from "./ILdapClient";
|
||||
import { ILdapClientFactory } from "./ILdapClientFactory";
|
||||
import { ISession, GroupsAndEmails } from "./ISession";
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
import { Winston } from "../../../types/Dependencies";
|
||||
import Util = require("util");
|
||||
import { HashGenerator } from "../utils/HashGenerator";
|
||||
import { IConnector } from "./connector/IConnector";
|
||||
|
||||
export class Client implements IClient {
|
||||
export class Session implements ISession {
|
||||
private userDN: string;
|
||||
private password: string;
|
||||
private ldapClient: ILdapClient;
|
||||
private connector: IConnector;
|
||||
private logger: Winston;
|
||||
private options: LdapConfiguration;
|
||||
|
||||
|
@ -20,12 +19,12 @@ export class Client implements IClient {
|
|||
private usersSearchBase: string;
|
||||
|
||||
constructor(userDN: string, password: string, options: LdapConfiguration,
|
||||
ldapClientFactory: ILdapClientFactory, logger: Winston) {
|
||||
connector: IConnector, logger: Winston) {
|
||||
this.options = options;
|
||||
this.logger = logger;
|
||||
this.userDN = userDN;
|
||||
this.password = password;
|
||||
this.ldapClient = ldapClientFactory.create();
|
||||
this.connector = connector;
|
||||
|
||||
this.groupsSearchBase = (this.options.additional_groups_dn)
|
||||
? Util.format("%s,%s", this.options.additional_groups_dn, this.options.base_dn)
|
||||
|
@ -38,7 +37,7 @@ export class Client implements IClient {
|
|||
|
||||
open(): BluebirdPromise<void> {
|
||||
this.logger.debug("LDAP: Bind user '%s'", this.userDN);
|
||||
return this.ldapClient.bindAsync(this.userDN, this.password)
|
||||
return this.connector.bindAsync(this.userDN, this.password)
|
||||
.error(function (err: Error) {
|
||||
return BluebirdPromise.reject(new exceptions.LdapBindError(err.message));
|
||||
});
|
||||
|
@ -46,7 +45,7 @@ export class Client implements IClient {
|
|||
|
||||
close(): BluebirdPromise<void> {
|
||||
this.logger.debug("LDAP: Unbind user '%s'", this.userDN);
|
||||
return this.ldapClient.unbindAsync()
|
||||
return this.connector.unbindAsync()
|
||||
.error(function (err: Error) {
|
||||
return BluebirdPromise.reject(new exceptions.LdapBindError(err.message));
|
||||
});
|
||||
|
@ -75,7 +74,7 @@ export class Client implements IClient {
|
|||
attributes: [that.options.group_name_attribute],
|
||||
filter: groupsFilter
|
||||
};
|
||||
return that.ldapClient.searchAsync(that.groupsSearchBase, query);
|
||||
return that.connector.searchAsync(that.groupsSearchBase, query);
|
||||
})
|
||||
.then(function (docs: { cn: string }[]) {
|
||||
const groups = docs.map((doc: any) => { return doc.cn; });
|
||||
|
@ -96,7 +95,7 @@ export class Client implements IClient {
|
|||
};
|
||||
|
||||
that.logger.debug("LDAP: searching for user dn of %s", username);
|
||||
return that.ldapClient.searchAsync(this.usersSearchBase, query)
|
||||
return that.connector.searchAsync(this.usersSearchBase, query)
|
||||
.then(function (users: { dn: string }[]) {
|
||||
if (users.length > 0) {
|
||||
that.logger.debug("LDAP: retrieved user dn is %s", users[0].dn);
|
||||
|
@ -117,7 +116,7 @@ export class Client implements IClient {
|
|||
|
||||
return this.searchUserDn(username)
|
||||
.then(function (userDN) {
|
||||
return that.ldapClient.searchAsync(userDN, query);
|
||||
return that.connector.searchAsync(userDN, query);
|
||||
})
|
||||
.then(function (docs: { [mail_attribute: string]: string }[]) {
|
||||
const emails: string[] = docs
|
||||
|
@ -148,10 +147,10 @@ export class Client implements IClient {
|
|||
}
|
||||
};
|
||||
that.logger.debug("Password new='%s'", change.modification.userPassword);
|
||||
return that.ldapClient.modifyAsync(res[1], change);
|
||||
return that.connector.modifyAsync(res[1], change);
|
||||
})
|
||||
.then(function () {
|
||||
return that.ldapClient.unbindAsync();
|
||||
return that.connector.unbindAsync();
|
||||
});
|
||||
}
|
||||
}
|
37
server/src/lib/ldap/SessionFactory.ts
Normal file
37
server/src/lib/ldap/SessionFactory.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import Ldapjs = require("ldapjs");
|
||||
import Winston = require("winston");
|
||||
|
||||
import { IConnectorFactory } from "./connector/IConnectorFactory";
|
||||
import { ISessionFactory } from "./ISessionFactory";
|
||||
import { ISession } from "./ISession";
|
||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
||||
import { Session } from "./Session";
|
||||
import { SafeSession } from "./SafeSession";
|
||||
|
||||
|
||||
export class SessionFactory implements ISessionFactory {
|
||||
private config: LdapConfiguration;
|
||||
private connectorFactory: IConnectorFactory;
|
||||
private logger: typeof Winston;
|
||||
|
||||
constructor(ldapConfiguration: LdapConfiguration,
|
||||
connectorFactory: IConnectorFactory,
|
||||
logger: typeof Winston) {
|
||||
this.config = ldapConfiguration;
|
||||
this.connectorFactory = connectorFactory;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
create(userDN: string, password: string): ISession {
|
||||
const connector = this.connectorFactory.create();
|
||||
return new SafeSession(
|
||||
new Session(
|
||||
userDN,
|
||||
password,
|
||||
this.config,
|
||||
connector,
|
||||
this.logger
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
16
server/src/lib/ldap/SessionFactoryStub.spec.ts
Normal file
16
server/src/lib/ldap/SessionFactoryStub.spec.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import Sinon = require("sinon");
|
||||
|
||||
import { ISession } from "./ISession";
|
||||
import { ISessionFactory } from "./ISessionFactory";
|
||||
|
||||
export class SessionFactoryStub implements ISessionFactory {
|
||||
createStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.createStub = Sinon.stub();
|
||||
}
|
||||
|
||||
create(userDN: string, password: string): ISession {
|
||||
return this.createStub(userDN, password);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { IClient, GroupsAndEmails } from "./IClient";
|
||||
import Bluebird = require("bluebird");
|
||||
import Sinon = require("sinon");
|
||||
|
||||
export class ClientStub implements IClient {
|
||||
import { ISession, GroupsAndEmails } from "./ISession";
|
||||
|
||||
export class SessionStub implements ISession {
|
||||
openStub: Sinon.SinonStub;
|
||||
closeStub: Sinon.SinonStub;
|
||||
searchUserDnStub: Sinon.SinonStub;
|
||||
|
@ -20,27 +20,27 @@ export class ClientStub implements IClient {
|
|||
this.modifyPasswordStub = Sinon.stub();
|
||||
}
|
||||
|
||||
open(): BluebirdPromise<void> {
|
||||
open(): Bluebird<void> {
|
||||
return this.openStub();
|
||||
}
|
||||
|
||||
close(): BluebirdPromise<void> {
|
||||
close(): Bluebird<void> {
|
||||
return this.closeStub();
|
||||
}
|
||||
|
||||
searchUserDn(username: string): BluebirdPromise<string> {
|
||||
searchUserDn(username: string): Bluebird<string> {
|
||||
return this.searchUserDnStub(username);
|
||||
}
|
||||
|
||||
searchEmails(username: string): BluebirdPromise<string[]> {
|
||||
searchEmails(username: string): Bluebird<string[]> {
|
||||
return this.searchEmailsStub(username);
|
||||
}
|
||||
|
||||
searchGroups(username: string): BluebirdPromise<string[]> {
|
||||
searchGroups(username: string): Bluebird<string[]> {
|
||||
return this.searchGroupsStub(username);
|
||||
}
|
||||
|
||||
modifyPassword(username: string, newPassword: string): BluebirdPromise<void> {
|
||||
modifyPassword(username: string, newPassword: string): Bluebird<void> {
|
||||
return this.modifyPasswordStub(username, newPassword);
|
||||
}
|
||||
}
|
35
server/src/lib/ldap/UsersDatabaseStub.spec.ts
Normal file
35
server/src/lib/ldap/UsersDatabaseStub.spec.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import Sinon = require("sinon");
|
||||
|
||||
import { IUsersDatabase } from "./IUsersDatabase";
|
||||
import { GroupsAndEmails } from "./ISession";
|
||||
|
||||
export class UsersDatabaseStub implements IUsersDatabase {
|
||||
checkUserPasswordStub: Sinon.SinonStub;
|
||||
getEmailsStub: Sinon.SinonStub;
|
||||
getGroupsStub: Sinon.SinonStub;
|
||||
updatePasswordStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.checkUserPasswordStub = Sinon.stub();
|
||||
this.getEmailsStub = Sinon.stub();
|
||||
this.getGroupsStub = Sinon.stub();
|
||||
this.updatePasswordStub = Sinon.stub();
|
||||
}
|
||||
|
||||
checkUserPassword(username: string, password: string): Bluebird<GroupsAndEmails> {
|
||||
return this.checkUserPasswordStub(username, password);
|
||||
}
|
||||
|
||||
getEmails(username: string): Bluebird<string[]> {
|
||||
return this.getEmailsStub(username);
|
||||
}
|
||||
|
||||
getGroups(username: string): Bluebird<string[]> {
|
||||
return this.getGroupsStub(username);
|
||||
}
|
||||
|
||||
updatePassword(username: string, newPassword: string): Bluebird<void> {
|
||||
return this.updatePasswordStub(username, newPassword);
|
||||
}
|
||||
}
|
|
@ -1,25 +1,23 @@
|
|||
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>;
|
||||
}
|
||||
}
|
||||
import Bluebird = require("bluebird");
|
||||
import { IConnector } from "./IConnector";
|
||||
import Exceptions = require("../../Exceptions");
|
||||
|
||||
interface SearchEntry {
|
||||
object: any;
|
||||
}
|
||||
|
||||
export class LdapClient implements ILdapClient {
|
||||
private client: LdapJs.ClientAsync;
|
||||
export interface ClientAsync {
|
||||
on(event: string, callback: (data?: any) => void): void;
|
||||
bindAsync(username: string, password: string): Bluebird<void>;
|
||||
unbindAsync(): Bluebird<void>;
|
||||
searchAsync(base: string, query: LdapJs.SearchOptions): Bluebird<EventEmitter>;
|
||||
modifyAsync(userdn: string, change: LdapJs.Change): Bluebird<void>;
|
||||
}
|
||||
|
||||
export class Connector implements IConnector {
|
||||
private client: ClientAsync;
|
||||
|
||||
constructor(url: string, ldapjs: typeof LdapJs) {
|
||||
const ldapClient = ldapjs.createClient({
|
||||
|
@ -32,23 +30,23 @@ export class LdapClient implements ILdapClient {
|
|||
clientLogger.level("trace");
|
||||
}*/
|
||||
|
||||
this.client = BluebirdPromise.promisifyAll(ldapClient) as any;
|
||||
this.client = Bluebird.promisifyAll(ldapClient) as any;
|
||||
}
|
||||
|
||||
bindAsync(username: string, password: string): BluebirdPromise<void> {
|
||||
bindAsync(username: string, password: string): Bluebird<void> {
|
||||
return this.client.bindAsync(username, password);
|
||||
}
|
||||
|
||||
unbindAsync(): BluebirdPromise<void> {
|
||||
unbindAsync(): Bluebird<void> {
|
||||
return this.client.unbindAsync();
|
||||
}
|
||||
|
||||
searchAsync(base: string, query: any): BluebirdPromise<any[]> {
|
||||
searchAsync(base: string, query: any): Bluebird<any[]> {
|
||||
const that = this;
|
||||
return this.client.searchAsync(base, query)
|
||||
.then(function (res: EventEmitter) {
|
||||
const doc: SearchEntry[] = [];
|
||||
return new BluebirdPromise<any[]>((resolve, reject) => {
|
||||
return new Bluebird<any[]>((resolve, reject) => {
|
||||
res.on("searchEntry", function (entry: SearchEntry) {
|
||||
doc.push(entry.object);
|
||||
});
|
||||
|
@ -61,11 +59,11 @@ export class LdapClient implements ILdapClient {
|
|||
});
|
||||
})
|
||||
.catch(function (err: Error) {
|
||||
return BluebirdPromise.reject(new Exceptions.LdapSearchError(err.message));
|
||||
return Bluebird.reject(new Exceptions.LdapSearchError(err.message));
|
||||
});
|
||||
}
|
||||
|
||||
modifyAsync(dn: string, changeRequest: any): BluebirdPromise<void> {
|
||||
modifyAsync(dn: string, changeRequest: any): Bluebird<void> {
|
||||
return this.client.modifyAsync(dn, changeRequest);
|
||||
}
|
||||
}
|
18
server/src/lib/ldap/connector/ConnectorFactory.ts
Normal file
18
server/src/lib/ldap/connector/ConnectorFactory.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { IConnector } from "./IConnector";
|
||||
import { Connector } from "./Connector";
|
||||
import { LdapConfiguration } from "../../configuration/schema/LdapConfiguration";
|
||||
import { Ldapjs } from "Dependencies";
|
||||
|
||||
export class ConnectorFactory {
|
||||
private configuration: LdapConfiguration;
|
||||
private ldapjs: Ldapjs;
|
||||
|
||||
constructor(configuration: LdapConfiguration, ldapjs: Ldapjs) {
|
||||
this.configuration = configuration;
|
||||
this.ldapjs = ldapjs;
|
||||
}
|
||||
|
||||
create(): IConnector {
|
||||
return new Connector(this.configuration.url, this.ldapjs);
|
||||
}
|
||||
}
|
|
@ -1,16 +1,16 @@
|
|||
import Sinon = require("sinon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { ILdapClientFactory } from "./ILdapClientFactory";
|
||||
import { ILdapClient } from "./ILdapClient";
|
||||
import { IConnectorFactory } from "./IConnectorFactory";
|
||||
import { IConnector } from "./IConnector";
|
||||
|
||||
export class LdapClientFactoryStub implements ILdapClientFactory {
|
||||
export class ConnectorFactoryStub implements IConnectorFactory {
|
||||
createStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.createStub = Sinon.stub();
|
||||
}
|
||||
|
||||
create(): ILdapClient {
|
||||
create(): IConnector {
|
||||
return this.createStub();
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import Sinon = require("sinon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { ILdapClient } from "./ILdapClient";
|
||||
import { IConnector } from "./IConnector";
|
||||
|
||||
export class LdapClientStub implements ILdapClient {
|
||||
export class ConnectorStub implements IConnector {
|
||||
bindAsyncStub: Sinon.SinonStub;
|
||||
unbindAsyncStub: Sinon.SinonStub;
|
||||
searchAsyncStub: Sinon.SinonStub;
|
10
server/src/lib/ldap/connector/IConnector.ts
Normal file
10
server/src/lib/ldap/connector/IConnector.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
import Bluebird = require("bluebird");
|
||||
import EventEmitter = require("events");
|
||||
|
||||
export interface IConnector {
|
||||
bindAsync(username: string, password: string): Bluebird<void>;
|
||||
unbindAsync(): Bluebird<void>;
|
||||
searchAsync(base: string, query: any): Bluebird<any[]>;
|
||||
modifyAsync(dn: string, changeRequest: any): Bluebird<void>;
|
||||
}
|
5
server/src/lib/ldap/connector/IConnectorFactory.ts
Normal file
5
server/src/lib/ldap/connector/IConnectorFactory.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { IConnector } from "./IConnector";
|
||||
|
||||
export interface IConnectorFactory {
|
||||
create(): IConnector;
|
||||
}
|
|
@ -54,7 +54,7 @@ describe("routes/firstfactor/post", function () {
|
|||
});
|
||||
|
||||
it("should reply with 204 if success", function () {
|
||||
mocks.ldapAuthenticator.authenticateStub.withArgs("username", "password")
|
||||
mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
|
||||
.returns(BluebirdPromise.resolve({
|
||||
emails: emails,
|
||||
groups: groups
|
||||
|
@ -67,14 +67,14 @@ describe("routes/firstfactor/post", function () {
|
|||
});
|
||||
|
||||
it("should retrieve email from LDAP", function () {
|
||||
mocks.ldapAuthenticator.authenticateStub.withArgs("username", "password")
|
||||
mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
|
||||
.returns(BluebirdPromise.resolve([{ mail: ["test@example.com"] }]));
|
||||
return FirstFactorPost.default(vars)(req as any, res as any);
|
||||
});
|
||||
|
||||
it("should set first email address as user session variable", function () {
|
||||
const emails = ["test_ok@example.com"];
|
||||
mocks.ldapAuthenticator.authenticateStub.withArgs("username", "password")
|
||||
mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
|
||||
.returns(BluebirdPromise.resolve({
|
||||
emails: emails,
|
||||
groups: groups
|
||||
|
@ -87,7 +87,7 @@ describe("routes/firstfactor/post", function () {
|
|||
});
|
||||
|
||||
it("should return error message when LDAP authenticator throws", function () {
|
||||
mocks.ldapAuthenticator.authenticateStub.withArgs("username", "password")
|
||||
mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
|
||||
.returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials")));
|
||||
|
||||
return FirstFactorPost.default(vars)(req as any, res as any)
|
||||
|
|
|
@ -5,7 +5,6 @@ import BluebirdPromise = require("bluebird");
|
|||
import express = require("express");
|
||||
import { AccessController } from "../../access_control/AccessController";
|
||||
import { Regulator } from "../../regulation/Regulator";
|
||||
import { GroupsAndEmails } from "../../ldap/IClient";
|
||||
import Endpoint = require("../../../../../shared/api");
|
||||
import ErrorReplies = require("../../ErrorReplies");
|
||||
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
|
||||
|
@ -15,6 +14,7 @@ import UserMessages = require("../../../../../shared/UserMessages");
|
|||
import { MethodCalculator } from "../../authentication/MethodCalculator";
|
||||
import { ServerVariables } from "../../ServerVariables";
|
||||
import { AuthenticationSession } from "../../../../types/AuthenticationSession";
|
||||
import { GroupsAndEmails } from "../../ldap/ISession";
|
||||
|
||||
export default function (vars: ServerVariables) {
|
||||
return function (req: express.Request, res: express.Response)
|
||||
|
@ -34,7 +34,7 @@ export default function (vars: ServerVariables) {
|
|||
})
|
||||
.then(function () {
|
||||
vars.logger.info(req, "No regulation applied.");
|
||||
return vars.ldapAuthenticator.authenticate(username, password);
|
||||
return vars.usersDatabase.checkUserPassword(username, password);
|
||||
})
|
||||
.then(function (groupsAndEmails: GroupsAndEmails) {
|
||||
vars.logger.info(req,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
import PasswordResetFormPost = require("./post");
|
||||
import { PasswordUpdater } from "../../../ldap/PasswordUpdater";
|
||||
import { AuthenticationSessionHandler } from "../../../AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
|
||||
import { UserDataStore } from "../../../storage/UserDataStore";
|
||||
|
@ -69,7 +68,7 @@ describe("routes/password-reset/form/post", function () {
|
|||
req.body = {};
|
||||
req.body.password = "new-password";
|
||||
|
||||
mocks.ldapPasswordUpdater.updatePasswordStub.returns(BluebirdPromise.resolve());
|
||||
mocks.usersDatabase.updatePasswordStub.returns(BluebirdPromise.resolve());
|
||||
|
||||
authSession.identity_check = {
|
||||
userid: "user",
|
||||
|
@ -104,7 +103,7 @@ describe("routes/password-reset/form/post", function () {
|
|||
req.body = {};
|
||||
req.body.password = "new-password";
|
||||
|
||||
mocks.ldapPasswordUpdater.updatePasswordStub
|
||||
mocks.usersDatabase.updatePasswordStub
|
||||
.returns(BluebirdPromise.reject("Internal error with LDAP"));
|
||||
|
||||
authSession.identity_check = {
|
||||
|
|
|
@ -34,7 +34,7 @@ export default function (vars: ServerVariables) {
|
|||
resolve();
|
||||
})
|
||||
.then(function () {
|
||||
return vars.ldapPasswordUpdater.updatePassword(authSession.identity_check.userid, newPassword);
|
||||
return vars.usersDatabase.updatePassword(authSession.identity_check.userid, newPassword);
|
||||
})
|
||||
.then(function () {
|
||||
vars.logger.info(req, "Password reset for user '%s'",
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
import PasswordResetHandler
|
||||
from "./PasswordResetHandler";
|
||||
import PasswordUpdater = require("../../../ldap/PasswordUpdater");
|
||||
import { UserDataStore } from "../../../storage/UserDataStore";
|
||||
import Sinon = require("sinon");
|
||||
import winston = require("winston");
|
||||
|
@ -60,7 +59,7 @@ describe("routes/password-reset/identity/PasswordResetHandler", function () {
|
|||
it("should fail when no userid is provided", function () {
|
||||
req.query.userid = undefined;
|
||||
const handler = new PasswordResetHandler(vars.logger,
|
||||
vars.ldapEmailsRetriever);
|
||||
vars.usersDatabase);
|
||||
return handler.preValidationInit(req as any)
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject("It should fail");
|
||||
|
@ -71,9 +70,9 @@ describe("routes/password-reset/identity/PasswordResetHandler", function () {
|
|||
});
|
||||
|
||||
it("should fail if ldap fail", function () {
|
||||
mocks.ldapEmailsRetriever.retrieveStub
|
||||
mocks.usersDatabase.getEmailsStub
|
||||
.returns(BluebirdPromise.reject("Internal error"));
|
||||
new PasswordResetHandler(vars.logger, vars.ldapEmailsRetriever)
|
||||
new PasswordResetHandler(vars.logger, vars.usersDatabase)
|
||||
.preValidationInit(req as any)
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject(new Error("should not be here"));
|
||||
|
@ -84,9 +83,9 @@ describe("routes/password-reset/identity/PasswordResetHandler", function () {
|
|||
});
|
||||
|
||||
it("should returns identity when ldap replies", function () {
|
||||
mocks.ldapEmailsRetriever.retrieveStub
|
||||
mocks.usersDatabase.getEmailsStub
|
||||
.returns(BluebirdPromise.resolve(["test@example.com"]));
|
||||
return new PasswordResetHandler(vars.logger, vars.ldapEmailsRetriever)
|
||||
return new PasswordResetHandler(vars.logger, vars.usersDatabase)
|
||||
.preValidationInit(req as any);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,17 +8,17 @@ import { IdentityValidable } from "../../../IdentityValidable";
|
|||
import { PRE_VALIDATION_TEMPLATE } from "../../../IdentityCheckPreValidationTemplate";
|
||||
import Constants = require("../constants");
|
||||
import { IRequestLogger } from "../../../logging/IRequestLogger";
|
||||
import { IEmailsRetriever } from "../../../ldap/IEmailsRetriever";
|
||||
import { IUsersDatabase } from "../../../ldap/IUsersDatabase";
|
||||
|
||||
export const TEMPLATE_NAME = "password-reset-form";
|
||||
|
||||
export default class PasswordResetHandler implements IdentityValidable {
|
||||
private logger: IRequestLogger;
|
||||
private emailsRetriever: IEmailsRetriever;
|
||||
private usersDatabase: IUsersDatabase;
|
||||
|
||||
constructor(logger: IRequestLogger, emailsRetriever: IEmailsRetriever) {
|
||||
constructor(logger: IRequestLogger, usersDatabase: IUsersDatabase) {
|
||||
this.logger = logger;
|
||||
this.emailsRetriever = emailsRetriever;
|
||||
this.usersDatabase = usersDatabase;
|
||||
}
|
||||
|
||||
challenge(): string {
|
||||
|
@ -36,7 +36,7 @@ export default class PasswordResetHandler implements IdentityValidable {
|
|||
return BluebirdPromise.reject(
|
||||
new exceptions.AccessDeniedError("No user id provided"));
|
||||
|
||||
return that.emailsRetriever.retrieve(userid);
|
||||
return that.usersDatabase.getEmails(userid);
|
||||
})
|
||||
.then(function (emails: string[]) {
|
||||
if (!emails && emails.length <= 0) throw new Error("No email found");
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
|
||||
import Assert = require("assert");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Express = require("express");
|
||||
import Sinon = require("sinon");
|
||||
import winston = require("winston");
|
||||
|
||||
import VerifyGet = require("./get");
|
||||
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../../../types/AuthenticationSession";
|
||||
import Sinon = require("sinon");
|
||||
import winston = require("winston");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import express = require("express");
|
||||
import ExpressMock = require("../../stubs/express.spec");
|
||||
import { ServerVariables } from "../../ServerVariables";
|
||||
import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../ServerVariablesMockBuilder.spec";
|
||||
|
@ -44,7 +45,7 @@ describe("routes/verify/get", function () {
|
|||
authSession.second_factor = true;
|
||||
authSession.userid = "myuser";
|
||||
authSession.groups = ["mygroup", "othergroup"];
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
Sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "myuser");
|
||||
Sinon.assert.calledWithExactly(res.setHeader, "Remote-Groups", "mygroup,othergroup");
|
||||
|
@ -53,7 +54,7 @@ describe("routes/verify/get", function () {
|
|||
});
|
||||
|
||||
function test_session(_authSession: AuthenticationSession, status_code: number) {
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert.equal(status_code, res.status.getCall(0).args[0]);
|
||||
});
|
||||
|
@ -156,7 +157,7 @@ describe("routes/verify/get", function () {
|
|||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
authSession.first_factor = true;
|
||||
authSession.userid = "user1";
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert(res.status.calledWith(204));
|
||||
Assert(res.send.calledOnce);
|
||||
|
@ -166,7 +167,7 @@ describe("routes/verify/get", function () {
|
|||
it("should be rejected with 401 when first factor is not validated", function () {
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
authSession.first_factor = false;
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert(res.status.calledWith(401));
|
||||
});
|
||||
|
@ -184,7 +185,7 @@ describe("routes/verify/get", function () {
|
|||
authSession.userid = "myuser";
|
||||
authSession.groups = ["mygroup", "othergroup"];
|
||||
authSession.last_activity_datetime = currentTime;
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
})
|
||||
|
@ -203,7 +204,7 @@ describe("routes/verify/get", function () {
|
|||
authSession.userid = "myuser";
|
||||
authSession.groups = ["mygroup", "othergroup"];
|
||||
authSession.last_activity_datetime = currentTime;
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
})
|
||||
|
@ -220,11 +221,11 @@ describe("routes/verify/get", function () {
|
|||
it("should return error code 401", function() {
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
mocks.config.authentication_methods.default_method = "single_factor";
|
||||
mocks.ldapAuthenticator.authenticateStub.rejects(new Error(
|
||||
mocks.usersDatabase.checkUserPasswordStub.rejects(new Error(
|
||||
"Invalid credentials"));
|
||||
req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
|
||||
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert(res.status.calledWithExactly(401));
|
||||
});
|
||||
|
@ -234,12 +235,12 @@ describe("routes/verify/get", function () {
|
|||
const REDIRECT_URL = "http://redirection_url.com";
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
mocks.config.authentication_methods.default_method = "single_factor";
|
||||
mocks.ldapAuthenticator.authenticateStub.rejects(new Error(
|
||||
mocks.usersDatabase.checkUserPasswordStub.rejects(new Error(
|
||||
"Invalid credentials"));
|
||||
req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
|
||||
req.query["rd"] = REDIRECT_URL;
|
||||
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert(res.redirect.calledWithExactly(REDIRECT_URL));
|
||||
});
|
||||
|
@ -250,12 +251,12 @@ describe("routes/verify/get", function () {
|
|||
it("should authenticate correctly", function () {
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
mocks.config.authentication_methods.default_method = "single_factor";
|
||||
mocks.ldapAuthenticator.authenticateStub.returns({
|
||||
mocks.usersDatabase.checkUserPasswordStub.returns({
|
||||
groups: ["mygroup", "othergroup"],
|
||||
});
|
||||
req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
|
||||
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
Sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "john");
|
||||
Sinon.assert.calledWithExactly(res.setHeader, "Remote-Groups", "mygroup,othergroup");
|
||||
|
@ -269,12 +270,12 @@ describe("routes/verify/get", function () {
|
|||
mocks.config.authentication_methods.per_subdomain_methods = {
|
||||
"secret.example.com": "two_factor"
|
||||
};
|
||||
mocks.ldapAuthenticator.authenticateStub.resolves({
|
||||
mocks.usersDatabase.checkUserPasswordStub.resolves({
|
||||
groups: ["mygroup", "othergroup"],
|
||||
});
|
||||
req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
|
||||
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert(res.status.calledWithExactly(401));
|
||||
});
|
||||
|
@ -283,12 +284,12 @@ describe("routes/verify/get", function () {
|
|||
it("should fail when base64 token is not valid", function () {
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
mocks.config.authentication_methods.default_method = "single_factor";
|
||||
mocks.ldapAuthenticator.authenticateStub.resolves({
|
||||
mocks.usersDatabase.checkUserPasswordStub.resolves({
|
||||
groups: ["mygroup", "othergroup"],
|
||||
});
|
||||
req.headers["proxy-authorization"] = "Basic i_m*not_a_base64*token";
|
||||
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert(res.status.calledWithExactly(401));
|
||||
});
|
||||
|
@ -297,12 +298,12 @@ describe("routes/verify/get", function () {
|
|||
it("should fail when base64 token has not format user:psswd", function () {
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
mocks.config.authentication_methods.default_method = "single_factor";
|
||||
mocks.ldapAuthenticator.authenticateStub.resolves({
|
||||
mocks.usersDatabase.checkUserPasswordStub.resolves({
|
||||
groups: ["mygroup", "othergroup"],
|
||||
});
|
||||
req.headers["proxy-authorization"] = "Basic am9objpwYXNzOmJhZA==";
|
||||
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert(res.status.calledWithExactly(401));
|
||||
});
|
||||
|
@ -311,11 +312,11 @@ describe("routes/verify/get", function () {
|
|||
it("should fail when bad user password is provided", function () {
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
mocks.config.authentication_methods.default_method = "single_factor";
|
||||
mocks.ldapAuthenticator.authenticateStub.rejects(new Error(
|
||||
mocks.usersDatabase.checkUserPasswordStub.rejects(new Error(
|
||||
"Invalid credentials"));
|
||||
req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
|
||||
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert(res.status.calledWithExactly(401));
|
||||
});
|
||||
|
@ -324,12 +325,12 @@ describe("routes/verify/get", function () {
|
|||
it("should fail when resource is restricted", function () {
|
||||
mocks.accessController.isAccessAllowedMock.returns(false);
|
||||
mocks.config.authentication_methods.default_method = "single_factor";
|
||||
mocks.ldapAuthenticator.authenticateStub.resolves({
|
||||
mocks.usersDatabase.checkUserPasswordStub.resolves({
|
||||
groups: ["mygroup", "othergroup"],
|
||||
});
|
||||
req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
|
||||
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
return VerifyGet.default(vars)(req as Express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert(res.status.calledWithExactly(401));
|
||||
});
|
||||
|
|
|
@ -12,60 +12,51 @@ export default function (req: Express.Request, res: Express.Response,
|
|||
vars: ServerVariables, authorizationHeader: string)
|
||||
: BluebirdPromise<{ username: string, groups: string[] }> {
|
||||
let username: string;
|
||||
let groups: string[];
|
||||
let domain: string;
|
||||
let originalUri: string;
|
||||
|
||||
return new BluebirdPromise<[string, string]>(function (resolve, reject) {
|
||||
const originalUrl = ObjectPath.get<Express.Request, string>(req, "headers.x-original-url");
|
||||
domain = DomainExtractor.fromUrl(originalUrl);
|
||||
originalUri =
|
||||
ObjectPath.get<Express.Request, string>(req, "headers.x-original-uri");
|
||||
const authenticationMethod =
|
||||
MethodCalculator.compute(vars.config.authentication_methods, domain);
|
||||
return BluebirdPromise.resolve()
|
||||
.then(() => {
|
||||
const originalUrl = ObjectPath.get<Express.Request, string>(req, "headers.x-original-url");
|
||||
domain = DomainExtractor.fromUrl(originalUrl);
|
||||
originalUri =
|
||||
ObjectPath.get<Express.Request, string>(req, "headers.x-original-uri");
|
||||
const authenticationMethod =
|
||||
MethodCalculator.compute(vars.config.authentication_methods, domain);
|
||||
|
||||
if (authenticationMethod != "single_factor") {
|
||||
reject(new Error("This domain is not protected with single factor. " +
|
||||
"You cannot log in with basic authentication."));
|
||||
return;
|
||||
}
|
||||
if (authenticationMethod != "single_factor") {
|
||||
return BluebirdPromise.reject(new Error("This domain is not protected with single factor. " +
|
||||
"You cannot log in with basic authentication."));
|
||||
}
|
||||
|
||||
const base64Re = new RegExp("^Basic ((?:[A-Za-z0-9+/]{4})*" +
|
||||
"(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)$");
|
||||
const isTokenValidBase64 = base64Re.test(authorizationHeader);
|
||||
const base64Re = new RegExp("^Basic ((?:[A-Za-z0-9+/]{4})*" +
|
||||
"(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)$");
|
||||
const isTokenValidBase64 = base64Re.test(authorizationHeader);
|
||||
|
||||
if (!isTokenValidBase64) {
|
||||
reject(new Error("No valid base64 token found in the header"));
|
||||
return;
|
||||
}
|
||||
if (!isTokenValidBase64) {
|
||||
return BluebirdPromise.reject(new Error("No valid base64 token found in the header"));
|
||||
}
|
||||
|
||||
const tokenMatches = authorizationHeader.match(base64Re);
|
||||
const base64Token = tokenMatches[1];
|
||||
const decodedToken = Buffer.from(base64Token, "base64").toString();
|
||||
const splittedToken = decodedToken.split(":");
|
||||
const tokenMatches = authorizationHeader.match(base64Re);
|
||||
const base64Token = tokenMatches[1];
|
||||
const decodedToken = Buffer.from(base64Token, "base64").toString();
|
||||
const splittedToken = decodedToken.split(":");
|
||||
|
||||
if (splittedToken.length != 2) {
|
||||
reject(new Error(
|
||||
"The authorization token is invalid. Expecting 'userid:password'"));
|
||||
return;
|
||||
}
|
||||
if (splittedToken.length != 2) {
|
||||
return BluebirdPromise.reject(new Error(
|
||||
"The authorization token is invalid. Expecting 'userid:password'"));
|
||||
}
|
||||
|
||||
username = splittedToken[0];
|
||||
const password = splittedToken[1];
|
||||
resolve([username, password]);
|
||||
})
|
||||
.then(function ([userid, password]) {
|
||||
return vars.ldapAuthenticator.authenticate(userid, password);
|
||||
username = splittedToken[0];
|
||||
const password = splittedToken[1];
|
||||
return vars.usersDatabase.checkUserPassword(username, password);
|
||||
})
|
||||
.then(function (groupsAndEmails) {
|
||||
groups = groupsAndEmails.groups;
|
||||
return AccessControl(req, vars, domain, originalUri, username, groups);
|
||||
})
|
||||
.then(function () {
|
||||
return BluebirdPromise.resolve({
|
||||
username: username,
|
||||
groups: groups
|
||||
});
|
||||
return AccessControl(req, vars, domain, originalUri, username, groupsAndEmails.groups)
|
||||
.then(() => BluebirdPromise.resolve({
|
||||
username: username,
|
||||
groups: groupsAndEmails.groups
|
||||
}));
|
||||
})
|
||||
.catch(function (err: Error) {
|
||||
return BluebirdPromise.reject(
|
||||
|
|
|
@ -104,7 +104,7 @@ function setupResetPassword(app: Express.Application, vars: ServerVariables) {
|
|||
IdentityCheckMiddleware.register(app,
|
||||
Endpoints.RESET_PASSWORD_IDENTITY_START_GET,
|
||||
Endpoints.RESET_PASSWORD_IDENTITY_FINISH_GET,
|
||||
new ResetPasswordIdentityHandler(vars.logger, vars.ldapEmailsRetriever),
|
||||
new ResetPasswordIdentityHandler(vars.logger, vars.usersDatabase),
|
||||
vars);
|
||||
|
||||
app.get(Endpoints.RESET_PASSWORD_REQUEST_GET,
|
||||
|
|
Loading…
Reference in New Issue
Block a user