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:
Clément Michaud 2018-08-25 19:22:48 +02:00 committed by GitHub
parent e50b798edc
commit 6438a5e48f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 843 additions and 878 deletions

6
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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();
});
});
});
});

View File

@ -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));
});
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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));
});
}
}

View File

@ -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();
});
});
});
});

View File

@ -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));
});
}
}

View File

@ -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);
}
}

View File

@ -1,6 +0,0 @@
import BluebirdPromise = require("bluebird");
import { GroupsAndEmails } from "./IClient";
export interface IAuthenticator {
authenticate(username: string, password: string): BluebirdPromise<GroupsAndEmails>;
}

View File

@ -1,6 +0,0 @@
import { IClient } from "./IClient";
export interface IClientFactory {
create(userDN: string, password: string): IClient;
}

View File

@ -1,6 +0,0 @@
import BluebirdPromise = require("bluebird");
import { IClient } from "./IClient";
export interface IEmailsRetriever {
retrieve(username: string, client?: IClient): BluebirdPromise<string[]>;
}

View File

@ -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>;
}

View File

@ -1,6 +0,0 @@
import { ILdapClient } from "./ILdapClient";
export interface ILdapClientFactory {
create(): ILdapClient;
}

View File

@ -1,5 +0,0 @@
import BluebirdPromise = require("bluebird");
export interface IPasswordUpdater {
updatePassword(username: string, newPassword: string): BluebirdPromise<void>;
}

View File

@ -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[]>;

View File

@ -0,0 +1,6 @@
import { ISession } from "./ISession";
export interface ISessionFactory {
create(userDN: string, password: string): ISession;
}

View 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>;
}

View File

@ -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);
});
});

View File

@ -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);
}
}

View 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);
})
});
});
});

View 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));
});
}
}

View File

@ -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();
});
});
});
});

View File

@ -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));
});
}
}

View File

@ -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);
}
}

View File

@ -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 () {

View File

@ -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));

View 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);
});
});

View File

@ -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))

View File

@ -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[]) {

View File

@ -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();
});
}
}

View 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
)
);
}
}

View 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);
}
}

View File

@ -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);
}
}

View 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);
}
}

View File

@ -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);
}
}

View 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);
}
}

View File

@ -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();
}
}

View File

@ -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;

View 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>;
}

View File

@ -0,0 +1,5 @@
import { IConnector } from "./IConnector";
export interface IConnectorFactory {
create(): IConnector;
}

View File

@ -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)

View File

@ -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,

View File

@ -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 = {

View File

@ -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'",

View File

@ -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);
});
});

View File

@ -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");

View File

@ -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));
});

View File

@ -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(

View File

@ -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,