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