From fd59044f5e290379402a73cd9d3c47f779f6e5eb Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Sun, 16 Jul 2017 17:37:13 +0200 Subject: [PATCH] Open and close ldap client after each operation to avoid issues with idle connections and ECONNRESET exceptions --- config.template.yml | 2 +- example/nginx/html/secret.html | 2 +- src/server/index.ts | 1 + src/server/lib/Exceptions.ts | 8 + src/server/lib/Server.ts | 2 +- src/server/lib/ServerVariables.ts | 29 ++- src/server/lib/ldap/Authenticator.ts | 54 ++++ .../lib/{LdapClient.ts => ldap/Client.ts} | 140 +++++----- src/server/lib/ldap/EmailsRetriever.ts | 46 ++++ src/server/lib/ldap/PasswordUpdater.ts | 43 ++++ src/server/lib/ldap/common.ts | 18 ++ src/server/lib/routes/firstfactor/post.ts | 23 +- .../lib/routes/password-reset/form/post.ts | 6 +- .../identity/PasswordResetHandler.ts | 4 +- src/types/Dependencies.ts | 3 + src/types/ldapjs-async.d.ts | 1 + test/unit/server/DataPersistence.test.ts | 7 +- .../server/IdentityCheckMiddleware.test.ts | 2 +- test/unit/server/LdapClient.test.ts | 242 ------------------ test/unit/server/ServerConfiguration.test.ts | 19 +- .../SessionConfigurationBuilder.test.ts | 42 +-- test/unit/server/ldap/Authenticator.test.ts | 125 +++++++++ test/unit/server/ldap/EmailsRetriever.test.ts | 94 +++++++ test/unit/server/ldap/PasswordUpdater.test.ts | 83 ++++++ test/unit/server/mocks/LdapClient.ts | 20 -- test/unit/server/mocks/ServerVariablesMock.ts | 26 +- .../server/routes/firstfactor/post.test.ts | 63 ++--- .../identity/PasswordResetHandler.test.ts | 40 +-- .../server/routes/password-reset/post.test.ts | 37 ++- .../totp/register/RegistrationHandler.test.ts | 2 +- .../secondfactor/totp/sign/post.test.ts | 6 +- .../u2f/identity/RegistrationHandler.test.ts | 2 +- .../secondfactor/u2f/register/post.test.ts | 5 +- .../u2f/register_request/get.test.ts | 5 +- .../routes/secondfactor/u2f/sign/post.test.ts | 5 +- .../secondfactor/u2f/sign_request/get.test.ts | 5 +- test/unit/server/routes/verify/get.test.ts | 4 +- test/unit/server/server/PrivatePages.ts | 5 +- test/unit/server/server/PublicPages.ts | 5 +- test/unit/server/server/Server.test.ts | 8 +- 40 files changed, 730 insertions(+), 504 deletions(-) create mode 100644 src/server/lib/ldap/Authenticator.ts rename src/server/lib/{LdapClient.ts => ldap/Client.ts} (52%) create mode 100644 src/server/lib/ldap/EmailsRetriever.ts create mode 100644 src/server/lib/ldap/PasswordUpdater.ts create mode 100644 src/server/lib/ldap/common.ts delete mode 100644 test/unit/server/LdapClient.test.ts create mode 100644 test/unit/server/ldap/Authenticator.test.ts create mode 100644 test/unit/server/ldap/EmailsRetriever.test.ts create mode 100644 test/unit/server/ldap/PasswordUpdater.test.ts delete mode 100644 test/unit/server/mocks/LdapClient.ts diff --git a/config.template.yml b/config.template.yml index e93838164..29625f84f 100644 --- a/config.template.yml +++ b/config.template.yml @@ -5,7 +5,7 @@ port: 80 # Log level # # Level of verbosity for logs -logs_level: info +logs_level: debug # LDAP configuration # diff --git a/example/nginx/html/secret.html b/example/nginx/html/secret.html index e40f3bd90..386bd8931 100644 --- a/example/nginx/html/secret.html +++ b/example/nginx/html/secret.html @@ -5,6 +5,6 @@ This is a very important secret!
- Go back to home page. + Go back to home page. diff --git a/src/server/index.ts b/src/server/index.ts index 70f2b72c1..621d28c12 100755 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -19,6 +19,7 @@ const yamlContent = YAML.load(configurationFilepath); const deps: GlobalDependencies = { u2f: require("u2f"), + dovehash: require("dovehash"), nodemailer: require("nodemailer"), ldapjs: require("ldapjs"), session: require("express-session"), diff --git a/src/server/lib/Exceptions.ts b/src/server/lib/Exceptions.ts index d0332d145..bc310eadf 100644 --- a/src/server/lib/Exceptions.ts +++ b/src/server/lib/Exceptions.ts @@ -15,6 +15,14 @@ export class LdapBindError extends Error { } } +export class LdapError extends Error { + constructor(message?: string) { + super(message); + this.name = "LdapError"; + Object.setPrototypeOf(this, LdapError.prototype); + } +} + export class IdentityError extends Error { constructor(message?: string) { super(message); diff --git a/src/server/lib/Server.ts b/src/server/lib/Server.ts index adda7e431..401844ba8 100644 --- a/src/server/lib/Server.ts +++ b/src/server/lib/Server.ts @@ -8,7 +8,7 @@ import ConfigurationAdapter from "./ConfigurationAdapter"; import { TOTPValidator } from "./TOTPValidator"; import { TOTPGenerator } from "./TOTPGenerator"; import RestApi from "./RestApi"; -import { LdapClient } from "./LdapClient"; +import { Client } from "./ldap/Client"; import BluebirdPromise = require("bluebird"); import ServerVariables = require("./ServerVariables"); import SessionConfigurationBuilder from "./SessionConfigurationBuilder"; diff --git a/src/server/lib/ServerVariables.ts b/src/server/lib/ServerVariables.ts index 5739deed2..5282052b6 100644 --- a/src/server/lib/ServerVariables.ts +++ b/src/server/lib/ServerVariables.ts @@ -1,6 +1,9 @@ import winston = require("winston"); -import { LdapClient } from "./LdapClient"; +import { Authenticator } from "./ldap/Authenticator"; +import { PasswordUpdater } from "./ldap/PasswordUpdater"; +import { EmailsRetriever } from "./ldap/EmailsRetriever"; + import { TOTPValidator } from "./TOTPValidator"; import { TOTPGenerator } from "./TOTPGenerator"; import U2F = require("u2f"); @@ -19,7 +22,9 @@ export const VARIABLES_KEY = "authelia-variables"; export interface ServerVariables { logger: typeof winston; - ldap: LdapClient; + ldapAuthenticator: Authenticator; + ldapPasswordUpdater: PasswordUpdater; + ldapEmailsRetriever: EmailsRetriever; totpValidator: TOTPValidator; totpGenerator: TOTPGenerator; u2f: typeof U2F; @@ -41,7 +46,9 @@ export function fill(app: express.Application, config: Configuration.AppConfigur const userDataStore = new UserDataStore(datastore_options, deps.nedb); const regulator = new AuthenticationRegulator(userDataStore, five_minutes); const notifier = NotifierFactory.build(config.notifier, deps.nodemailer); - const ldap = new LdapClient(config.ldap, deps.ldapjs, deps.winston); + const ldapAuthenticator = new Authenticator(config.ldap, deps.ldapjs, deps.winston); + const ldapPasswordUpdater = new PasswordUpdater(config.ldap, deps.ldapjs, deps.dovehash, deps.winston); + const ldapEmailsRetriever = new EmailsRetriever(config.ldap, deps.ldapjs, deps.winston); const accessController = new AccessController(config.access_control, deps.winston); const totpValidator = new TOTPValidator(deps.speakeasy); const totpGenerator = new TOTPGenerator(deps.speakeasy); @@ -49,7 +56,9 @@ export function fill(app: express.Application, config: Configuration.AppConfigur const variables: ServerVariables = { accessController: accessController, config: config, - ldap: ldap, + ldapAuthenticator: ldapAuthenticator, + ldapPasswordUpdater: ldapPasswordUpdater, + ldapEmailsRetriever: ldapEmailsRetriever, logger: deps.winston, notifier: notifier, regulator: regulator, @@ -74,8 +83,16 @@ export function getNotifier(app: express.Application): INotifier { return (app.get(VARIABLES_KEY) as ServerVariables).notifier; } -export function getLdapClient(app: express.Application): LdapClient { - return (app.get(VARIABLES_KEY) as ServerVariables).ldap; +export function getLdapAuthenticator(app: express.Application): Authenticator { + return (app.get(VARIABLES_KEY) as ServerVariables).ldapAuthenticator; +} + +export function getLdapPasswordUpdater(app: express.Application): PasswordUpdater { + return (app.get(VARIABLES_KEY) as ServerVariables).ldapPasswordUpdater; +} + +export function getLdapEmailsRetriever(app: express.Application): EmailsRetriever { + return (app.get(VARIABLES_KEY) as ServerVariables).ldapEmailsRetriever; } export function getConfiguration(app: express.Application): Configuration.AppConfiguration { diff --git a/src/server/lib/ldap/Authenticator.ts b/src/server/lib/ldap/Authenticator.ts new file mode 100644 index 000000000..bf936a469 --- /dev/null +++ b/src/server/lib/ldap/Authenticator.ts @@ -0,0 +1,54 @@ +import BluebirdPromise = require("bluebird"); +import exceptions = require("../Exceptions"); +import ldapjs = require("ldapjs"); +import { Client, Attributes } from "./Client"; +import { buildUserDN } from "./common"; + +import { LdapConfiguration } from "./../../../types/Configuration"; +import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies"; + + +export class Authenticator { + private options: LdapConfiguration; + private ldapjs: Ldapjs; + private logger: Winston; + + constructor(options: LdapConfiguration, ldapjs: Ldapjs, logger: Winston) { + this.options = options; + this.ldapjs = ldapjs; + this.logger = logger; + } + + private createClient(userDN: string, password: string): Client { + return new Client(userDN, password, this.options, this.ldapjs, undefined, this.logger); + } + + authenticate(username: string, password: string): BluebirdPromise { + const self = this; + const userDN = buildUserDN(username, this.options); + const userClient = this.createClient(userDN, password); + const adminClient = this.createClient(this.options.user, this.options.password); + let attributes: Attributes; + + return userClient.open() + .then(function () { + return userClient.close(); + }) + .then(function () { + return adminClient.open(); + }) + .then(function () { + return adminClient.searchEmailsAndGroups(username); + }) + .then(function (attr: Attributes) { + attributes = attr; + return adminClient.close(); + }) + .then(function () { + return BluebirdPromise.resolve(attributes); + }) + .error(function (err: Error) { + return BluebirdPromise.reject(new exceptions.LdapError(err.message)); + }); + } +} \ No newline at end of file diff --git a/src/server/lib/LdapClient.ts b/src/server/lib/ldap/Client.ts similarity index 52% rename from src/server/lib/LdapClient.ts rename to src/server/lib/ldap/Client.ts index 8504a38d2..c3c5632cd 100644 --- a/src/server/lib/LdapClient.ts +++ b/src/server/lib/ldap/Client.ts @@ -1,79 +1,65 @@ import util = require("util"); import BluebirdPromise = require("bluebird"); -import exceptions = require("./Exceptions"); -import Dovehash = require("dovehash"); +import exceptions = require("../Exceptions"); import ldapjs = require("ldapjs"); +import { buildUserDN } from "./common"; import { EventEmitter } from "events"; -import { LdapConfiguration } from "./../../types/Configuration"; -import { Ldapjs } from "../../types/Dependencies"; -import { Winston } from "../../types/Dependencies"; +import { LdapConfiguration } from "./../../../types/Configuration"; +import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies"; interface SearchEntry { object: any; } -export class LdapClient { - private options: LdapConfiguration; +export interface Attributes { + groups: string[]; + emails: string[]; +} + +export class Client { + private userDN: string; + private password: string; + private client: ldapjs.ClientAsync; + private ldapjs: Ldapjs; private logger: Winston; - private adminClient: ldapjs.ClientAsync; + private dovehash: Dovehash; + private options: LdapConfiguration; - constructor(options: LdapConfiguration, ldapjs: Ldapjs, logger: Winston) { + constructor(userDN: string, password: string, options: LdapConfiguration, ldapjs: Ldapjs, dovehash: Dovehash, logger: Winston) { this.options = options; this.ldapjs = ldapjs; + this.dovehash = dovehash; this.logger = logger; + this.userDN = userDN; + this.password = password; - this.connect(); - } - - private createClient(): ldapjs.ClientAsync { - const ldapClient = this.ldapjs.createClient({ + const ldapClient = ldapjs.createClient({ url: this.options.url, reconnect: true }); - ldapClient.on("error", function (err: Error) { - console.error("LDAP Error:", err.message); - }); + const clientLogger = (ldapClient as any).log; + if (clientLogger) { + clientLogger.level("trace"); + } - return BluebirdPromise.promisifyAll(ldapClient) as ldapjs.ClientAsync; + this.client = BluebirdPromise.promisifyAll(ldapClient) as ldapjs.ClientAsync; } - connect(): BluebirdPromise { - const userDN = this.options.user; - const password = this.options.password; - - this.adminClient = this.createClient(); - return this.adminClient.bindAsync(userDN, password); + open(): BluebirdPromise { + this.logger.debug("LDAP: Bind user '%s'", this.userDN); + return this.client.bindAsync(this.userDN, this.password) + .error(function (err: Error) { + return BluebirdPromise.reject(new exceptions.LdapBindError(err.message)); + }); } - private buildUserDN(username: string): string { - let userNameAttribute = this.options.user_name_attribute; - // if not provided, default to cn - if (!userNameAttribute) userNameAttribute = "cn"; - - const additionalUserDN = this.options.additional_user_dn; - const base_dn = this.options.base_dn; - - let userDN = util.format("%s=%s", userNameAttribute, username); - if (additionalUserDN) userDN += util.format(",%s", additionalUserDN); - userDN += util.format(",%s", base_dn); - return userDN; - } - - checkPassword(username: string, password: string): BluebirdPromise { - const userDN = this.buildUserDN(username); - const that = this; - const ldapClient = this.createClient(); - - this.logger.debug("LDAP: Check password by binding user '%s'", userDN); - return ldapClient.bindAsync(userDN, password) - .then(function () { - that.logger.debug("LDAP: Unbind user '%s'", userDN); - return ldapClient.unbindAsync(); - }) + close(): BluebirdPromise { + this.logger.debug("LDAP: Unbind user '%s'", this.userDN); + return this.client.unbindAsync() .error(function (err: Error) { return BluebirdPromise.reject(new exceptions.LdapBindError(err.message)); }); @@ -83,7 +69,7 @@ export class LdapClient { const that = this; that.logger.debug("LDAP: Search for '%s' in '%s'", JSON.stringify(query), base); - return that.adminClient.searchAsync(base, query) + return that.client.searchAsync(base, query) .then(function (res: EventEmitter) { const doc: SearchEntry[] = []; @@ -97,7 +83,7 @@ export class LdapClient { reject(new exceptions.LdapSearchError(err.message)); }); res.on("end", function () { - that.logger.debug("LDAP: Result of search is '%s'.", JSON.stringify(doc)); + that.logger.debug("LDAP: Search ended and results are '%s'.", JSON.stringify(doc)); resolve(doc); }); }); @@ -107,8 +93,9 @@ export class LdapClient { }); } - retrieveGroups(username: string): BluebirdPromise { - const userDN = this.buildUserDN(username); + private searchGroups(username: string): BluebirdPromise { + const that = this; + const userDN = buildUserDN(username, this.options); const password = this.options.password; let groupNameAttribute = this.options.group_name_attribute; @@ -127,24 +114,22 @@ export class LdapClient { filter: "member=" + userDN }; - const that = this; - this.logger.debug("LDAP: get groups of user %s", username); const groups: string[] = []; return that.search(groupDN, query) .then(function (docs) { for (let i = 0; i < docs.length; ++i) { groups.push(docs[i].cn); } - that.logger.debug("LDAP: got groups '%s'", groups); + that.logger.debug("LDAP: groups of user %s are %s", username, groups); }) .then(function () { return BluebirdPromise.resolve(groups); }); } - retrieveEmails(username: string): BluebirdPromise { + searchEmails(username: string): BluebirdPromise { const that = this; - const user_dn = this.buildUserDN(username); + const userDN = buildUserDN(username, this.options); const query = { scope: "base", @@ -152,8 +137,7 @@ export class LdapClient { attributes: ["mail"] }; - this.logger.debug("LDAP: get emails of user '%s'", username); - return this.search(user_dn, query) + return this.search(userDN, query) .then(function (docs) { const emails = []; for (let i = 0; i < docs.length; ++i) { @@ -163,29 +147,45 @@ export class LdapClient { emails.concat(docs[i].mail); } } - that.logger.debug("LDAP: got emails '%s'", emails); + that.logger.debug("LDAP: emails of user '%s' are %s", username, emails); return BluebirdPromise.resolve(emails); }); } - updatePassword(username: string, newPassword: string): BluebirdPromise { - const user_dn = this.buildUserDN(username); + searchEmailsAndGroups(username: string): BluebirdPromise { + const that = this; + let retrievedEmails: string[], retrievedGroups: string[]; - const encoded_password = Dovehash.encode("SSHA", newPassword); + return this.searchEmails(username) + .then(function (emails: string[]) { + retrievedEmails = emails; + return that.searchGroups(username); + }) + .then(function (groups: string[]) { + retrievedGroups = groups; + return BluebirdPromise.resolve({ + emails: retrievedEmails, + groups: retrievedGroups + }); + }); + } + + modifyPassword(username: string, newPassword: string): BluebirdPromise { + const that = this; + const userDN = buildUserDN(username, this.options); + + const encodedPassword = this.dovehash.encode("SSHA", newPassword); const change = { operation: "replace", modification: { - userPassword: encoded_password + userPassword: encodedPassword } }; - const that = this; this.logger.debug("LDAP: update password of user '%s'", username); - - that.logger.debug("LDAP: modify password"); - return that.adminClient.modifyAsync(user_dn, change) + return this.client.modifyAsync(userDN, change) .then(function () { - return that.adminClient.unbindAsync(); + return that.client.unbindAsync(); }); } } diff --git a/src/server/lib/ldap/EmailsRetriever.ts b/src/server/lib/ldap/EmailsRetriever.ts new file mode 100644 index 000000000..edd2a6e2f --- /dev/null +++ b/src/server/lib/ldap/EmailsRetriever.ts @@ -0,0 +1,46 @@ +import BluebirdPromise = require("bluebird"); +import exceptions = require("../Exceptions"); +import ldapjs = require("ldapjs"); +import { Client } from "./Client"; +import { buildUserDN } from "./common"; + +import { LdapConfiguration } from "./../../../types/Configuration"; +import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies"; + + +export class EmailsRetriever { + private options: LdapConfiguration; + private ldapjs: Ldapjs; + private logger: Winston; + + constructor(options: LdapConfiguration, ldapjs: Ldapjs, logger: Winston) { + this.options = options; + this.ldapjs = ldapjs; + this.logger = logger; + } + + private createClient(userDN: string, password: string): Client { + return new Client(userDN, password, this.options, this.ldapjs, undefined, this.logger); + } + + retrieve(username: string): BluebirdPromise { + const userDN = buildUserDN(username, this.options); + const adminClient = this.createClient(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); + }) + .error(function (err: Error) { + return BluebirdPromise.reject(new exceptions.LdapError("Failed during password update: " + err.message)); + }); + } +} diff --git a/src/server/lib/ldap/PasswordUpdater.ts b/src/server/lib/ldap/PasswordUpdater.ts new file mode 100644 index 000000000..8134c3b7a --- /dev/null +++ b/src/server/lib/ldap/PasswordUpdater.ts @@ -0,0 +1,43 @@ +import BluebirdPromise = require("bluebird"); +import exceptions = require("../Exceptions"); +import ldapjs = require("ldapjs"); +import { Client } from "./Client"; +import { buildUserDN } from "./common"; + +import { LdapConfiguration } from "./../../../types/Configuration"; +import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies"; + + +export class PasswordUpdater { + private options: LdapConfiguration; + private ldapjs: Ldapjs; + private logger: Winston; + private dovehash: Dovehash; + + constructor(options: LdapConfiguration, ldapjs: Ldapjs, dovehash: Dovehash, logger: Winston) { + this.options = options; + this.ldapjs = ldapjs; + this.logger = logger; + this.dovehash = dovehash; + } + + private createClient(userDN: string, password: string): Client { + return new Client(userDN, password, this.options, this.ldapjs, this.dovehash, this.logger); + } + + updatePassword(username: string, newPassword: string): BluebirdPromise { + const userDN = buildUserDN(username, this.options); + const adminClient = this.createClient(this.options.user, this.options.password); + + return adminClient.open() + .then(function () { + return adminClient.modifyPassword(username, newPassword); + }) + .then(function () { + return adminClient.close(); + }) + .error(function (err: Error) { + return BluebirdPromise.reject(new exceptions.LdapError("Failed during password update: " + err.message)); + }); + } +} diff --git a/src/server/lib/ldap/common.ts b/src/server/lib/ldap/common.ts new file mode 100644 index 000000000..b9a4c755a --- /dev/null +++ b/src/server/lib/ldap/common.ts @@ -0,0 +1,18 @@ +import util = require("util"); + +import { LdapConfiguration } from "./../../../types/Configuration"; + + +export function buildUserDN(username: string, options: LdapConfiguration): string { + let userNameAttribute = options.user_name_attribute; + // if not provided, default to cn + if (!userNameAttribute) userNameAttribute = "cn"; + + const additionalUserDN = options.additional_user_dn; + const base_dn = options.base_dn; + + let userDN = util.format("%s=%s", userNameAttribute, username); + if (additionalUserDN) userDN += util.format(",%s", additionalUserDN); + userDN += util.format(",%s", base_dn); + return userDN; +} \ No newline at end of file diff --git a/src/server/lib/routes/firstfactor/post.ts b/src/server/lib/routes/firstfactor/post.ts index aef3ff0fa..ded2b58c1 100644 --- a/src/server/lib/routes/firstfactor/post.ts +++ b/src/server/lib/routes/firstfactor/post.ts @@ -5,7 +5,7 @@ import BluebirdPromise = require("bluebird"); import express = require("express"); import { AccessController } from "../../access_control/AccessController"; import { AuthenticationRegulator } from "../../AuthenticationRegulator"; -import { LdapClient } from "../../LdapClient"; +import { Client, Attributes } from "../../ldap/Client"; import Endpoint = require("../../../endpoints"); import ErrorReplies = require("../../ErrorReplies"); import ServerVariables = require("../../ServerVariables"); @@ -16,7 +16,7 @@ export default function (req: express.Request, res: express.Response): BluebirdP const password: string = req.body.password; const logger = ServerVariables.getLogger(req.app); - const ldap = ServerVariables.getLdapClient(req.app); + const ldap = ServerVariables.getLdapAuthenticator(req.app); const config = ServerVariables.getConfiguration(req.app); if (!username || !password) { @@ -36,18 +36,15 @@ export default function (req: express.Request, res: express.Response): BluebirdP return regulator.regulate(username) .then(function () { logger.info("1st factor: No regulation applied."); - return ldap.checkPassword(username, password); + return ldap.authenticate(username, password); }) - .then(function () { - logger.info("1st factor: LDAP binding successful"); + .then(function (attributes: Attributes) { + logger.info("1st factor: LDAP binding successful. Retrieved information about user are %s", JSON.stringify(attributes)); authSession.userid = username; authSession.first_factor = true; - logger.debug("1st factor: Retrieve email from LDAP"); - return BluebirdPromise.join(ldap.retrieveEmails(username), ldap.retrieveGroups(username)); - }) - .then(function (data: [string[], string[]]) { - const emails: string[] = data[0]; - const groups: string[] = data[1]; + + const emails: string[] = attributes.emails; + const groups: string[] = attributes.groups; if (!emails || emails.length <= 0) { const errMessage = "No emails found. The user should have at least one email address to reset password."; @@ -55,12 +52,12 @@ export default function (req: express.Request, res: express.Response): BluebirdP return BluebirdPromise.reject(new Error(errMessage)); } - logger.debug("1st factor: Retrieved email are %s", emails); - logger.debug("1st factor: Retrieved groups are %s", groups); authSession.email = emails[0]; authSession.groups = groups; + logger.debug("1st factor: Mark successful authentication to regulator."); regulator.mark(username, true); + logger.debug("1st factor: Redirect to %s", Endpoint.SECOND_FACTOR_GET); res.redirect(Endpoint.SECOND_FACTOR_GET); return BluebirdPromise.resolve(); diff --git a/src/server/lib/routes/password-reset/form/post.ts b/src/server/lib/routes/password-reset/form/post.ts index 5a2cd45dd..addfb1615 100644 --- a/src/server/lib/routes/password-reset/form/post.ts +++ b/src/server/lib/routes/password-reset/form/post.ts @@ -11,10 +11,10 @@ import Constants = require("./../constants"); export default function (req: express.Request, res: express.Response): BluebirdPromise { const logger = ServerVariables.getLogger(req.app); - const ldap = ServerVariables.getLdapClient(req.app); + const ldapPasswordUpdater = ServerVariables.getLdapPasswordUpdater(req.app); const authSession = AuthenticationSession.get(req); - const new_password = objectPath.get(req, "body.password"); + const newPassword = objectPath.get(req, "body.password"); const userid = authSession.identity_check.userid; const challenge = authSession.identity_check.challenge; @@ -26,7 +26,7 @@ export default function (req: express.Request, res: express.Response): BluebirdP logger.info("POST reset-password: User %s wants to reset his/her password", userid); - return ldap.updatePassword(userid, new_password) + return ldapPasswordUpdater.updatePassword(userid, newPassword) .then(function () { logger.info("POST reset-password: Password reset for user '%s'", userid); AuthenticationSession.reset(req); diff --git a/src/server/lib/routes/password-reset/identity/PasswordResetHandler.ts b/src/server/lib/routes/password-reset/identity/PasswordResetHandler.ts index 898d55325..f2c44183a 100644 --- a/src/server/lib/routes/password-reset/identity/PasswordResetHandler.ts +++ b/src/server/lib/routes/password-reset/identity/PasswordResetHandler.ts @@ -25,8 +25,8 @@ export default class PasswordResetHandler implements IdentityValidable { if (!userid) return BluebirdPromise.reject(new exceptions.AccessDeniedError("No user id provided")); - const ldap = ServerVariables.getLdapClient(req.app); - return ldap.retrieveEmails(userid) + const emailsRetriever = ServerVariables.getLdapEmailsRetriever(req.app); + return emailsRetriever.retrieve(userid) .then(function (emails: string[]) { if (!emails && emails.length <= 0) throw new Error("No email found"); diff --git a/src/types/Dependencies.ts b/src/types/Dependencies.ts index beabd9cfc..ffda38148 100644 --- a/src/types/Dependencies.ts +++ b/src/types/Dependencies.ts @@ -6,7 +6,9 @@ import nedb = require("nedb"); import ldapjs = require("ldapjs"); import u2f = require("u2f"); import RedisSession = require("connect-redis"); +import dovehash = require("dovehash"); +export type Dovehash = typeof dovehash; export type Nodemailer = typeof nodemailer; export type Speakeasy = typeof speakeasy; export type Winston = typeof winston; @@ -18,6 +20,7 @@ export type ConnectRedis = typeof RedisSession; export interface GlobalDependencies { u2f: U2f; + dovehash: Dovehash; nodemailer: Nodemailer; ldapjs: Ldapjs; session: Session; diff --git a/src/types/ldapjs-async.d.ts b/src/types/ldapjs-async.d.ts index 669c15e64..9fce7f516 100644 --- a/src/types/ldapjs-async.d.ts +++ b/src/types/ldapjs-async.d.ts @@ -4,6 +4,7 @@ import { EventEmitter } from "events"; declare module "ldapjs" { export interface ClientAsync { + on(event: string, callback: (data?: any) => void): void; bindAsync(username: string, password: string): BluebirdPromise; unbindAsync(): BluebirdPromise; searchAsync(base: string, query: ldapjs.SearchOptions): BluebirdPromise; diff --git a/test/unit/server/DataPersistence.test.ts b/test/unit/server/DataPersistence.test.ts index 539285737..26d86c9b0 100644 --- a/test/unit/server/DataPersistence.test.ts +++ b/test/unit/server/DataPersistence.test.ts @@ -48,6 +48,8 @@ describe("test data persistence", function () { }) }; + ldapClient.bind.withArgs("cn=admin,dc=example,dc=com", + "password").yields(); ldapClient.bind.withArgs("cn=test_ok,ou=users,dc=example,dc=com", "password").yields(); ldapClient.bind.withArgs("cn=test_nok,ou=users,dc=example,dc=com", @@ -61,7 +63,7 @@ describe("test data persistence", function () { ldap: { url: "ldap://127.0.0.1:389", base_dn: "ou=users,dc=example,dc=com", - user: "user", + user: "cn=admin,dc=example,dc=com", password: "password" }, session: { @@ -108,7 +110,8 @@ describe("test data persistence", function () { winston: winston, ldapjs: ldap, speakeasy: speakeasy, - ConnectRedis: sinon.spy() + ConnectRedis: sinon.spy(), + dovehash: sinon.spy() }; const j1 = request.jar(); diff --git a/test/unit/server/IdentityCheckMiddleware.test.ts b/test/unit/server/IdentityCheckMiddleware.test.ts index 5aa8d2f81..a63c47ae7 100644 --- a/test/unit/server/IdentityCheckMiddleware.test.ts +++ b/test/unit/server/IdentityCheckMiddleware.test.ts @@ -49,7 +49,7 @@ describe("test identity check process", function () { req.app = {}; const mocks = ServerVariablesMock.mock(req.app); mocks.logger = winston; - mocks.userDataStore = userDataStore; + mocks.userDataStore = userDataStore as any; mocks.notifier = notifier; app = express(); diff --git a/test/unit/server/LdapClient.test.ts b/test/unit/server/LdapClient.test.ts deleted file mode 100644 index 729de3aca..000000000 --- a/test/unit/server/LdapClient.test.ts +++ /dev/null @@ -1,242 +0,0 @@ - -import LdapClient = require("../../../src/server/lib/LdapClient"); -import { LdapConfiguration } from "../../../src/types/Configuration"; - -import sinon = require("sinon"); -import BluebirdPromise = require("bluebird"); -import assert = require("assert"); -import ldapjs = require("ldapjs"); -import winston = require("winston"); -import { EventEmitter } from "events"; - -import { LdapjsMock, LdapjsClientMock } from "./mocks/ldapjs"; - - -describe("test ldap validation", function () { - let ldap: LdapClient.LdapClient; - let ldapClient: LdapjsClientMock; - let ldapjs: LdapjsMock; - let ldapConfig: LdapConfiguration; - - beforeEach(function () { - ldapClient = LdapjsClientMock(); - ldapjs = LdapjsMock(); - ldapjs.createClient.returns(ldapClient); - - ldapConfig = { - url: "http://localhost:324", - user: "admin", - password: "password", - base_dn: "dc=example,dc=com", - additional_user_dn: "ou=users" - }; - - ldap = new LdapClient.LdapClient(ldapConfig, ldapjs, winston); - }); - - describe("test checking password", test_checking_password); - describe("test get emails from username", test_get_emails); - describe("test get groups from username", test_get_groups); - describe("test update password", test_update_password); - - function test_checking_password() { - function test_check_password_internal() { - const username = "username"; - const password = "password"; - return ldap.checkPassword(username, password); - } - - it("should bind the user if good credentials provided", function () { - ldapClient.bind.yields(); - ldapClient.unbind.yields(); - return test_check_password_internal(); - }); - - it("should bind the user with correct DN", function () { - ldapConfig.user_name_attribute = "uid"; - const username = "user"; - const password = "password"; - ldapClient.bind.withArgs("uid=user,ou=users,dc=example,dc=com").yields(); - ldapClient.unbind.yields(); - return ldap.checkPassword(username, password); - }); - - it("should default to cn user search filter if no filter provided", function () { - const username = "user"; - const password = "password"; - ldapClient.bind.withArgs("cn=user,ou=users,dc=example,dc=com").yields(); - ldapClient.unbind.yields(); - return ldap.checkPassword(username, password); - }); - - it("should not bind the user if wrong credentials provided", function () { - ldapClient.bind.yields("wrong credentials"); - const promise = test_check_password_internal(); - return promise.catch(function () { - return BluebirdPromise.resolve(); - }); - }); - } - - function test_get_emails() { - let res_emitter: any; - let expected_doc: any; - - beforeEach(function () { - expected_doc = { - object: { - mail: "user@example.com" - } - }; - - res_emitter = { - on: sinon.spy(function (event: string, fn: (doc: any) => void) { - if (event != "error") fn(expected_doc); - }) - }; - }); - - it("should retrieve the email of an existing user", function () { - ldapClient.search.yields(undefined, res_emitter); - - return ldap.retrieveEmails("user") - .then(function (emails) { - assert.deepEqual(emails, [expected_doc.object.mail]); - return BluebirdPromise.resolve(); - }); - }); - - it("should retrieve email for user with uid name attribute", function () { - ldapConfig.user_name_attribute = "uid"; - ldapClient.search.withArgs("uid=username,ou=users,dc=example,dc=com").yields(undefined, res_emitter); - return ldap.retrieveEmails("username") - .then(function (emails) { - assert.deepEqual(emails, ["user@example.com"]); - return BluebirdPromise.resolve(); - }); - }); - - it("should fail on error with search method", function () { - const expected_doc = { - mail: ["user@example.com"] - }; - ldapClient.search.yields("Error while searching mails"); - - return ldap.retrieveEmails("user") - .catch(function () { - return BluebirdPromise.resolve(); - }); - }); - } - - function test_get_groups() { - let res_emitter: any; - let expected_doc1: any, expected_doc2: any; - - beforeEach(function () { - expected_doc1 = { - object: { - cn: "group1" - } - }; - - expected_doc2 = { - object: { - cn: "group2" - } - }; - - res_emitter = { - on: sinon.spy(function (event: string, fn: (doc: any) => void) { - if (event != "error") fn(expected_doc1); - if (event != "error") fn(expected_doc2); - }) - }; - }); - - it("should retrieve the groups of an existing user", function () { - ldapClient.search.yields(undefined, res_emitter); - return ldap.retrieveGroups("user") - .then(function (groups) { - assert.deepEqual(groups, ["group1", "group2"]); - return BluebirdPromise.resolve(); - }); - }); - - it("should reduce the scope to additional_group_dn", function (done) { - ldapConfig.additional_group_dn = "ou=groups"; - ldapClient.search.yields(undefined, res_emitter); - ldap.retrieveGroups("user") - .then(function() { - assert.equal(ldapClient.search.getCall(0).args[0], "ou=groups,dc=example,dc=com"); - done(); - }); - }); - - it("should use default group_name_attr if not provided", function (done) { - ldapClient.search.yields(undefined, res_emitter); - ldap.retrieveGroups("user") - .then(function() { - assert.equal(ldapClient.search.getCall(0).args[0], "dc=example,dc=com"); - assert.equal(ldapClient.search.getCall(0).args[1].filter, "member=cn=user,ou=users,dc=example,dc=com"); - assert.deepEqual(ldapClient.search.getCall(0).args[1].attributes, ["cn"]); - done(); - }); - }); - - it("should fail on error with search method", function () { - ldapClient.search.yields("error"); - return ldap.retrieveGroups("user") - .catch(function () { - return BluebirdPromise.resolve(); - }); - }); - } - - function test_update_password() { - it("should update the password successfully", function () { - const change = { - operation: "replace", - modification: { - userPassword: "new-password" - } - }; - const userdn = "cn=user,ou=users,dc=example,dc=com"; - - ldapClient.bind.yields(); - ldapClient.unbind.yields(); - ldapClient.modify.yields(); - - return ldap.updatePassword("user", "new-password") - .then(function () { - assert.deepEqual(ldapClient.modify.getCall(0).args[0], userdn); - assert.deepEqual(ldapClient.modify.getCall(0).args[1].operation, change.operation); - - const userPassword = ldapClient.modify.getCall(0).args[1].modification.userPassword; - assert(/{SSHA}/.test(userPassword)); - return BluebirdPromise.resolve(); - }) - .catch(function(err) { return BluebirdPromise.reject(new Error("It should fail")); }); - }); - - it("should fail when ldap throws an error", function () { - ldapClient.bind.yields(undefined); - ldapClient.modify.yields("Error"); - - return ldap.updatePassword("user", "new-password") - .catch(function () { - return BluebirdPromise.resolve(); - }); - }); - - it("should update password of user using particular user name attribute", function () { - ldapConfig.user_name_attribute = "uid"; - - ldapClient.bind.yields(); - ldapClient.unbind.yields(); - ldapClient.modify.withArgs("uid=username,ou=users,dc=example,dc=com").yields(); - return ldap.updatePassword("username", "newpass"); - }); - } -}); - diff --git a/test/unit/server/ServerConfiguration.test.ts b/test/unit/server/ServerConfiguration.test.ts index 5b1220d80..e5c17a034 100644 --- a/test/unit/server/ServerConfiguration.test.ts +++ b/test/unit/server/ServerConfiguration.test.ts @@ -1,6 +1,6 @@ import assert = require("assert"); -import sinon = require("sinon"); +import Sinon = require("sinon"); import nedb = require("nedb"); import express = require("express"); import winston = require("winston"); @@ -16,17 +16,17 @@ import Server from "../../../src/server/lib/Server"; describe("test server configuration", function () { let deps: GlobalDependencies; - let sessionMock: sinon.SinonSpy; + let sessionMock: Sinon.SinonSpy; before(function () { const transporter = { - sendMail: sinon.stub().yields() + sendMail: Sinon.stub().yields() }; - const createTransport = sinon.stub(nodemailer, "createTransport"); + const createTransport = Sinon.stub(nodemailer, "createTransport"); createTransport.returns(transporter); - sessionMock = sinon.spy(session); + sessionMock = Sinon.spy(session); deps = { nodemailer: nodemailer, @@ -35,15 +35,16 @@ describe("test server configuration", function () { nedb: nedb, winston: winston, ldapjs: { - createClient: sinon.spy(function () { + createClient: Sinon.spy(function () { return { - on: sinon.spy(), - bind: sinon.spy(), + on: Sinon.spy(), + bind: Sinon.spy(), }; }) }, session: sessionMock as any, - ConnectRedis: sinon.spy() + ConnectRedis: Sinon.spy(), + dovehash: Sinon.spy() as any }; }); diff --git a/test/unit/server/SessionConfigurationBuilder.test.ts b/test/unit/server/SessionConfigurationBuilder.test.ts index 97f87ef0b..b148d0d84 100644 --- a/test/unit/server/SessionConfigurationBuilder.test.ts +++ b/test/unit/server/SessionConfigurationBuilder.test.ts @@ -3,7 +3,7 @@ import { AppConfiguration } from "../../../src/types/Configuration"; import { GlobalDependencies } from "../../../src/types/Dependencies"; import ExpressSession = require("express-session"); import ConnectRedis = require("connect-redis"); -import sinon = require("sinon"); +import Sinon = require("sinon"); import Assert = require("assert"); describe("test session configuration builder", function () { @@ -36,14 +36,15 @@ describe("test session configuration builder", function () { }; const deps: GlobalDependencies = { - ConnectRedis: sinon.spy() as any, - ldapjs: sinon.spy() as any, - nedb: sinon.spy() as any, - nodemailer: sinon.spy() as any, - session: sinon.spy() as any, - speakeasy: sinon.spy() as any, - u2f: sinon.spy() as any, - winston: sinon.spy() as any + ConnectRedis: Sinon.spy() as any, + ldapjs: Sinon.spy() as any, + nedb: Sinon.spy() as any, + nodemailer: Sinon.spy() as any, + session: Sinon.spy() as any, + speakeasy: Sinon.spy() as any, + u2f: Sinon.spy() as any, + winston: Sinon.spy() as any, + dovehash: Sinon.spy() as any }; const options = SessionConfigurationBuilder.build(configuration, deps); @@ -94,17 +95,18 @@ describe("test session configuration builder", function () { store_in_memory: true }; - const RedisStoreMock = sinon.spy(); + const RedisStoreMock = Sinon.spy(); const deps: GlobalDependencies = { - ConnectRedis: sinon.stub().returns(RedisStoreMock) as any, - ldapjs: sinon.spy() as any, - nedb: sinon.spy() as any, - nodemailer: sinon.spy() as any, - session: sinon.spy() as any, - speakeasy: sinon.spy() as any, - u2f: sinon.spy() as any, - winston: sinon.spy() as any + ConnectRedis: Sinon.stub().returns(RedisStoreMock) as any, + ldapjs: Sinon.spy() as any, + nedb: Sinon.spy() as any, + nodemailer: Sinon.spy() as any, + session: Sinon.spy() as any, + speakeasy: Sinon.spy() as any, + u2f: Sinon.spy() as any, + winston: Sinon.spy() as any, + dovehash: Sinon.spy() as any }; const options = SessionConfigurationBuilder.build(configuration, deps); @@ -118,10 +120,10 @@ describe("test session configuration builder", function () { maxAge: 3600, domain: "example.com" }, - store: sinon.match.object as any + store: Sinon.match.object as any }; - Assert((deps.ConnectRedis as sinon.SinonStub).calledWith(deps.session)); + Assert((deps.ConnectRedis as Sinon.SinonStub).calledWith(deps.session)); Assert.equal(options.secret, expectedOptions.secret); Assert.equal(options.resave, expectedOptions.resave); Assert.equal(options.saveUninitialized, expectedOptions.saveUninitialized); diff --git a/test/unit/server/ldap/Authenticator.test.ts b/test/unit/server/ldap/Authenticator.test.ts new file mode 100644 index 000000000..cc652f6b7 --- /dev/null +++ b/test/unit/server/ldap/Authenticator.test.ts @@ -0,0 +1,125 @@ + +import { Authenticator } from "../../../../src/server/lib/ldap/Authenticator"; +import { LdapConfiguration } from "../../../../src/types/Configuration"; + +import sinon = require("sinon"); +import BluebirdPromise = require("bluebird"); +import assert = require("assert"); +import ldapjs = require("ldapjs"); +import winston = require("winston"); +import { EventEmitter } from "events"; + +import { LdapjsMock, LdapjsClientMock } from "../mocks/ldapjs"; + + +describe("test ldap authentication", function () { + let authenticator: Authenticator; + let ldapClient: LdapjsClientMock; + let ldapjs: LdapjsMock; + let ldapConfig: LdapConfiguration; + let adminUserDN: string; + let adminPassword: string; + + function retrieveEmailsAndGroups(ldapClient: LdapjsClientMock) { + const email0 = { + object: { + mail: "user@example.com" + } + }; + + const email1 = { + object: { + mail: "user@example1.com" + } + }; + + const group0 = { + object: { + group: "group0" + } + }; + + const emailsEmitter = { + on: sinon.spy(function (event: string, fn: (doc: any) => void) { + if (event != "error") fn(email0); + if (event != "error") fn(email1); + }) + }; + + const groupsEmitter = { + on: sinon.spy(function (event: string, fn: (doc: any) => void) { + if (event != "error") fn(group0); + }) + }; + + ldapClient.search.onCall(0).yields(undefined, emailsEmitter); + ldapClient.search.onCall(1).yields(undefined, groupsEmitter); + } + + beforeEach(function () { + ldapClient = LdapjsClientMock(); + ldapjs = LdapjsMock(); + ldapjs.createClient.returns(ldapClient); + + // winston.level = "debug"; + + adminUserDN = "cn=admin,dc=example,dc=com"; + adminPassword = "password"; + + ldapConfig = { + url: "http://localhost:324", + user: adminUserDN, + password: adminPassword, + base_dn: "dc=example,dc=com", + additional_user_dn: "ou=users" + }; + + authenticator = new Authenticator(ldapConfig, ldapjs, winston); + }); + + function test_check_password_internal() { + const username = "username"; + const password = "password"; + return authenticator.authenticate(username, password); + } + + describe("success", function () { + beforeEach(function () { + retrieveEmailsAndGroups(ldapClient); + ldapClient.bind.withArgs(adminUserDN, adminPassword).yields(); + ldapClient.unbind.yields(); + }); + + it("should bind the user if good credentials provided", function () { + ldapClient.bind.withArgs("cn=username,ou=users,dc=example,dc=com", "password").yields(); + return test_check_password_internal(); + }); + + it("should bind the user with correct DN", function () { + ldapConfig.user_name_attribute = "uid"; + ldapClient.bind.withArgs("uid=username,ou=users,dc=example,dc=com", "password").yields(); + return test_check_password_internal(); + }); + }); + + describe("failure", function () { + it("should not bind the user if wrong credentials provided", function () { + ldapClient.bind.yields("wrong credentials"); + return test_check_password_internal() + .catch(function () { + return BluebirdPromise.resolve(); + }); + }); + + it("should not bind the user if search of emails or group fails", function () { + ldapClient.bind.withArgs("cn=username,ou=users,dc=example,dc=com", "password").yields(); + ldapClient.bind.withArgs(adminUserDN, adminPassword).yields(); + ldapClient.unbind.yields(); + ldapClient.search.yields("wrong credentials"); + return test_check_password_internal() + .catch(function () { + return BluebirdPromise.resolve(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/unit/server/ldap/EmailsRetriever.test.ts b/test/unit/server/ldap/EmailsRetriever.test.ts new file mode 100644 index 000000000..a5abe5a57 --- /dev/null +++ b/test/unit/server/ldap/EmailsRetriever.test.ts @@ -0,0 +1,94 @@ + +import { EmailsRetriever } from "../../../../src/server/lib/ldap/EmailsRetriever"; +import { LdapConfiguration } from "../../../../src/types/Configuration"; + +import sinon = require("sinon"); +import BluebirdPromise = require("bluebird"); +import assert = require("assert"); +import ldapjs = require("ldapjs"); +import winston = require("winston"); +import { EventEmitter } from "events"; + +import { LdapjsMock, LdapjsClientMock } from "../mocks/ldapjs"; + + +describe("test emails retriever", function () { + let emailsRetriever: EmailsRetriever; + let ldapClient: LdapjsClientMock; + let ldapjs: LdapjsMock; + let ldapConfig: LdapConfiguration; + let adminUserDN: string; + let adminPassword: string; + + beforeEach(function () { + ldapClient = LdapjsClientMock(); + ldapjs = LdapjsMock(); + ldapjs.createClient.returns(ldapClient); + + // winston.level = "debug"; + + adminUserDN = "cn=admin,dc=example,dc=com"; + adminPassword = "password"; + + ldapConfig = { + url: "http://localhost:324", + user: adminUserDN, + password: adminPassword, + base_dn: "dc=example,dc=com", + additional_user_dn: "ou=users" + }; + + emailsRetriever = new EmailsRetriever(ldapConfig, ldapjs, winston); + }); + + function retrieveEmails(ldapClient: LdapjsClientMock) { + const email0 = { + object: { + mail: "user@example.com" + } + }; + + const email1 = { + object: { + mail: "user@example1.com" + } + }; + + const emailsEmitter = { + on: sinon.spy(function (event: string, fn: (doc: any) => void) { + if (event != "error") fn(email0); + if (event != "error") fn(email1); + }) + }; + + ldapClient.search.onCall(0).yields(undefined, emailsEmitter); + } + + function test_emails_retrieval() { + const username = "username"; + return emailsRetriever.retrieve(username); + } + + describe("success", function () { + beforeEach(function () { + ldapClient.bind.withArgs(adminUserDN, adminPassword).yields(); + ldapClient.unbind.yields(); + }); + + it("should update the password successfully", function () { + retrieveEmails(ldapClient); + return test_emails_retrieval(); + }); + }); + + describe("failure", function () { + it("should fail retrieving emails when search operation fails", function () { + ldapClient.bind.withArgs(adminUserDN, adminPassword).yields(); + ldapClient.search.yields("wrong credentials"); + return test_emails_retrieval() + .catch(function () { + return BluebirdPromise.resolve(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/unit/server/ldap/PasswordUpdater.test.ts b/test/unit/server/ldap/PasswordUpdater.test.ts new file mode 100644 index 000000000..3d2e49a42 --- /dev/null +++ b/test/unit/server/ldap/PasswordUpdater.test.ts @@ -0,0 +1,83 @@ + +import { PasswordUpdater } from "../../../../src/server/lib/ldap/PasswordUpdater"; +import { LdapConfiguration } from "../../../../src/types/Configuration"; + +import sinon = require("sinon"); +import BluebirdPromise = require("bluebird"); +import assert = require("assert"); +import ldapjs = require("ldapjs"); +import winston = require("winston"); +import { EventEmitter } from "events"; + +import { LdapjsMock, LdapjsClientMock } from "../mocks/ldapjs"; + + +describe("test password update", function () { + let passwordUpdater: PasswordUpdater; + let ldapClient: LdapjsClientMock; + let ldapjs: LdapjsMock; + let ldapConfig: LdapConfiguration; + let adminUserDN: string; + let adminPassword: string; + let dovehash: any; + + beforeEach(function () { + ldapClient = LdapjsClientMock(); + ldapjs = LdapjsMock(); + ldapjs.createClient.returns(ldapClient); + + // winston.level = "debug"; + + adminUserDN = "cn=admin,dc=example,dc=com"; + adminPassword = "password"; + + ldapConfig = { + url: "http://localhost:324", + user: adminUserDN, + password: adminPassword, + base_dn: "dc=example,dc=com", + additional_user_dn: "ou=users" + }; + + dovehash = { + encode: sinon.stub() + }; + + passwordUpdater = new PasswordUpdater(ldapConfig, ldapjs, dovehash, winston); + }); + + function test_update_password() { + const username = "username"; + const newpassword = "newpassword"; + return passwordUpdater.updatePassword(username, newpassword); + } + + describe("success", function () { + beforeEach(function () { + ldapClient.bind.withArgs(adminUserDN, adminPassword).yields(); + ldapClient.unbind.yields(); + }); + + it("should update the password successfully", function () { + dovehash.encode.returns("{SSHA}AQmxaKfobGY9HSQa6aDYkAWOgPGNhGYn"); + ldapClient.modify.withArgs("cn=username,ou=users,dc=example,dc=com", { + operation: "replace", + modification: { + userPassword: "{SSHA}AQmxaKfobGY9HSQa6aDYkAWOgPGNhGYn" + } + }).yields(); + return test_update_password(); + }); + }); + + describe("failure", function () { + it("should fail updating password when modify operation fails", function () { + ldapClient.bind.withArgs(adminUserDN, adminPassword).yields(); + ldapClient.modify.yields("wrong credentials"); + return test_update_password() + .catch(function () { + return BluebirdPromise.resolve(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/unit/server/mocks/LdapClient.ts b/test/unit/server/mocks/LdapClient.ts deleted file mode 100644 index 416071cbd..000000000 --- a/test/unit/server/mocks/LdapClient.ts +++ /dev/null @@ -1,20 +0,0 @@ - -import sinon = require("sinon"); - -export interface LdapClientMock { - checkPassword: sinon.SinonStub; - retrieveEmails: sinon.SinonStub; - retrieveGroups: sinon.SinonStub; - search: sinon.SinonStub; - updatePassword: sinon.SinonStub; -} - -export function LdapClientMock(): LdapClientMock { - return { - checkPassword: sinon.stub(), - retrieveEmails: sinon.stub(), - retrieveGroups: sinon.stub(), - search: sinon.stub(), - updatePassword: sinon.stub() - }; -} diff --git a/test/unit/server/mocks/ServerVariablesMock.ts b/test/unit/server/mocks/ServerVariablesMock.ts index d349e9565..8f5b65a08 100644 --- a/test/unit/server/mocks/ServerVariablesMock.ts +++ b/test/unit/server/mocks/ServerVariablesMock.ts @@ -16,18 +16,20 @@ export interface ServerVariablesMock { } -export function mock(app: express.Application): ServerVariablesMock { - const mocks: ServerVariablesMock = { - accessController: sinon.stub(), - config: sinon.stub(), - ldap: sinon.stub(), - logger: sinon.stub(), - notifier: sinon.stub(), - regulator: sinon.stub(), - totpGenerator: sinon.stub(), - totpValidator: sinon.stub(), - u2f: sinon.stub(), - userDataStore: sinon.stub() +export function mock(app: express.Application): ServerVariables { + const mocks: ServerVariables = { + accessController: sinon.stub() as any, + config: sinon.stub() as any, + ldapAuthenticator: sinon.stub() as any, + ldapEmailsRetriever: sinon.stub() as any, + ldapPasswordUpdater: sinon.stub() as any, + logger: sinon.stub() as any, + notifier: sinon.stub() as any, + regulator: sinon.stub() as any, + totpGenerator: sinon.stub() as any, + totpValidator: sinon.stub() as any, + u2f: sinon.stub() as any, + userDataStore: sinon.stub() as any, }; app.get = sinon.stub().withArgs(VARIABLES_KEY).returns(mocks); return mocks; diff --git a/test/unit/server/routes/firstfactor/post.test.ts b/test/unit/server/routes/firstfactor/post.test.ts index b2bcfff27..dea06aab5 100644 --- a/test/unit/server/routes/firstfactor/post.test.ts +++ b/test/unit/server/routes/firstfactor/post.test.ts @@ -11,9 +11,9 @@ import Endpoints = require("../../../../../src/server/endpoints"); import AuthenticationRegulatorMock = require("../../mocks/AuthenticationRegulator"); import AccessControllerMock = require("../../mocks/AccessController"); -import { LdapClientMock } from "../../mocks/LdapClient"; import ExpressMock = require("../../mocks/express"); import ServerVariablesMock = require("../../mocks/ServerVariablesMock"); +import { ServerVariables } from "../../../../../src/server/lib/ServerVariables"; describe("test the first factor validation route", function () { let req: ExpressMock.RequestMock; @@ -21,9 +21,9 @@ describe("test the first factor validation route", function () { let emails: string[]; let groups: string[]; let configuration; - let ldapMock: LdapClientMock; let regulator: AuthenticationRegulatorMock.AuthenticationRegulatorMock; let accessController: AccessControllerMock.AccessControllerMock; + let serverVariables: ServerVariables; beforeEach(function () { configuration = { @@ -36,8 +36,6 @@ describe("test the first factor validation route", function () { emails = ["test_ok@example.com"]; groups = ["group1", "group2" ]; - ldapMock = LdapClientMock(); - accessController = AccessControllerMock.AccessControllerMock(); accessController.isDomainAllowedForUser.returns(true); @@ -61,19 +59,24 @@ describe("test the first factor validation route", function () { AuthenticationSession.reset(req as any); - const mocks = ServerVariablesMock.mock(req.app); - mocks.ldap = ldapMock; - mocks.config = configuration; - mocks.logger = winston; - mocks.regulator = regulator; - mocks.accessController = accessController; + serverVariables = ServerVariablesMock.mock(req.app); + serverVariables.ldapAuthenticator = { + authenticate: sinon.stub() + } as any; + serverVariables.config = configuration as any; + serverVariables.logger = winston as any; + serverVariables.regulator = regulator as any; + serverVariables.accessController = accessController as any; res = ExpressMock.ResponseMock(); }); it("should redirect client to second factor page", function () { - ldapMock.checkPassword.withArgs("username").returns(BluebirdPromise.resolve()); - ldapMock.retrieveEmails.returns(BluebirdPromise.resolve(emails)); + (serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password") + .returns(BluebirdPromise.resolve({ + emails: emails, + groups: groups + })); const authSession = AuthenticationSession.get(req as any); return FirstFactorPost.default(req as any, res as any) .then(function () { @@ -83,24 +86,28 @@ describe("test the first factor validation route", function () { }); it("should retrieve email from LDAP", function () { - ldapMock.checkPassword.returns(BluebirdPromise.resolve()); - ldapMock.retrieveEmails = sinon.stub().withArgs("username").returns(BluebirdPromise.resolve([{ mail: ["test@example.com"] }])); + (serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password") + .returns(BluebirdPromise.resolve([{ mail: ["test@example.com"] }])); return FirstFactorPost.default(req as any, res as any); }); it("should set first email address as user session variable", function () { const emails = ["test_ok@example.com"]; const authSession = AuthenticationSession.get(req as any); - ldapMock.checkPassword.returns(BluebirdPromise.resolve()); - ldapMock.retrieveEmails.returns(BluebirdPromise.resolve(emails)); + (serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password") + .returns(BluebirdPromise.resolve({ + emails: emails, + groups: groups + })); return FirstFactorPost.default(req as any, res as any) .then(function () { assert.equal("test_ok@example.com", authSession.email); }); }); - it("should return status code 401 when LDAP binding throws", function () { - ldapMock.checkPassword.returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials"))); + it("should return status code 401 when LDAP authenticator throws", function () { + (serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password") + .returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials"))); return FirstFactorPost.default(req as any, res as any) .then(function () { assert.equal(401, res.status.getCall(0).args[0]); @@ -108,15 +115,6 @@ describe("test the first factor validation route", function () { }); }); - it("should return status code 500 when LDAP search throws", function () { - ldapMock.checkPassword.returns(BluebirdPromise.resolve()); - ldapMock.retrieveEmails.returns(BluebirdPromise.reject(new exceptions.LdapSearchError("error while retrieving emails"))); - return FirstFactorPost.default(req as any, res as any) - .then(function () { - assert.equal(500, res.status.getCall(0).args[0]); - }); - }); - it("should return status code 403 when regulator rejects authentication", function () { const err = new exceptions.AuthenticationRegulationError("Authentication regulation..."); regulator.regulate.returns(BluebirdPromise.reject(err)); @@ -126,17 +124,6 @@ describe("test the first factor validation route", function () { assert.equal(1, res.send.callCount); }); }); - - it("should fail when admin user does not have rights to retrieve attribute mail", function () { - ldapMock.checkPassword.returns(BluebirdPromise.resolve()); - ldapMock.retrieveEmails = sinon.stub().withArgs("username").returns(BluebirdPromise.resolve([])); - ldapMock.retrieveGroups = sinon.stub().withArgs("username").returns(BluebirdPromise.resolve(["group1"])); - return FirstFactorPost.default(req as any, res as any) - .then(function () { - assert.equal(500, res.status.getCall(0).args[0]); - assert.equal(1, res.send.callCount); - }); - }); }); diff --git a/test/unit/server/routes/password-reset/identity/PasswordResetHandler.test.ts b/test/unit/server/routes/password-reset/identity/PasswordResetHandler.test.ts index f19e77ae7..177f5f4b7 100644 --- a/test/unit/server/routes/password-reset/identity/PasswordResetHandler.test.ts +++ b/test/unit/server/routes/password-reset/identity/PasswordResetHandler.test.ts @@ -1,22 +1,22 @@ import PasswordResetHandler from "../../../../../../src/server/lib/routes/password-reset/identity/PasswordResetHandler"; -import LdapClient = require("../../../../../../src/server/lib/LdapClient"); +import PasswordUpdater = require("../../../../../../src/server/lib/ldap/PasswordUpdater"); +import { ServerVariables } from "../../../../../../src/server/lib/ServerVariables"; import sinon = require("sinon"); import winston = require("winston"); import assert = require("assert"); import BluebirdPromise = require("bluebird"); import ExpressMock = require("../../../mocks/express"); -import { LdapClientMock } from "../../../mocks/LdapClient"; import { UserDataStore } from "../../../mocks/UserDataStore"; import ServerVariablesMock = require("../../../mocks/ServerVariablesMock"); describe("test reset password identity check", function () { let req: ExpressMock.RequestMock; let res: ExpressMock.ResponseMock; - let user_data_store: UserDataStore; - let ldap_client: LdapClientMock; + let userDataStore: UserDataStore; let configuration: any; + let serverVariables: ServerVariables; beforeEach(function () { req = { @@ -43,15 +43,15 @@ describe("test reset password identity check", function () { inMemoryOnly: true }; - const mocks = ServerVariablesMock.mock(req.app); + serverVariables = ServerVariablesMock.mock(req.app); - user_data_store = UserDataStore(); - user_data_store.set_u2f_meta.returns(BluebirdPromise.resolve({})); - user_data_store.get_u2f_meta.returns(BluebirdPromise.resolve({})); - user_data_store.issue_identity_check_token.returns(BluebirdPromise.resolve({})); - user_data_store.consume_identity_check_token.returns(BluebirdPromise.resolve({})); - mocks.userDataStore = user_data_store; + userDataStore = UserDataStore(); + userDataStore.set_u2f_meta.returns(BluebirdPromise.resolve({})); + userDataStore.get_u2f_meta.returns(BluebirdPromise.resolve({})); + userDataStore.issue_identity_check_token.returns(BluebirdPromise.resolve({})); + userDataStore.consume_identity_check_token.returns(BluebirdPromise.resolve({})); + serverVariables.userDataStore = userDataStore as any; configuration = { @@ -61,11 +61,11 @@ describe("test reset password identity check", function () { } }; - mocks.logger = winston; - mocks.config = configuration; - - ldap_client = LdapClientMock(); - mocks.ldap = ldap_client; + serverVariables.logger = winston; + serverVariables.config = configuration; + serverVariables.ldapEmailsRetriever = { + retrieve: sinon.stub() + } as any; res = ExpressMock.ResponseMock(); }); @@ -82,7 +82,7 @@ describe("test reset password identity check", function () { }); it("should fail if ldap fail", function (done) { - ldap_client.retrieveEmails.returns(BluebirdPromise.reject("Internal error")); + (serverVariables.ldapEmailsRetriever as any).retrieve.returns(BluebirdPromise.reject("Internal error")); new PasswordResetHandler().preValidationInit(req as any) .catch(function (err: Error) { done(); @@ -91,16 +91,16 @@ describe("test reset password identity check", function () { it("should perform a search in ldap to find email address", function (done) { configuration.ldap.user_name_attribute = "uid"; - ldap_client.retrieveEmails.returns(BluebirdPromise.resolve([])); + (serverVariables.ldapEmailsRetriever as any).retrieve.returns(BluebirdPromise.resolve([])); new PasswordResetHandler().preValidationInit(req as any) .then(function () { - assert.equal("user", ldap_client.retrieveEmails.getCall(0).args[0]); + assert.equal("user", (serverVariables.ldapEmailsRetriever as any).retrieve.getCall(0).args[0]); done(); }); }); it("should returns identity when ldap replies", function (done) { - ldap_client.retrieveEmails.returns(BluebirdPromise.resolve(["test@example.com"])); + (serverVariables.ldapEmailsRetriever as any).retrieve.returns(BluebirdPromise.resolve(["test@example.com"])); new PasswordResetHandler().preValidationInit(req as any) .then(function () { done(); diff --git a/test/unit/server/routes/password-reset/post.test.ts b/test/unit/server/routes/password-reset/post.test.ts index 1ef436e8b..40ec930b1 100644 --- a/test/unit/server/routes/password-reset/post.test.ts +++ b/test/unit/server/routes/password-reset/post.test.ts @@ -1,24 +1,24 @@ import PasswordResetFormPost = require("../../../../../src/server/lib/routes/password-reset/form/post"); -import LdapClient = require("../../../../../src/server/lib/LdapClient"); +import { PasswordUpdater } from "../../../../../src/server/lib/ldap/PasswordUpdater"; import AuthenticationSession = require("../../../../../src/server/lib/AuthenticationSession"); +import { ServerVariables } from "../../../../../src/server/lib/ServerVariables"; import sinon = require("sinon"); import winston = require("winston"); import assert = require("assert"); import BluebirdPromise = require("bluebird"); import ExpressMock = require("../../mocks/express"); -import { LdapClientMock } from "../../mocks/LdapClient"; import { UserDataStore } from "../../mocks/UserDataStore"; import ServerVariablesMock = require("../../mocks/ServerVariablesMock"); describe("test reset password route", function () { let req: ExpressMock.RequestMock; let res: ExpressMock.ResponseMock; - let user_data_store: UserDataStore; - let ldapClient: LdapClientMock; + let userDataStore: UserDataStore; let configuration: any; let authSession: AuthenticationSession.AuthenticationSession; + let serverVariables: ServerVariables; beforeEach(function () { req = { @@ -45,13 +45,13 @@ describe("test reset password route", function () { inMemoryOnly: true }; - const mocks = ServerVariablesMock.mock(req.app); - user_data_store = UserDataStore(); - user_data_store.set_u2f_meta.returns(BluebirdPromise.resolve({})); - user_data_store.get_u2f_meta.returns(BluebirdPromise.resolve({})); - user_data_store.issue_identity_check_token.returns(BluebirdPromise.resolve({})); - user_data_store.consume_identity_check_token.returns(BluebirdPromise.resolve({})); - mocks.userDataStore = user_data_store; + serverVariables = ServerVariablesMock.mock(req.app); + userDataStore = UserDataStore(); + userDataStore.set_u2f_meta.returns(BluebirdPromise.resolve({})); + userDataStore.get_u2f_meta.returns(BluebirdPromise.resolve({})); + userDataStore.issue_identity_check_token.returns(BluebirdPromise.resolve({})); + userDataStore.consume_identity_check_token.returns(BluebirdPromise.resolve({})); + serverVariables.userDataStore = userDataStore as any; configuration = { @@ -61,11 +61,12 @@ describe("test reset password route", function () { } }; - mocks.logger = winston; - mocks.config = configuration; + serverVariables.logger = winston; + serverVariables.config = configuration; - ldapClient = LdapClientMock(); - mocks.ldap = ldapClient; + serverVariables.ldapPasswordUpdater = { + updatePassword: sinon.stub() + } as any; res = ExpressMock.ResponseMock(); }); @@ -79,8 +80,7 @@ describe("test reset password route", function () { req.body = {}; req.body.password = "new-password"; - ldapClient.updatePassword.returns(BluebirdPromise.resolve()); - ldapClient.checkPassword.returns(BluebirdPromise.resolve()); + (serverVariables.ldapPasswordUpdater.updatePassword as sinon.SinonStub).returns(BluebirdPromise.resolve()); return PasswordResetFormPost.default(req as any, res as any) .then(function () { const authSession = AuthenticationSession.get(req as any); @@ -111,8 +111,7 @@ describe("test reset password route", function () { req.body = {}; req.body.password = "new-password"; - ldapClient.checkPassword.yields(undefined); - ldapClient.updatePassword.returns(BluebirdPromise.reject("Internal error with LDAP")); + (serverVariables.ldapPasswordUpdater.updatePassword as sinon.SinonStub).returns(BluebirdPromise.reject("Internal error with LDAP")); res.send = sinon.spy(function () { assert.equal(res.status.getCall(0).args[0], 500); done(); diff --git a/test/unit/server/routes/secondfactor/totp/register/RegistrationHandler.test.ts b/test/unit/server/routes/secondfactor/totp/register/RegistrationHandler.test.ts index 2cb0be33d..ec5b850d8 100644 --- a/test/unit/server/routes/secondfactor/totp/register/RegistrationHandler.test.ts +++ b/test/unit/server/routes/secondfactor/totp/register/RegistrationHandler.test.ts @@ -43,7 +43,7 @@ describe("test totp register", function () { userDataStore.issue_identity_check_token = sinon.stub().returns(BluebirdPromise.resolve({})); userDataStore.consume_identity_check_token = sinon.stub().returns(BluebirdPromise.resolve({})); userDataStore.set_totp_secret = sinon.stub().returns(BluebirdPromise.resolve({})); - mocks.userDataStore = userDataStore; + mocks.userDataStore = userDataStore as any; res = ExpressMock.ResponseMock(); }); diff --git a/test/unit/server/routes/secondfactor/totp/sign/post.test.ts b/test/unit/server/routes/secondfactor/totp/sign/post.test.ts index 1a1bd998a..21b2393eb 100644 --- a/test/unit/server/routes/secondfactor/totp/sign/post.test.ts +++ b/test/unit/server/routes/secondfactor/totp/sign/post.test.ts @@ -53,9 +53,9 @@ describe("test totp route", function () { userDataStore.get_totp_secret.returns(BluebirdPromise.resolve(doc)); mocks.logger = winston; - mocks.totpValidator = totpValidator; - mocks.config = config; - mocks.userDataStore = userDataStore; + mocks.totpValidator = totpValidator as any; + mocks.config = config as any; + mocks.userDataStore = userDataStore as any; }); diff --git a/test/unit/server/routes/secondfactor/u2f/identity/RegistrationHandler.test.ts b/test/unit/server/routes/secondfactor/u2f/identity/RegistrationHandler.test.ts index 2126cae5c..117bfbafc 100644 --- a/test/unit/server/routes/secondfactor/u2f/identity/RegistrationHandler.test.ts +++ b/test/unit/server/routes/secondfactor/u2f/identity/RegistrationHandler.test.ts @@ -41,7 +41,7 @@ describe("test register handler", function () { userDataStore.get_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({})); userDataStore.issue_identity_check_token = sinon.stub().returns(BluebirdPromise.resolve({})); userDataStore.consume_identity_check_token = sinon.stub().returns(BluebirdPromise.resolve({})); - mocks.userDataStore = userDataStore; + mocks.userDataStore = userDataStore as any; res = ExpressMock.ResponseMock(); res.send = sinon.spy(); diff --git a/test/unit/server/routes/secondfactor/u2f/register/post.test.ts b/test/unit/server/routes/secondfactor/u2f/register/post.test.ts index 21a80353c..1b928fc75 100644 --- a/test/unit/server/routes/secondfactor/u2f/register/post.test.ts +++ b/test/unit/server/routes/secondfactor/u2f/register/post.test.ts @@ -4,6 +4,7 @@ import BluebirdPromise = require("bluebird"); import assert = require("assert"); import U2FRegisterPost = require("../../../../../../../src/server/lib/routes/secondfactor/u2f/register/post"); import AuthenticationSession = require("../../../../../../../src/server/lib/AuthenticationSession"); +import { ServerVariables } from "../../../../../../../src/server/lib/ServerVariables"; import winston = require("winston"); import ExpressMock = require("../../../../mocks/express"); @@ -16,7 +17,7 @@ describe("test u2f routes: register", function () { let req: ExpressMock.RequestMock; let res: ExpressMock.ResponseMock; let userDataStore: UserDataStoreMock.UserDataStore; - let mocks: ServerVariablesMock.ServerVariablesMock; + let mocks: ServerVariables; let authSession: AuthenticationSession.AuthenticationSession; beforeEach(function () { @@ -46,7 +47,7 @@ describe("test u2f routes: register", function () { userDataStore = UserDataStoreMock.UserDataStore(); userDataStore.set_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({})); userDataStore.get_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({})); - mocks.userDataStore = userDataStore; + mocks.userDataStore = userDataStore as any; res = ExpressMock.ResponseMock(); res.send = sinon.spy(); diff --git a/test/unit/server/routes/secondfactor/u2f/register_request/get.test.ts b/test/unit/server/routes/secondfactor/u2f/register_request/get.test.ts index 55c2d74a7..55473eb52 100644 --- a/test/unit/server/routes/secondfactor/u2f/register_request/get.test.ts +++ b/test/unit/server/routes/secondfactor/u2f/register_request/get.test.ts @@ -4,6 +4,7 @@ import BluebirdPromise = require("bluebird"); import assert = require("assert"); import U2FRegisterRequestGet = require("../../../../../../../src/server/lib/routes/secondfactor/u2f/register_request/get"); import AuthenticationSession = require("../../../../../../../src/server/lib/AuthenticationSession"); +import { ServerVariables } from "../../../../../../../src/server/lib/ServerVariables"; import winston = require("winston"); import ExpressMock = require("../../../../mocks/express"); @@ -16,7 +17,7 @@ describe("test u2f routes: register_request", function () { let req: ExpressMock.RequestMock; let res: ExpressMock.ResponseMock; let userDataStore: UserDataStoreMock.UserDataStore; - let mocks: ServerVariablesMock.ServerVariablesMock; + let mocks: ServerVariables; let authSession: AuthenticationSession.AuthenticationSession; beforeEach(function () { @@ -46,7 +47,7 @@ describe("test u2f routes: register_request", function () { userDataStore = UserDataStoreMock.UserDataStore(); userDataStore.set_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({})); userDataStore.get_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({})); - mocks.userDataStore = userDataStore; + mocks.userDataStore = userDataStore as any; res = ExpressMock.ResponseMock(); res.send = sinon.spy(); diff --git a/test/unit/server/routes/secondfactor/u2f/sign/post.test.ts b/test/unit/server/routes/secondfactor/u2f/sign/post.test.ts index f9d243867..b56b9c8ad 100644 --- a/test/unit/server/routes/secondfactor/u2f/sign/post.test.ts +++ b/test/unit/server/routes/secondfactor/u2f/sign/post.test.ts @@ -4,6 +4,7 @@ import BluebirdPromise = require("bluebird"); import assert = require("assert"); import U2FSignPost = require("../../../../../../../src/server/lib/routes/secondfactor/u2f/sign/post"); import AuthenticationSession = require("../../../../../../../src/server/lib/AuthenticationSession"); +import { ServerVariables } from "../../../../../../../src/server/lib/ServerVariables"; import winston = require("winston"); import ExpressMock = require("../../../../mocks/express"); @@ -16,7 +17,7 @@ describe("test u2f routes: sign", function () { let req: ExpressMock.RequestMock; let res: ExpressMock.ResponseMock; let userDataStore: UserDataStoreMock.UserDataStore; - let mocks: ServerVariablesMock.ServerVariablesMock; + let mocks: ServerVariables; let authSession: AuthenticationSession.AuthenticationSession; beforeEach(function () { @@ -46,7 +47,7 @@ describe("test u2f routes: sign", function () { userDataStore = UserDataStoreMock.UserDataStore(); userDataStore.set_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({})); userDataStore.get_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({})); - mocks.userDataStore = userDataStore; + mocks.userDataStore = userDataStore as any; res = ExpressMock.ResponseMock(); res.send = sinon.spy(); diff --git a/test/unit/server/routes/secondfactor/u2f/sign_request/get.test.ts b/test/unit/server/routes/secondfactor/u2f/sign_request/get.test.ts index 365f44672..bc5261a14 100644 --- a/test/unit/server/routes/secondfactor/u2f/sign_request/get.test.ts +++ b/test/unit/server/routes/secondfactor/u2f/sign_request/get.test.ts @@ -4,6 +4,7 @@ import BluebirdPromise = require("bluebird"); import assert = require("assert"); import U2FSignRequestGet = require("../../../../../../../src/server/lib/routes/secondfactor/u2f/sign_request/get"); import AuthenticationSession = require("../../../../../../../src/server/lib/AuthenticationSession"); +import { ServerVariables } from "../../../../../../../src/server/lib/ServerVariables"; import winston = require("winston"); import ExpressMock = require("../../../../mocks/express"); @@ -18,7 +19,7 @@ describe("test u2f routes: sign_request", function () { let req: ExpressMock.RequestMock; let res: ExpressMock.ResponseMock; let userDataStore: UserDataStoreMock.UserDataStore; - let mocks: ServerVariablesMock.ServerVariablesMock; + let mocks: ServerVariables; let authSession: AuthenticationSession.AuthenticationSession; beforeEach(function () { @@ -50,7 +51,7 @@ describe("test u2f routes: sign_request", function () { userDataStore = UserDataStoreMock.UserDataStore(); userDataStore.set_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({})); userDataStore.get_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({})); - mocks.userDataStore = userDataStore; + mocks.userDataStore = userDataStore as any; res = ExpressMock.ResponseMock(); res.send = sinon.spy(); diff --git a/test/unit/server/routes/verify/get.test.ts b/test/unit/server/routes/verify/get.test.ts index 0981a6c3d..8e2700d5e 100644 --- a/test/unit/server/routes/verify/get.test.ts +++ b/test/unit/server/routes/verify/get.test.ts @@ -27,9 +27,9 @@ describe("test authentication token verification", function () { req.headers = {}; req.headers.host = "secret.example.com"; const mocks = ServerVariablesMock.mock(req.app); - mocks.config = {}; + mocks.config = {} as any; mocks.logger = winston; - mocks.accessController = accessController; + mocks.accessController = accessController as any; }); it("should be already authenticated", function (done) { diff --git a/test/unit/server/server/PrivatePages.ts b/test/unit/server/server/PrivatePages.ts index 5e3d88c0a..32cc6316f 100644 --- a/test/unit/server/server/PrivatePages.ts +++ b/test/unit/server/server/PrivatePages.ts @@ -1,7 +1,5 @@ import Server from "../../../../src/server/lib/Server"; -import LdapClient = require("../../../../src/server/lib/LdapClient"); - import BluebirdPromise = require("bluebird"); import speakeasy = require("speakeasy"); import request = require("request"); @@ -108,7 +106,8 @@ describe("Private pages of the server must not be accessible without session", f session: ExpressSession, winston: Winston, speakeasy: speakeasy, - ConnectRedis: Sinon.spy() + ConnectRedis: Sinon.spy(), + dovehash: Sinon.spy() as any }; server = new Server(); diff --git a/test/unit/server/server/PublicPages.ts b/test/unit/server/server/PublicPages.ts index b6b5ccae5..92365270d 100644 --- a/test/unit/server/server/PublicPages.ts +++ b/test/unit/server/server/PublicPages.ts @@ -1,7 +1,5 @@ import Server from "../../../../src/server/lib/Server"; -import LdapClient = require("../../../../src/server/lib/LdapClient"); - import BluebirdPromise = require("bluebird"); import speakeasy = require("speakeasy"); import Request = require("request"); @@ -108,7 +106,8 @@ describe("Public pages of the server must be accessible without session", functi session: ExpressSession, winston: Winston, speakeasy: speakeasy, - ConnectRedis: Sinon.spy() + ConnectRedis: Sinon.spy(), + dovehash: Sinon.spy() as any }; server = new Server(); diff --git a/test/unit/server/server/Server.test.ts b/test/unit/server/server/Server.test.ts index b3a7969ca..7e06b815d 100644 --- a/test/unit/server/server/Server.test.ts +++ b/test/unit/server/server/Server.test.ts @@ -1,7 +1,6 @@ import Server from "../../../../src/server/lib/Server"; -import LdapClient = require("../../../../src/server/lib/LdapClient"); import { LdapjsClientMock } from "./../mocks/ldapjs"; import BluebirdPromise = require("bluebird"); @@ -92,7 +91,7 @@ describe("test the server", function () { "password").yields(); ldapClient.bind.withArgs("cn=test_nok,ou=users,dc=example,dc=com", - "password").yields("error"); + "password").yields("Bad credentials"); ldapClient.unbind.yields(); ldapClient.modify.yields(); @@ -106,7 +105,10 @@ describe("test the server", function () { session: ExpressSession, winston: Winston, speakeasy: speakeasy, - ConnectRedis: Sinon.spy() + ConnectRedis: Sinon.spy(), + dovehash: { + encode: Sinon.stub().returns("abc") + } }; server = new Server();