Move ldap client to typescript

This commit is contained in:
Clement Michaud 2017-05-21 01:15:34 +02:00
parent bada70cf64
commit b54c181d27
18 changed files with 681 additions and 579 deletions

169
src/lib/LdapClient.ts Normal file
View File

@ -0,0 +1,169 @@
import util = require("util");
import BluebirdPromise = require("bluebird");
import exceptions = require("./Exceptions");
import Dovehash = require("dovehash");
import ldapjs = require("ldapjs");
import { EventEmitter } from "events";
import { LdapConfiguration } from "./Configuration";
import { Ldapjs } from "../types/Dependencies";
import { ILogger } from "./ILogger";
interface SearchEntry {
object: any;
}
export class LdapClient {
options: LdapConfiguration;
ldapjs: Ldapjs;
logger: ILogger;
client: ldapjs.ClientAsync;
constructor(options: LdapConfiguration, ldapjs: Ldapjs, logger: ILogger) {
this.options = options;
this.ldapjs = ldapjs;
this.logger = logger;
this.connect();
}
connect(): void {
const ldap_client = this.ldapjs.createClient({
url: this.options.url,
reconnect: true
});
ldap_client.on("error", function (err: Error) {
console.error("LDAP Error:", err.message);
});
this.client = BluebirdPromise.promisifyAll(ldap_client) as ldapjs.ClientAsync;
}
private build_user_dn(username: string): string {
let user_name_attr = this.options.user_name_attribute;
// if not provided, default to cn
if (!user_name_attr) user_name_attr = "cn";
const additional_user_dn = this.options.additional_user_dn;
const base_dn = this.options.base_dn;
let user_dn = util.format("%s=%s", user_name_attr, username);
if (additional_user_dn) user_dn += util.format(",%s", additional_user_dn);
user_dn += util.format(",%s", base_dn);
return user_dn;
}
bind(username: string, password: string): BluebirdPromise<void> {
const user_dn = this.build_user_dn(username);
this.logger.debug("LDAP: Bind user %s", user_dn);
return this.client.bindAsync(user_dn, password)
.error(function (err) {
throw new exceptions.LdapBindError(err.message);
});
}
private search_in_ldap(base: string, query: ldapjs.SearchOptions): BluebirdPromise<any> {
this.logger.debug("LDAP: Search for %s in %s", JSON.stringify(query), base);
return new BluebirdPromise((resolve, reject) => {
this.client.searchAsync(base, query)
.then(function (res: EventEmitter) {
const doc: SearchEntry[] = [];
res.on("searchEntry", function (entry: SearchEntry) {
doc.push(entry.object);
});
res.on("error", function (err: Error) {
reject(err);
});
res.on("end", function () {
resolve(doc);
});
})
.catch(function (err) {
reject(err);
});
});
}
get_groups(username: string): BluebirdPromise<string[]> {
const user_dn = this.build_user_dn(username);
let group_name_attr = this.options.group_name_attribute;
if (!group_name_attr) group_name_attr = "cn";
const additional_group_dn = this.options.additional_group_dn;
const base_dn = this.options.base_dn;
let group_dn = base_dn;
if (additional_group_dn)
group_dn = util.format("%s,", additional_group_dn) + group_dn;
const query = {
scope: "sub",
attributes: [group_name_attr],
filter: "member=" + user_dn
};
const that = this;
this.logger.debug("LDAP: get groups of user %s", username);
return this.search_in_ldap(group_dn, query)
.then(function (docs) {
const groups = [];
for (let i = 0; i < docs.length; ++i) {
groups.push(docs[i].cn);
}
that.logger.debug("LDAP: got groups %s", groups);
return Promise.resolve(groups);
});
}
get_emails(username: string): BluebirdPromise<string[]> {
const that = this;
const user_dn = this.build_user_dn(username);
const query = {
scope: "base",
sizeLimit: 1,
attributes: ["mail"]
};
this.logger.debug("LDAP: get emails of user %s", username);
return this.search_in_ldap(user_dn, query)
.then(function (docs) {
const emails = [];
for (let i = 0; i < docs.length; ++i) {
if (typeof docs[i].mail === "string")
emails.push(docs[i].mail);
else {
emails.concat(docs[i].mail);
}
}
that.logger.debug("LDAP: got emails %s", emails);
return Promise.resolve(emails);
});
}
update_password(username: string, new_password: string): BluebirdPromise<void> {
const user_dn = this.build_user_dn(username);
const encoded_password = Dovehash.encode("SSHA", new_password);
const change = {
operation: "replace",
modification: {
userPassword: encoded_password
}
};
const that = this;
this.logger.debug("LDAP: update password of user %s", username);
this.logger.debug("LDAP: bind admin");
return this.client.bindAsync(this.options.user, this.options.password)
.then(function () {
that.logger.debug("LDAP: modify password");
return that.client.modifyAsync(user_dn, change);
});
}
}

View File

@ -8,6 +8,7 @@ import { NotifierFactory } from "./notifiers/NotifierFactory";
import TOTPValidator from "./TOTPValidator"; import TOTPValidator from "./TOTPValidator";
import TOTPGenerator from "./TOTPGenerator"; import TOTPGenerator from "./TOTPGenerator";
import RestApi from "./RestApi"; import RestApi from "./RestApi";
import { LdapClient } from "./LdapClient";
import * as Express from "express"; import * as Express from "express";
import * as BodyParser from "body-parser"; import * as BodyParser from "body-parser";
@ -16,8 +17,6 @@ import * as http from "http";
import AccessController from "./access_control/AccessController"; import AccessController from "./access_control/AccessController";
const Ldap = require("./ldap");
export default class Server { export default class Server {
private httpServer: http.Server; private httpServer: http.Server;
@ -58,7 +57,7 @@ export default class Server {
const data_store = new UserDataStore(datastore_options); const data_store = new UserDataStore(datastore_options);
const regulator = new AuthenticationRegulator(data_store, five_minutes); const regulator = new AuthenticationRegulator(data_store, five_minutes);
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer); const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
const ldap = new Ldap(deps, config.ldap); const ldap = new LdapClient(config.ldap, deps.ldapjs, deps.winston);
const accessController = new AccessController(config.access_control, deps.winston); const accessController = new AccessController(config.access_control, deps.winston);
const totpValidator = new TOTPValidator(deps.speakeasy); const totpValidator = new TOTPValidator(deps.speakeasy);
const totpGenerator = new TOTPGenerator(deps.speakeasy); const totpGenerator = new TOTPGenerator(deps.speakeasy);

View File

@ -1,154 +0,0 @@
module.exports = Ldap;
var util = require('util');
var Promise = require('bluebird');
var exceptions = require('./Exceptions');
var Dovehash = require('dovehash');
function Ldap(deps, ldap_config) {
this.ldap_config = ldap_config;
this.ldapjs = deps.ldapjs;
this.logger = deps.winston;
this.connect();
}
Ldap.prototype.connect = function() {
var ldap_client = this.ldapjs.createClient({
url: this.ldap_config.url,
reconnect: true
});
ldap_client.on('error', function(err) {
console.error('LDAP Error:', err.message)
});
this.ldap_client = Promise.promisifyAll(ldap_client);
}
Ldap.prototype._build_user_dn = function(username) {
var user_name_attr = this.ldap_config.user_name_attribute;
// if not provided, default to cn
if(!user_name_attr) user_name_attr = 'cn';
var additional_user_dn = this.ldap_config.additional_user_dn;
var base_dn = this.ldap_config.base_dn;
var user_dn = util.format("%s=%s", user_name_attr, username);
if(additional_user_dn) user_dn += util.format(",%s", additional_user_dn);
user_dn += util.format(',%s', base_dn);
return user_dn;
}
Ldap.prototype.bind = function(username, password) {
var user_dn = this._build_user_dn(username);
this.logger.debug('LDAP: Bind user %s', user_dn);
return this.ldap_client.bindAsync(user_dn, password)
.error(function(err) {
throw new exceptions.LdapBindError(err.message);
});
}
Ldap.prototype._search_in_ldap = function(base, query) {
var that = this;
this.logger.debug('LDAP: Search for %s in %s', JSON.stringify(query), base);
return new Promise(function(resolve, reject) {
that.ldap_client.searchAsync(base, query)
.then(function(res) {
var doc = [];
res.on('searchEntry', function(entry) {
doc.push(entry.object);
});
res.on('error', function(err) {
reject(new exceptions.LdapSearchError(err));
});
res.on('end', function(result) {
resolve(doc);
});
})
.catch(function(err) {
reject(err);
});
});
}
Ldap.prototype.get_groups = function(username) {
var user_dn = this._build_user_dn(username);
var group_name_attr = this.ldap_config.group_name_attribute;
if(!group_name_attr) group_name_attr = 'cn';
var additional_group_dn = this.ldap_config.additional_group_dn;
var base_dn = this.ldap_config.base_dn;
var group_dn = base_dn;
if(additional_group_dn)
group_dn = util.format('%s,', additional_group_dn) + group_dn;
var query = {};
query.scope = 'sub';
query.attributes = [group_name_attr];
query.filter = 'member=' + user_dn ;
var that = this;
this.logger.debug('LDAP: get groups of user %s', username);
return this._search_in_ldap(group_dn, query)
.then(function(docs) {
var groups = [];
for(var i = 0; i<docs.length; ++i) {
groups.push(docs[i].cn);
}
that.logger.debug('LDAP: got groups %s', groups);
return Promise.resolve(groups);
});
}
Ldap.prototype.get_emails = function(username) {
var that = this;
var user_dn = this._build_user_dn(username);
var query = {};
query.scope = 'base';
query.sizeLimit = 1;
query.attributes = ['mail'];
this.logger.debug('LDAP: get emails of user %s', username);
return this._search_in_ldap(user_dn, query)
.then(function(docs) {
var emails = [];
for(var i = 0; i<docs.length; ++i) {
if(typeof docs[i].mail === 'string')
emails.push(docs[i].mail);
else {
emails.concat(docs[i].mail);
}
}
that.logger.debug('LDAP: got emails %s', emails);
return Promise.resolve(emails);
});
}
Ldap.prototype.update_password = function(username, new_password) {
var user_dn = this._build_user_dn(username);
var encoded_password = Dovehash.encode('SSHA', new_password);
var change = new this.ldapjs.Change({
operation: 'replace',
modification: {
userPassword: encoded_password
}
});
var that = this;
this.logger.debug('LDAP: update password of user %s', username);
this.logger.debug('LDAP: bind admin');
return this.ldap_client.bindAsync(this.ldap_config.user, this.ldap_config.password)
.then(function() {
that.logger.debug('LDAP: modify password');
return that.ldap_client.modifyAsync(user_dn, change);
});
}

View File

@ -19,12 +19,10 @@ module.exports = {
function pre_check(req) { function pre_check(req) {
var userid = objectPath.get(req, 'body.userid'); var userid = objectPath.get(req, 'body.userid');
if(!userid) { if(!userid) {
var err = new exceptions.AccessDeniedError(); return Promise.reject(new exceptions.AccessDeniedError("No user id provided"));
return Promise.reject(err);
} }
var ldap = req.app.get('ldap'); var ldap = req.app.get('ldap');
return ldap.get_emails(userid) return ldap.get_emails(userid)
.then(function(emails) { .then(function(emails) {
if(!emails && emails.length <= 0) throw new Error('No email found'); if(!emails && emails.length <= 0) throw new Error('No email found');

View File

@ -10,11 +10,12 @@ export type Speakeasy = typeof speakeasy;
export type Winston = typeof winston; export type Winston = typeof winston;
export type Session = typeof session; export type Session = typeof session;
export type Nedb = typeof nedb; export type Nedb = typeof nedb;
export type Ldapjs = typeof ldapjs;
export interface GlobalDependencies { export interface GlobalDependencies {
u2f: object; u2f: object;
nodemailer: Nodemailer; nodemailer: Nodemailer;
ldapjs: object; ldapjs: Ldapjs;
session: Session; session: Session;
winston: Winston; winston: Winston;
speakeasy: Speakeasy; speakeasy: Speakeasy;

4
src/types/dovehash.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module "dovehash" {
function encode(algo: string, text: string): string;
}

11
src/types/ldapjs-async.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
import ldapjs = require("ldapjs");
import * as Promise from "bluebird";
import { EventEmitter } from "events";
declare module "ldapjs" {
export interface ClientAsync {
bindAsync(username: string, password: string): Promise<void>;
searchAsync(base: string, query: ldapjs.SearchOptions): Promise<EventEmitter>;
modifyAsync(userdn: string, change: ldapjs.Change): Promise<void>;
}
}

View File

@ -0,0 +1,243 @@
import LdapClient = require("../../src/lib/LdapClient");
import { LdapConfiguration } from "../../src/lib/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 ldap_client: LdapjsClientMock;
let ldapjs: LdapjsMock;
let ldap_config: LdapConfiguration;
beforeEach(function () {
ldap_client = {
bind: sinon.stub(),
search: sinon.stub(),
modify: sinon.stub(),
on: sinon.stub()
} as any;
ldapjs = LdapjsMock();
ldapjs.createClient.returns(ldap_client);
ldap_config = {
url: "http://localhost:324",
user: "admin",
password: "password",
base_dn: "dc=example,dc=com",
additional_user_dn: "ou=users"
};
ldap = new LdapClient.LdapClient(ldap_config, ldapjs, winston);
return ldap.connect();
});
describe("test binding", test_binding);
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_binding() {
function test_bind() {
const username = "username";
const password = "password";
return ldap.bind(username, password);
}
it("should bind the user if good credentials provided", function () {
ldap_client.bind.yields();
return test_bind();
});
it("should bind the user with correct DN", function () {
ldap_config.user_name_attribute = "uid";
const username = "user";
const password = "password";
ldap_client.bind.withArgs("uid=user,ou=users,dc=example,dc=com").yields();
return ldap.bind(username, password);
});
it("should default to cn user search filter if no filter provided", function () {
const username = "user";
const password = "password";
ldap_client.bind.withArgs("cn=user,ou=users,dc=example,dc=com").yields();
return ldap.bind(username, password);
});
it("should not bind the user if wrong credentials provided", function () {
ldap_client.bind.yields("wrong credentials");
const promise = test_bind();
return promise.catch(function () {
return Promise.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 () {
ldap_client.search.yields(undefined, res_emitter);
return ldap.get_emails("user")
.then(function (emails) {
assert.deepEqual(emails, [expected_doc.object.mail]);
return Promise.resolve();
});
});
it("should retrieve email for user with uid name attribute", function () {
ldap_config.user_name_attribute = "uid";
ldap_client.search.withArgs("uid=username,ou=users,dc=example,dc=com").yields(undefined, res_emitter);
return ldap.get_emails("username")
.then(function (emails) {
assert.deepEqual(emails, ["user@example.com"]);
return Promise.resolve();
});
});
it("should fail on error with search method", function () {
const expected_doc = {
mail: ["user@example.com"]
};
ldap_client.search.yields("Error while searching mails");
return ldap.get_emails("user")
.catch(function () {
return Promise.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 () {
ldap_client.search.yields(undefined, res_emitter);
return ldap.get_groups("user")
.then(function (groups) {
assert.deepEqual(groups, ["group1", "group2"]);
return Promise.resolve();
});
});
it("should reduce the scope to additional_group_dn", function (done) {
ldap_config.additional_group_dn = "ou=groups";
ldap_client.search.yields(undefined, res_emitter);
ldap.get_groups("user")
.then(function() {
assert.equal(ldap_client.search.getCall(0).args[0], "ou=groups,dc=example,dc=com");
done();
});
});
it("should use default group_name_attr if not provided", function (done) {
ldap_client.search.yields(undefined, res_emitter);
ldap.get_groups("user")
.then(function() {
assert.equal(ldap_client.search.getCall(0).args[0], "dc=example,dc=com");
assert.equal(ldap_client.search.getCall(0).args[1].filter, "member=cn=user,ou=users,dc=example,dc=com");
assert.deepEqual(ldap_client.search.getCall(0).args[1].attributes, ["cn"]);
done();
});
});
it("should fail on error with search method", function () {
ldap_client.search.yields("error");
return ldap.get_groups("user")
.catch(function () {
return Promise.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";
ldap_client.bind.yields(undefined);
ldap_client.modify.yields(undefined);
return ldap.update_password("user", "new-password")
.then(function () {
assert.deepEqual(ldap_client.modify.getCall(0).args[0], userdn);
assert.deepEqual(ldap_client.modify.getCall(0).args[1].operation, change.operation);
const userPassword = ldap_client.modify.getCall(0).args[1].modification.userPassword;
assert(/{SSHA}/.test(userPassword));
return Promise.resolve();
});
});
it("should fail when ldap throws an error", function () {
ldap_client.bind.yields(undefined);
ldap_client.modify.yields("Error");
return ldap.update_password("user", "new-password")
.catch(function () {
return Promise.resolve();
});
});
it("should update password of user using particular user name attribute", function () {
ldap_config.user_name_attribute = "uid";
ldap_client.bind.yields(undefined);
ldap_client.modify.withArgs("uid=username,ou=users,dc=example,dc=com").yields();
return ldap.update_password("username", "newpass");
});
}
});

View File

@ -1,6 +1,6 @@
import Server from "../../src/lib/Server"; import Server from "../../src/lib/Server";
import Ldap = require("../../src/lib/ldap"); import LdapClient = require("../../src/lib/LdapClient");
import Promise = require("bluebird"); import Promise = require("bluebird");
import speakeasy = require("speakeasy"); import speakeasy = require("speakeasy");

View File

@ -1,10 +0,0 @@
import sinon = require("sinon");
export = function () {
return {
bind: sinon.stub(),
get_emails: sinon.stub(),
get_groups: sinon.stub()
};
};

View File

@ -0,0 +1,20 @@
import sinon = require("sinon");
export interface LdapClientMock {
bind: sinon.SinonStub;
get_emails: sinon.SinonStub;
get_groups: sinon.SinonStub;
search_in_ldap: sinon.SinonStub;
update_password: sinon.SinonStub;
}
export function LdapClientMock(): LdapClientMock {
return {
bind: sinon.stub(),
get_emails: sinon.stub(),
get_groups: sinon.stub(),
search_in_ldap: sinon.stub(),
update_password: sinon.stub()
};
}

View File

@ -0,0 +1,18 @@
import sinon = require("sinon");
export interface UserDataStore {
set_u2f_meta: sinon.SinonStub;
get_u2f_meta: sinon.SinonStub;
issue_identity_check_token: sinon.SinonStub;
consume_identity_check_token: sinon.SinonStub;
}
export function UserDataStore(): UserDataStore {
return {
set_u2f_meta: sinon.stub(),
get_u2f_meta: sinon.stub(),
issue_identity_check_token: sinon.stub(),
consume_identity_check_token: sinon.stub()
};
}

View File

@ -1,11 +1,30 @@
import sinon = require("sinon"); import sinon = require("sinon");
export = { export interface RequestMock {
Response: function () { app?: any;
body?: any;
session?: any;
headers?: any;
}
export interface ResponseMock {
send: sinon.SinonStub | sinon.SinonSpy;
status: sinon.SinonStub;
json: sinon.SinonStub;
}
export function RequestMock(): RequestMock {
return {
app: {
get: sinon.stub()
}
};
}
export function ResponseMock(): ResponseMock {
return { return {
send: sinon.stub(), send: sinon.stub(),
status: sinon.stub() status: sinon.stub(),
json: sinon.stub()
}; };
} }
};

View File

@ -0,0 +1,28 @@
import sinon = require("sinon");
export interface LdapjsMock {
createClient: sinon.SinonStub;
}
export interface LdapjsClientMock {
bind: sinon.SinonStub;
search: sinon.SinonStub;
modify: sinon.SinonStub;
on: sinon.SinonStub;
}
export function LdapjsMock(): LdapjsMock {
return {
createClient: sinon.stub()
};
}
export function LdapjsClientMock(): LdapjsClientMock {
return {
bind: sinon.stub(),
search: sinon.stub(),
modify: sinon.stub(),
on: sinon.stub()
};
}

View File

@ -8,7 +8,7 @@ import FirstFactor = require("../../../src/lib/routes/FirstFactor");
import exceptions = require("../../../src/lib/Exceptions"); import exceptions = require("../../../src/lib/Exceptions");
import AuthenticationRegulatorMock = require("../mocks/AuthenticationRegulator"); import AuthenticationRegulatorMock = require("../mocks/AuthenticationRegulator");
import AccessControllerMock = require("../mocks/AccessController"); import AccessControllerMock = require("../mocks/AccessController");
import LdapMock = require("../mocks/Ldap"); import { LdapClientMock } from "../mocks/LdapClient";
import ExpressMock = require("../mocks/express"); import ExpressMock = require("../mocks/express");
describe("test the first factor validation route", function() { describe("test the first factor validation route", function() {
@ -17,7 +17,7 @@ describe("test the first factor validation route", function() {
let emails: string[]; let emails: string[];
let groups: string[]; let groups: string[];
let configuration; let configuration;
let ldapMock: any; let ldapMock: LdapClientMock;
let regulator: any; let regulator: any;
let accessController: any; let accessController: any;
@ -32,7 +32,7 @@ describe("test the first factor validation route", function() {
emails = [ "test_ok@example.com" ]; emails = [ "test_ok@example.com" ];
groups = [ "group1", "group2" ]; groups = [ "group1", "group2" ];
ldapMock = LdapMock(); ldapMock = LdapClientMock();
accessController = AccessControllerMock(); accessController = AccessControllerMock();
accessController.isDomainAllowedForUser.returns(true); accessController.isDomainAllowedForUser.returns(true);
@ -63,7 +63,7 @@ describe("test the first factor validation route", function() {
} }
} }
}; };
res = ExpressMock.Response(); res = ExpressMock.ResponseMock();
}); });
it("should return status code 204 when LDAP binding succeeds", function() { it("should return status code 204 when LDAP binding succeeds", function() {

View File

@ -0,0 +1,151 @@
import reset_password = require("../../../src/lib/routes/reset_password");
import LdapClient = require("../../../src/lib/LdapClient");
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";
describe("test reset password", function () {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let user_data_store: UserDataStore;
let ldap_client: LdapClientMock;
let configuration: any;
beforeEach(function () {
req = {
body: {
userid: "user"
},
app: {
get: sinon.stub()
},
session: {
auth_session: {
userid: "user",
email: "user@example.com",
first_factor: true,
second_factor: false
}
},
headers: {
host: "localhost"
}
};
const options = {
inMemoryOnly: true
};
user_data_store = UserDataStore();
user_data_store.set_u2f_meta.returns(Promise.resolve({}));
user_data_store.get_u2f_meta.returns(Promise.resolve({}));
user_data_store.issue_identity_check_token.returns(Promise.resolve({}));
user_data_store.consume_identity_check_token.returns(Promise.resolve({}));
req.app.get.withArgs("user data store").returns(user_data_store);
configuration = {
ldap: {
base_dn: "dc=example,dc=com",
user_name_attribute: "cn"
}
};
req.app.get.withArgs("logger").returns(winston);
req.app.get.withArgs("config").returns(configuration);
ldap_client = LdapClientMock();
req.app.get.withArgs("ldap").returns(ldap_client);
res = ExpressMock.ResponseMock();
});
describe("test reset password identity pre check", test_reset_password_check);
describe("test reset password post", test_reset_password_post);
function test_reset_password_check() {
it("should fail when no userid is provided", function (done) {
req.body.userid = undefined;
reset_password.icheck_interface.pre_check_callback(req)
.catch(function (err: Error) {
done();
});
});
it("should fail if ldap fail", function (done) {
ldap_client.get_emails.returns(BluebirdPromise.reject("Internal error"));
reset_password.icheck_interface.pre_check_callback(req)
.catch(function (err: Error) {
done();
});
});
it("should perform a search in ldap to find email address", function (done) {
configuration.ldap.user_name_attribute = "uid";
ldap_client.get_emails.returns(BluebirdPromise.resolve([]));
reset_password.icheck_interface.pre_check_callback(req)
.then(function () {
assert.equal("user", ldap_client.get_emails.getCall(0).args[0]);
done();
});
});
it("should returns identity when ldap replies", function (done) {
ldap_client.get_emails.returns(BluebirdPromise.resolve(["test@example.com"]));
reset_password.icheck_interface.pre_check_callback(req)
.then(function () {
done();
});
});
}
function test_reset_password_post() {
it("should update the password and reset auth_session for reauthentication", function (done) {
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.userid = "user";
req.session.auth_session.identity_check.challenge = "reset-password";
req.body = {};
req.body.password = "new-password";
ldap_client.update_password.returns(BluebirdPromise.resolve());
ldap_client.bind.returns(BluebirdPromise.resolve());
res.send = sinon.spy(function () {
assert.equal(res.status.getCall(0).args[0], 204);
assert.equal(req.session.auth_session, undefined);
done();
});
reset_password.post(req, res);
});
it("should fail if identity_challenge does not exist", function (done) {
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.challenge = undefined;
res.send = sinon.spy(function () {
assert.equal(res.status.getCall(0).args[0], 403);
done();
});
reset_password.post(req, res);
});
it("should fail when ldap fails", function (done) {
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.challenge = "reset-password";
req.body = {};
req.body.password = "new-password";
ldap_client.bind.yields(undefined);
ldap_client.update_password.returns(BluebirdPromise.reject("Internal error with LDAP"));
res.send = sinon.spy(function () {
assert.equal(res.status.getCall(0).args[0], 500);
done();
});
reset_password.post(req, res);
});
}
});

View File

@ -1,162 +0,0 @@
var reset_password = require('../../../src/lib/routes/reset_password');
var Ldap = require('../../../src/lib/ldap');
var sinon = require('sinon');
var winston = require('winston');
var assert = require('assert');
describe('test reset password', function() {
var req, res;
var user_data_store;
var ldap_client;
var ldap;
beforeEach(function() {
req = {}
req.body = {};
req.body.userid = 'user';
req.app = {};
req.app.get = sinon.stub();
req.app.get.withArgs('logger').returns(winston);
req.session = {};
req.session.auth_session = {};
req.session.auth_session.userid = 'user';
req.session.auth_session.email = 'user@example.com';
req.session.auth_session.first_factor = true;
req.session.auth_session.second_factor = false;
req.headers = {};
req.headers.host = 'localhost';
var options = {};
options.inMemoryOnly = true;
user_data_store = {};
user_data_store.set_u2f_meta = sinon.stub().returns(Promise.resolve({}));
user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve({}));
user_data_store.issue_identity_check_token = sinon.stub().returns(Promise.resolve({}));
user_data_store.consume_identity_check_token = sinon.stub().returns(Promise.resolve({}));
req.app.get.withArgs('user data store').returns(user_data_store);
config = {};
config.ldap = {};
config.ldap.base_dn = 'dc=example,dc=com';
config.ldap.user_name_attribute = 'cn';
req.app.get.withArgs('config').returns(config);
ldap_client = {};
ldap_client.bind = sinon.stub();
ldap_client.search = sinon.stub();
ldap_client.modify = sinon.stub();
ldap_client.on = sinon.spy();
ldapjs = {};
ldapjs.Change = sinon.spy();
ldapjs.createClient = sinon.spy(function() {
return ldap_client;
});
deps = {
ldapjs: ldapjs,
winston: winston
};
req.app.get.withArgs('ldap').returns(new Ldap(deps, config.ldap));
res = {};
res.send = sinon.spy();
res.json = sinon.spy();
res.status = sinon.spy();
});
describe('test reset password identity pre check', test_reset_password_check);
describe('test reset password post', test_reset_password_post);
function test_reset_password_check() {
it('should fail when no userid is provided', function(done) {
req.body.userid = undefined;
reset_password.icheck_interface.pre_check_callback(req)
.catch(function(err) {
done();
});
});
it('should fail if ldap fail', function(done) {
ldap_client.search.yields('Internal error');
reset_password.icheck_interface.pre_check_callback(req)
.catch(function(err) {
done();
});
});
it('should perform a search in ldap to find email address', function(done) {
config.ldap.user_name_attribute = 'uid';
ldap_client.search = sinon.spy(function(dn) {
if(dn == 'uid=user,dc=example,dc=com') done();
});
reset_password.icheck_interface.pre_check_callback(req);
});
it('should returns identity when ldap replies', function(done) {
var doc = {};
doc.object = {};
doc.object.email = ['test@example.com'];
doc.object.userid = 'user';
var res = {};
res.on = sinon.stub();
res.on.withArgs('searchEntry').yields(doc);
res.on.withArgs('end').yields();
ldap_client.search.yields(undefined, res);
reset_password.icheck_interface.pre_check_callback(req)
.then(function() {
done();
});
});
}
function test_reset_password_post() {
it('should update the password and reset auth_session for reauthentication', function(done) {
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.userid = 'user';
req.session.auth_session.identity_check.challenge = 'reset-password';
req.body = {};
req.body.password = 'new-password';
ldap_client.modify.yields(undefined);
ldap_client.bind.yields(undefined);
res.send = sinon.spy(function() {
assert.equal(ldap_client.modify.getCall(0).args[0], 'cn=user,dc=example,dc=com');
assert.equal(res.status.getCall(0).args[0], 204);
assert.equal(req.session.auth_session, undefined);
done();
});
reset_password.post(req, res);
});
it('should fail if identity_challenge does not exist', function(done) {
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.challenge = undefined;
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 403);
done();
});
reset_password.post(req, res);
});
it('should fail when ldap fails', function(done) {
req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.challenge = 'reset-password';
req.body = {};
req.body.password = 'new-password';
ldap_client.bind.yields(undefined);
ldap_client.modify.yields('Internal error with LDAP');
res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 500);
done();
});
reset_password.post(req, res);
});
}
});

View File

@ -1,233 +0,0 @@
var Ldap = require('../../src/lib/ldap');
var sinon = require('sinon');
var Promise = require('bluebird');
var assert = require('assert');
var ldapjs = require('ldapjs');
var winston = require('winston');
describe('test ldap validation', function() {
var ldap_client;
var ldap, ldapjs;
var ldap_config;
beforeEach(function() {
ldap_client = {
bind: sinon.stub(),
search: sinon.stub(),
modify: sinon.stub(),
on: sinon.stub()
};
ldapjs = {
Change: sinon.spy(),
createClient: sinon.spy(function() {
return ldap_client;
  })
}
ldap_config = {
url: 'http://localhost:324',
user: 'admin',
password: 'password',
base_dn: 'dc=example,dc=com',
additional_user_dn: 'ou=users'
};
var deps = {};
deps.ldapjs = ldapjs;
deps.winston = winston;
ldap = new Ldap(deps, ldap_config);
return ldap.connect();
});
describe('test binding', test_binding);
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_binding() {
function test_bind() {
var username = "username";
var password = "password";
return ldap.bind(username, password);
}
it('should bind the user if good credentials provided', function() {
ldap_client.bind.yields();
return test_bind();
});
it('should bind the user with correct DN', function() {
ldap_config.user_name_attribute = 'uid';
var username = 'user';
var password = 'password';
ldap_client.bind.withArgs('uid=user,ou=users,dc=example,dc=com').yields();
return ldap.bind(username, password);
});
it('should default to cn user search filter if no filter provided', function() {
var username = 'user';
var password = 'password';
ldap_client.bind.withArgs('cn=user,ou=users,dc=example,dc=com').yields();
return ldap.bind(username, password);
});
it('should not bind the user if wrong credentials provided', function() {
ldap_client.bind.yields('wrong credentials');
var promise = test_bind();
return promise.catch(function() {
return Promise.resolve();
});
});
}
function test_get_emails() {
var res_emitter;
var expected_doc;
beforeEach(function() {
expected_doc = {};
expected_doc.object = {};
expected_doc.object.mail = 'user@example.com';
res_emitter = {};
res_emitter.on = sinon.spy(function(event, fn) {
if(event != 'error') fn(expected_doc)
});
});
it('should retrieve the email of an existing user', function() {
ldap_client.search.yields(undefined, res_emitter);
return ldap.get_emails('user')
.then(function(emails) {
assert.deepEqual(emails, [expected_doc.object.mail]);
return Promise.resolve();
})
});
it('should retrieve email for user with uid name attribute', function() {
ldap_config.user_name_attribute = 'uid';
ldap_client.search.withArgs('uid=username,ou=users,dc=example,dc=com').yields(undefined, res_emitter);
return ldap.get_emails('username')
.then(function(emails) {
assert.deepEqual(emails, ['user@example.com']);
return Promise.resolve();
});
});
it('should fail on error with search method', function() {
var expected_doc = {};
expected_doc.mail = [];
expected_doc.mail.push('user@example.com');
ldap_client.search.yields('Error while searching mails');
return ldap.get_emails('user')
.catch(function() {
return Promise.resolve();
})
});
}
function test_get_groups() {
var res_emitter;
var expected_doc1, expected_doc2;
beforeEach(function() {
expected_doc1 = {};
expected_doc1.object = {};
expected_doc1.object.cn = 'group1';
expected_doc2 = {};
expected_doc2.object = {};
expected_doc2.object.cn = 'group2';
res_emitter = {};
res_emitter.on = sinon.spy(function(event, fn) {
if(event != 'error') fn(expected_doc1);
if(event != 'error') fn(expected_doc2);
});
});
it('should retrieve the groups of an existing user', function() {
ldap_client.search.yields(undefined, res_emitter);
return ldap.get_groups('user')
.then(function(groups) {
assert.deepEqual(groups, ['group1', 'group2']);
return Promise.resolve();
});
});
it('should reduce the scope to additional_group_dn', function(done) {
ldap_config.additional_group_dn = 'ou=groups';
ldap_client.search = sinon.spy(function(base_dn) {
assert.equal(base_dn, 'ou=groups,dc=example,dc=com');
done();
});
ldap.get_groups('user');
});
it('should use default group_name_attr if not provided', function(done) {
ldap_client.search = sinon.spy(function(base_dn, query) {
assert.equal(base_dn, 'dc=example,dc=com');
assert.equal(query.filter, 'member=cn=user,ou=users,dc=example,dc=com');
assert.deepEqual(query.attributes, ['cn']);
done();
});
ldap.get_groups('user');
});
it('should fail on error with search method', function() {
ldap_client.search.yields('error');
return ldap.get_groups('user')
.catch(function() {
return Promise.resolve();
})
});
}
function test_update_password() {
it('should update the password successfully', function() {
var change = {};
change.operation = 'replace';
change.modification = {};
change.modification.userPassword = 'new-password';
var userdn = 'cn=user,ou=users,dc=example,dc=com';
ldap_client.bind.yields(undefined);
ldap_client.modify.yields(undefined);
return ldap.update_password('user', 'new-password')
.then(function() {
assert.deepEqual(ldap_client.modify.getCall(0).args[0], userdn);
assert.deepEqual(ldapjs.Change.getCall(0).args[0].operation, change.operation);
var userPassword = ldapjs.Change.getCall(0).args[0].modification.userPassword;
assert(/{SSHA}/.test(userPassword));
return Promise.resolve();
})
});
it('should fail when ldap throws an error', function() {
ldap_client.bind.yields(undefined);
ldap_client.modify.yields('Error');
return ldap.update_password('user', 'new-password')
.catch(function() {
return Promise.resolve();
})
});
it('should update password of user using particular user name attribute', function() {
ldap_config.user_name_attribute = 'uid';
ldap_client.bind.yields(undefined);
ldap_client.modify.withArgs('uid=username,ou=users,dc=example,dc=com').yields();
return ldap.update_password('username', 'newpass');
});
}
});