mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Move ldap client to typescript
This commit is contained in:
parent
bada70cf64
commit
b54c181d27
169
src/lib/LdapClient.ts
Normal file
169
src/lib/LdapClient.ts
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
154
src/lib/ldap.js
154
src/lib/ldap.js
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -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');
|
||||||
|
|
|
@ -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
4
src/types/dovehash.d.ts
vendored
Normal 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
11
src/types/ldapjs-async.d.ts
vendored
Normal 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>;
|
||||||
|
}
|
||||||
|
}
|
243
test/unitary/LdapClient.test.ts
Normal file
243
test/unitary/LdapClient.test.ts
Normal 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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
|
|
||||||
import sinon = require("sinon");
|
|
||||||
|
|
||||||
export = function () {
|
|
||||||
return {
|
|
||||||
bind: sinon.stub(),
|
|
||||||
get_emails: sinon.stub(),
|
|
||||||
get_groups: sinon.stub()
|
|
||||||
};
|
|
||||||
};
|
|
20
test/unitary/mocks/LdapClient.ts
Normal file
20
test/unitary/mocks/LdapClient.ts
Normal 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()
|
||||||
|
};
|
||||||
|
}
|
18
test/unitary/mocks/UserDataStore.ts
Normal file
18
test/unitary/mocks/UserDataStore.ts
Normal 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()
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,11 +1,30 @@
|
||||||
|
|
||||||
import sinon = require("sinon");
|
import sinon = require("sinon");
|
||||||
|
|
||||||
export = {
|
export interface RequestMock {
|
||||||
Response: function () {
|
app?: any;
|
||||||
return {
|
body?: any;
|
||||||
send: sinon.stub(),
|
session?: any;
|
||||||
status: sinon.stub()
|
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 {
|
||||||
|
send: sinon.stub(),
|
||||||
|
status: sinon.stub(),
|
||||||
|
json: sinon.stub()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
28
test/unitary/mocks/ldapjs.ts
Normal file
28
test/unitary/mocks/ldapjs.ts
Normal 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()
|
||||||
|
};
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
|
151
test/unitary/routes/reset_password.test.ts
Normal file
151
test/unitary/routes/reset_password.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -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');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user