Log what is retrieved from headers to help debugging.

This commit is contained in:
Clement Michaud 2019-03-18 09:27:41 +01:00
parent 76fa325f08
commit 7c3d6cc376
30 changed files with 374 additions and 420 deletions

View File

@ -19,7 +19,7 @@ async function runTests(suite, withEnv) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let mochaArgs = ['--exit', '--colors', '--require', 'ts-node/register', 'test/suites/' + suite + '/test.ts'] let mochaArgs = ['--exit', '--colors', '--require', 'ts-node/register', 'test/suites/' + suite + '/test.ts']
if (program.forbidOnly) { if (program.forbidOnly) {
mochaArgs = ['--forbid-only', '--forbid-pending'] + mochaArgs; mochaArgs = ['--forbid-only', '--forbid-pending'].concat(mochaArgs);
} }
const mochaCommand = './node_modules/.bin/mocha ' + mochaArgs.join(' '); const mochaCommand = './node_modules/.bin/mocha ' + mochaArgs.join(' ');

View File

@ -4,11 +4,17 @@ var program = require('commander');
var spawn = require('child_process').spawn; var spawn = require('child_process').spawn;
program program
.option('--forbid-only', 'Forbid only and pending filters.')
.parse(process.argv); .parse(process.argv);
async function runTests(pattern) { async function runTests(patterns) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
mocha = spawn('./node_modules/.bin/mocha', ['--colors', '--require', 'ts-node/register', pattern], { let mochaArgs = ['--require', 'ts-node/register', '--require', './spec-helper.js', '--colors'];
if (program.forbidOnly) {
mochaArgs = ['--forbid-only', '--forbid-pending'].concat(mochaArgs);
}
const mocha = spawn('./node_modules/.bin/mocha', mochaArgs.concat(patterns), {
env: { env: {
...process.env, ...process.env,
'TS_NODE_PROJECT': './server/tsconfig.json' 'TS_NODE_PROJECT': './server/tsconfig.json'
@ -27,8 +33,10 @@ async function runTests(pattern) {
} }
async function test() { async function test() {
await runTests('server/src/**/*.spec.ts'); await runTests([
await runTests('shared/**/*.spec.ts'); 'server/src/**/*.spec.ts',
'shared/**/*.spec.ts'
]);
} }
test() test()

View File

@ -1,25 +1,18 @@
import express = require("express"); import * as Express from "express";
import { IRequestLogger } from "./logging/IRequestLogger"; import { IRequestLogger } from "./logging/IRequestLogger";
function replyWithError(req: express.Request, res: express.Response, function replyWithError(req: Express.Request, res: Express.Response,
code: number, logger: IRequestLogger, body?: Object): (err: Error) => void { code: number, logger: IRequestLogger, body?: Object): (err: Error) => void {
return function (err: Error): void { return function (err: Error): void {
if (req.originalUrl.startsWith("/api/") || code == 200) { logger.error(req, "Reply with error %d: %s", code, err.message);
logger.error(req, "Reply with error %d: %s", code, err.message); logger.debug(req, "%s", err.stack);
logger.debug(req, "%s", err.stack); res.status(code);
res.status(code); res.send(body);
res.send(body);
}
else {
logger.error(req, "Redirect to error %d: %s", code, err.message);
logger.debug(req, "%s", err.stack);
res.redirect("/error/" + code);
}
}; };
} }
export function redirectTo(redirectUrl: string, req: express.Request, export function redirectTo(redirectUrl: string, req: Express.Request,
res: express.Response, logger: IRequestLogger) { res: Express.Response, logger: IRequestLogger) {
return function(err: Error) { return function(err: Error) {
logger.error(req, "Error: %s", err.message); logger.error(req, "Error: %s", err.message);
logger.debug(req, "Redirecting to %s", redirectUrl); logger.debug(req, "Redirecting to %s", redirectUrl);
@ -27,22 +20,22 @@ export function redirectTo(redirectUrl: string, req: express.Request,
}; };
} }
export function replyWithError400(req: express.Request, export function replyWithError400(req: Express.Request,
res: express.Response, logger: IRequestLogger) { res: Express.Response, logger: IRequestLogger) {
return replyWithError(req, res, 400, logger); return replyWithError(req, res, 400, logger);
} }
export function replyWithError401(req: express.Request, export function replyWithError401(req: Express.Request,
res: express.Response, logger: IRequestLogger) { res: Express.Response, logger: IRequestLogger) {
return replyWithError(req, res, 401, logger); return replyWithError(req, res, 401, logger);
} }
export function replyWithError403(req: express.Request, export function replyWithError403(req: Express.Request,
res: express.Response, logger: IRequestLogger) { res: Express.Response, logger: IRequestLogger) {
return replyWithError(req, res, 403, logger); return replyWithError(req, res, 403, logger);
} }
export function replyWithError200(req: express.Request, export function replyWithError200(req: Express.Request,
res: express.Response, logger: IRequestLogger, message: string) { res: Express.Response, logger: IRequestLogger, message: string) {
return replyWithError(req, res, 200, logger, { error: message }); return replyWithError(req, res, 200, logger, { error: message });
} }

View File

@ -1,10 +1,9 @@
import sinon = require("sinon"); import * as Sinon from "sinon";
import * as IdentityCheckMiddleware from "./IdentityCheckMiddleware"; import * as IdentityCheckMiddleware from "./IdentityCheckMiddleware";
import exceptions = require("./Exceptions");
import { ServerVariables } from "./ServerVariables"; import { ServerVariables } from "./ServerVariables";
import Assert = require("assert"); import * as Assert from "assert";
import express = require("express"); import * as Express from "express";
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import ExpressMock = require("./stubs/express.spec"); import ExpressMock = require("./stubs/express.spec");
import { IdentityValidableStub } from "./IdentityValidableStub.spec"; import { IdentityValidableStub } from "./IdentityValidableStub.spec";
@ -13,11 +12,11 @@ import { ServerVariablesMock, ServerVariablesMockBuilder }
import { OPERATION_FAILED } from "../../../shared/UserMessages"; import { OPERATION_FAILED } from "../../../shared/UserMessages";
describe("IdentityCheckMiddleware", function () { describe("IdentityCheckMiddleware", function () {
let req: ExpressMock.RequestMock; let req: Express.Request;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let app: express.Application; let app: Express.Application;
let app_get: sinon.SinonStub; let app_get: Sinon.SinonStub;
let app_post: sinon.SinonStub; let app_post: Sinon.SinonStub;
let identityValidable: IdentityValidableStub; let identityValidable: IdentityValidableStub;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let vars: ServerVariables; let vars: ServerVariables;
@ -29,14 +28,6 @@ describe("IdentityCheckMiddleware", function () {
req = ExpressMock.RequestMock(); req = ExpressMock.RequestMock();
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();
req.headers = {};
req.originalUrl = "/non-api/xxx";
req.session = {};
req.query = {};
req.app = {};
identityValidable = new IdentityValidableStub(); identityValidable = new IdentityValidableStub();
mocks.notifier.notifyStub.returns(BluebirdPromise.resolve()); mocks.notifier.notifyStub.returns(BluebirdPromise.resolve());
@ -45,9 +36,9 @@ describe("IdentityCheckMiddleware", function () {
mocks.userDataStore.consumeIdentityValidationTokenStub mocks.userDataStore.consumeIdentityValidationTokenStub
.returns(BluebirdPromise.resolve({ userId: "user" })); .returns(BluebirdPromise.resolve({ userId: "user" }));
app = express(); app = Express();
app_get = sinon.stub(app, "get"); app_get = Sinon.stub(app, "get");
app_post = sinon.stub(app, "post"); app_post = Sinon.stub(app, "post");
}); });
afterEach(function () { afterEach(function () {
@ -56,22 +47,8 @@ describe("IdentityCheckMiddleware", function () {
}); });
describe("test start GET", function () { describe("test start GET", function () {
it("should redirect to error 401 if pre validation initialization \
throws a first factor error", function () {
identityValidable.preValidationInitStub.returns(BluebirdPromise.reject(
new exceptions.FirstFactorValidationError(
"Error during prevalidation")));
const callback = IdentityCheckMiddleware.post_start_validation(
identityValidable, vars);
return callback(req as any, res as any, undefined)
.then(() => {
Assert(res.redirect.calledWith("/error/401"));
});
});
// In that case we answer with 200 to avoid user enumeration. // In that case we answer with 200 to avoid user enumeration.
it("should send 200 if email is missing in provided identity", function () { it("should send 200 if email is missing in provided identity", async function () {
const identity = { userid: "abc" }; const identity = { userid: "abc" };
identityValidable.preValidationInitStub identityValidable.preValidationInitStub
@ -79,31 +56,26 @@ throws a first factor error", function () {
const callback = IdentityCheckMiddleware const callback = IdentityCheckMiddleware
.post_start_validation(identityValidable, vars); .post_start_validation(identityValidable, vars);
return callback(req as any, res as any, undefined) await callback(req as any, res as any, undefined)
.then(function () { Assert(identityValidable.preValidationResponseStub.called);
Assert(identityValidable.preValidationResponseStub.called);
});
}); });
// In that case we answer with 200 to avoid user enumeration. // In that case we answer with 200 to avoid user enumeration.
it("should send 200 if userid is missing in provided identity", it("should send 200 if userid is missing in provided identity", async function () {
function () { const identity = { email: "abc@example.com" };
const identity = { email: "abc@example.com" };
identityValidable.preValidationInitStub identityValidable.preValidationInitStub
.returns(BluebirdPromise.resolve(identity)); .returns(BluebirdPromise.resolve(identity));
const callback = IdentityCheckMiddleware const callback = IdentityCheckMiddleware
.post_start_validation(identityValidable, vars); .post_start_validation(identityValidable, vars);
return callback(req as any, res as any, undefined) await callback(req as any, res as any, undefined)
.then(function () { Assert(identityValidable.preValidationResponseStub.called);
Assert(identityValidable.preValidationResponseStub.called); });
});
});
it("should issue a token, send an email and return 204", async function () { it("should issue a token, send an email and return 204", async function () {
const identity = { userid: "user", email: "abc@example.com" }; const identity = { userid: "user", email: "abc@example.com" };
req.get = sinon.stub().withArgs("Host").returns("localhost"); req.get = Sinon.stub().withArgs("Host").returns("localhost");
identityValidable.preValidationInitStub identityValidable.preValidationInitStub
.returns(BluebirdPromise.resolve(identity)); .returns(BluebirdPromise.resolve(identity));
@ -124,42 +96,36 @@ throws a first factor error", function () {
describe("test finish GET", function () { describe("test finish GET", function () {
it("should return an error if no identity_token is provided", () => { it("should return an error if no identity_token is provided", async () => {
const callback = IdentityCheckMiddleware const callback = IdentityCheckMiddleware
.post_finish_validation(identityValidable, vars); .post_finish_validation(identityValidable, vars);
return callback(req as any, res as any, undefined) await callback(req as any, res as any, undefined)
.then(function () { Assert(res.status.calledWith(200));
Assert(res.status.calledWith(200)); Assert(res.send.calledWith({'error': OPERATION_FAILED}));
Assert(res.send.calledWith({'error': OPERATION_FAILED}));
});
}); });
it("should call postValidation if identity_token is provided and still \ it("should call postValidation if identity_token is provided and still valid", async function () {
valid", function () { req.query.identity_token = "token";
req.query.identity_token = "token"; const callback = IdentityCheckMiddleware
const callback = IdentityCheckMiddleware .post_finish_validation(identityValidable, vars);
.post_finish_validation(identityValidable, vars); await callback(req as any, res as any, undefined);
return callback(req as any, res as any, undefined); });
});
it("should return an error if identity_token is provided but invalid", it("should return an error if identity_token is provided but invalid", async function () {
function () { req.query.identity_token = "token";
req.query.identity_token = "token";
identityValidable.postValidationInitStub identityValidable.postValidationInitStub
.returns(BluebirdPromise.resolve()); .returns(BluebirdPromise.resolve());
mocks.userDataStore.consumeIdentityValidationTokenStub.reset(); mocks.userDataStore.consumeIdentityValidationTokenStub.reset();
mocks.userDataStore.consumeIdentityValidationTokenStub mocks.userDataStore.consumeIdentityValidationTokenStub
.returns(BluebirdPromise.reject(new Error("Invalid token"))); .returns(() => Promise.reject(new Error("Invalid token")));
const callback = IdentityCheckMiddleware const callback = IdentityCheckMiddleware
.post_finish_validation(identityValidable, vars); .post_finish_validation(identityValidable, vars);
return callback(req as any, res as any, undefined) await callback(req as any, res as any, undefined)
.then(() => { Assert(res.status.calledWith(200));
Assert(res.status.calledWith(200)); Assert(res.send.calledWith({'error': OPERATION_FAILED}));
Assert(res.send.calledWith({'error': OPERATION_FAILED})); });
});
});
}); });
}); });

View File

@ -1,4 +1,6 @@
import BluebirdPromise = require("bluebird"); import * as Bluebird from "bluebird";
import * as Express from "express";
import * as http from "http";
import { Configuration } from "./configuration/schema/Configuration"; import { Configuration } from "./configuration/schema/Configuration";
import { GlobalDependencies } from "../../types/Dependencies"; import { GlobalDependencies } from "../../types/Dependencies";
@ -9,8 +11,7 @@ import { ServerVariables } from "./ServerVariables";
import { ServerVariablesInitializer } from "./ServerVariablesInitializer"; import { ServerVariablesInitializer } from "./ServerVariablesInitializer";
import { Configurator } from "./web_server/Configurator"; import { Configurator } from "./web_server/Configurator";
import * as Express from "express"; import { GET_VARIABLE_KEY } from "../../../shared/constants";
import * as http from "http";
function clone(obj: any) { function clone(obj: any) {
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(obj));
@ -44,11 +45,11 @@ export default class Server {
JSON.stringify(displayableConfiguration, undefined, 2)); JSON.stringify(displayableConfiguration, undefined, 2));
} }
private setup(config: Configuration, app: Express.Application, deps: GlobalDependencies): BluebirdPromise<void> { private setup(config: Configuration, app: Express.Application, deps: GlobalDependencies): Bluebird<void> {
const that = this;
return ServerVariablesInitializer.initialize( return ServerVariablesInitializer.initialize(
config, this.globalLogger, this.requestLogger, deps) config, this.globalLogger, this.requestLogger, deps)
.then(function (vars: ServerVariables) { .then(function (vars: ServerVariables) {
app.set(GET_VARIABLE_KEY, vars);
return Configurator.configure(config, app, vars, deps); return Configurator.configure(config, app, vars, deps);
}); });
} }
@ -56,7 +57,7 @@ export default class Server {
private startServer(app: Express.Application, port: number) { private startServer(app: Express.Application, port: number) {
const that = this; const that = this;
that.globalLogger.info("Starting Authelia..."); that.globalLogger.info("Starting Authelia...");
return new BluebirdPromise<void>((resolve, reject) => { return new Bluebird<void>((resolve, reject) => {
this.httpServer = app.listen(port, function (err: string) { this.httpServer = app.listen(port, function (err: string) {
that.globalLogger.info("Listening on port %d...", port); that.globalLogger.info("Listening on port %d...", port);
resolve(); resolve();
@ -65,7 +66,7 @@ export default class Server {
} }
start(configuration: Configuration, deps: GlobalDependencies) start(configuration: Configuration, deps: GlobalDependencies)
: BluebirdPromise<void> { : Bluebird<void> {
const that = this; const that = this;
const app = Express(); const app = Express();

View File

@ -1,16 +1,17 @@
import * as Express from 'express';
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import Assert = require("assert"); import Assert = require("assert");
import FirstFactorPost = require("./post"); import FirstFactorPost = require("./post");
import exceptions = require("../../Exceptions"); import exceptions = require("../../Exceptions");
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler"; import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../types/AuthenticationSession"; import { AuthenticationSession } from "../../../../types/AuthenticationSession";
import ExpressMock = require("../../stubs/express.spec"); import * as ExpressMock from "../../stubs/express.spec";
import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../ServerVariablesMockBuilder.spec"; import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../ServerVariablesMockBuilder.spec";
import { ServerVariables } from "../../ServerVariables"; import { ServerVariables } from "../../ServerVariables";
import AuthenticationError from "../../authentication/AuthenticationError"; import AuthenticationError from "../../authentication/AuthenticationError";
describe("routes/firstfactor/post", function () { describe("routes/firstfactor/post", function () {
let req: ExpressMock.RequestMock; let req: Express.Request;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let emails: string[]; let emails: string[];
let groups: string[]; let groups: string[];
@ -29,23 +30,11 @@ describe("routes/firstfactor/post", function () {
mocks.regulator.regulateStub.returns(BluebirdPromise.resolve()); mocks.regulator.regulateStub.returns(BluebirdPromise.resolve());
mocks.regulator.markStub.returns(BluebirdPromise.resolve()); mocks.regulator.markStub.returns(BluebirdPromise.resolve());
req = { req = ExpressMock.RequestMock();
originalUrl: "/api/firstfactor", req.body = {
body: { username: "username",
username: "username", password: "password"
password: "password" }
},
query: {
redirect: "http://redirect.url"
},
session: {
cookie: {}
},
headers: {
host: "home.example.com"
}
};
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();
authSession = AuthenticationSessionHandler.get(req as any, vars.logger); authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
}); });

View File

@ -1,4 +1,3 @@
import * as ObjectPath from "object-path";
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import express = require("express"); import express = require("express");
import ErrorReplies = require("../../ErrorReplies"); import ErrorReplies = require("../../ErrorReplies");
@ -16,6 +15,7 @@ import { Subject } from "../../../lib/authorization/Subject";
import AuthenticationError from "../../../lib/authentication/AuthenticationError"; import AuthenticationError from "../../../lib/authentication/AuthenticationError";
import IsRedirectionSafe from "../../../lib/utils/IsRedirectionSafe"; import IsRedirectionSafe from "../../../lib/utils/IsRedirectionSafe";
import * as URLParse from "url-parse"; import * as URLParse from "url-parse";
import GetHeader from "../../utils/GetHeader";
export default function (vars: ServerVariables) { export default function (vars: ServerVariables) {
return function (req: express.Request, res: express.Response) return function (req: express.Request, res: express.Response)
@ -63,7 +63,7 @@ export default function (vars: ServerVariables) {
vars.regulator.mark(username, true); vars.regulator.mark(username, true);
}) })
.then(function() { .then(function() {
const targetUrl = ObjectPath.get<Express.Request, string>(req, "headers.x-target-url", undefined); const targetUrl = GetHeader(req, "x-target-url");
if (!targetUrl) { if (!targetUrl) {
res.status(204); res.status(204);
@ -85,7 +85,7 @@ export default function (vars: ServerVariables) {
const authorizationLevel = vars.authorizer.authorization(resObject, subject); const authorizationLevel = vars.authorizer.authorization(resObject, subject);
if (authorizationLevel <= AuthorizationLevel.ONE_FACTOR) { if (authorizationLevel <= AuthorizationLevel.ONE_FACTOR) {
if (IsRedirectionSafe(vars, authSession, new URLParse(targetUrl))) { if (IsRedirectionSafe(vars, new URLParse(targetUrl))) {
res.json({redirect: targetUrl}); res.json({redirect: targetUrl});
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
} else { } else {

View File

@ -1,9 +1,7 @@
import * as Express from "express";
import PasswordResetFormPost = require("./post"); import PasswordResetFormPost = require("./post");
import { AuthenticationSessionHandler } from "../../../AuthenticationSessionHandler"; import { AuthenticationSessionHandler } from "../../../AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../../types/AuthenticationSession"; import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
import { UserDataStore } from "../../../storage/UserDataStore";
import Sinon = require("sinon");
import Assert = require("assert"); import Assert = require("assert");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import ExpressMock = require("../../../stubs/express.spec"); import ExpressMock = require("../../../stubs/express.spec");
@ -12,32 +10,19 @@ import { ServerVariables } from "../../../ServerVariables";
import { Level } from "../../../authentication/Level"; import { Level } from "../../../authentication/Level";
describe("routes/password-reset/form/post", function () { describe("routes/password-reset/form/post", function () {
let req: ExpressMock.RequestMock; let req: Express.Request;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let vars: ServerVariables; let vars: ServerVariables;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let authSession: AuthenticationSession; let authSession: AuthenticationSession;
beforeEach(function () { beforeEach(function () {
req = { req = ExpressMock.RequestMock();
originalUrl: "/api/password-reset",
body: {
userid: "user"
},
session: {},
headers: {
host: "localhost"
}
};
const s = ServerVariablesMockBuilder.build(); const s = ServerVariablesMockBuilder.build();
mocks = s.mocks; mocks = s.mocks;
vars = s.variables; vars = s.variables;
const options = {
inMemoryOnly: true
};
mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({})); mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({})); mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({})); mocks.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
@ -65,7 +50,6 @@ describe("routes/password-reset/form/post", function () {
describe("test reset password post", () => { describe("test reset password post", () => {
it("should update the password and reset auth_session for reauthentication", function () { it("should update the password and reset auth_session for reauthentication", function () {
req.body = {};
req.body.password = "new-password"; req.body.password = "new-password";
mocks.usersDatabase.updatePasswordStub.returns(BluebirdPromise.resolve()); mocks.usersDatabase.updatePasswordStub.returns(BluebirdPromise.resolve());
@ -99,7 +83,6 @@ describe("routes/password-reset/form/post", function () {
}); });
it("should fail when ldap fails", function () { it("should fail when ldap fails", function () {
req.body = {};
req.body.password = "new-password"; req.body.password = "new-password";
mocks.usersDatabase.updatePasswordStub mocks.usersDatabase.updatePasswordStub

View File

@ -1,4 +1,4 @@
import * as Express from "express";
import PasswordResetHandler import PasswordResetHandler
from "./PasswordResetHandler"; from "./PasswordResetHandler";
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
@ -8,28 +8,20 @@ import { ServerVariablesMock, ServerVariablesMockBuilder }
import { ServerVariables } from "../../../ServerVariables"; import { ServerVariables } from "../../../ServerVariables";
describe("routes/password-reset/identity/PasswordResetHandler", function () { describe("routes/password-reset/identity/PasswordResetHandler", function () {
let req: ExpressMock.RequestMock; let req: Express.Request;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let vars: ServerVariables; let vars: ServerVariables;
beforeEach(function () { beforeEach(function () {
req = { req = ExpressMock.RequestMock();
originalUrl: "/non-api/xxx", req.body.username = "user";
body: { req.session.auth = {
username: "user" userid: "user",
}, email: "user@example.com",
session: { first_factor: true,
auth: { second_factor: false
userid: "user",
email: "user@example.com",
first_factor: true,
second_factor: false
}
},
headers: {
host: "localhost"
}
}; };
req.headers.host = "localhost";
const s = ServerVariablesMockBuilder.build(); const s = ServerVariablesMockBuilder.build();
mocks = s.mocks; mocks = s.mocks;

View File

@ -1,41 +1,44 @@
import * as Express from "express";
import Redirect from "./redirect"; import Redirect from "./redirect";
import ExpressMock = require("../../stubs/express.spec"); import ExpressMock = require("../../stubs/express.spec");
import { ServerVariablesMockBuilder, ServerVariablesMock } import { ServerVariablesMockBuilder }
from "../../ServerVariablesMockBuilder.spec"; from "../../ServerVariablesMockBuilder.spec";
import { ServerVariables } from "../../ServerVariables"; import { ServerVariables } from "../../ServerVariables";
import Assert = require("assert"); import Assert = require("assert");
import { HEADER_X_TARGET_URL } from "../../../../../shared/constants";
describe("routes/secondfactor/redirect", function() { describe("routes/secondfactor/redirect", function() {
let req: ExpressMock.RequestMock; let req: Express.Request;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock;
let vars: ServerVariables; let vars: ServerVariables;
beforeEach(function () { beforeEach(function () {
const s = ServerVariablesMockBuilder.build(); const s = ServerVariablesMockBuilder.build();
mocks = s.mocks;
vars = s.variables; vars = s.variables;
req = ExpressMock.RequestMock(); req = ExpressMock.RequestMock();
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();
vars.config.session.domain = 'example.com';
}); });
it("should redirect to default_redirection_url", function() { describe('redirect to default url if no target provided', () => {
vars.config.default_redirection_url = "http://default_redirection_url"; it("should redirect to default url", async () => {
Redirect(vars)(req as any, res as any) vars.config.default_redirection_url = "https://home.example.com";
.then(function() { await Redirect(vars)(req, res as any)
Assert(res.json.calledWith({ Assert(res.json.calledWith({redirect: "https://home.example.com"}));
redirect: "http://default_redirection_url"
}));
}); });
}); });
it("should redirect to /", function() { it("should redirect to safe url https://test.example.com/", async () => {
Redirect(vars)(req as any, res as any) req.headers[HEADER_X_TARGET_URL] = "https://test.example.com/";
.then(function() { await Redirect(vars)(req, res as any);
Assert(res.json.calledWith({ Assert(res.json.calledWith({redirect: "https://test.example.com/"}));
redirect: "/"
}));
});
}); });
it('should not redirect to unsafe target url', async () => {
vars.config.default_redirection_url = "https://home.example.com";
req.headers[HEADER_X_TARGET_URL] = "http://test.example.com/";
await Redirect(vars)(req, res as any);
Assert(res.status.calledWith(204));
})
}); });

View File

@ -1,39 +1,27 @@
import express = require("express"); import * as Express from "express";
import * as URLParse from "url-parse"; import * as URLParse from "url-parse";
import * as ObjectPath from "object-path";
import { ServerVariables } from "../../ServerVariables"; import { ServerVariables } from "../../ServerVariables";
import BluebirdPromise = require("bluebird");
import ErrorReplies = require("../../ErrorReplies");
import UserMessages = require("../../../../../shared/UserMessages");
import IsRedirectionSafe from "../../../lib/utils/IsRedirectionSafe"; import IsRedirectionSafe from "../../../lib/utils/IsRedirectionSafe";
import { AuthenticationSessionHandler } from "../../../lib/AuthenticationSessionHandler"; import GetHeader from "../../utils/GetHeader";
import { HEADER_X_TARGET_URL } from "../../../../../shared/constants";
export default function (vars: ServerVariables) { export default function (vars: ServerVariables) {
return function (req: express.Request, res: express.Response) return async function (req: Express.Request, res: Express.Response): Promise<void> {
: BluebirdPromise<void> { let redirectUrl = GetHeader(req, HEADER_X_TARGET_URL);
return new BluebirdPromise<void>(function (resolve, reject) { if (!redirectUrl && vars.config.default_redirection_url) {
let redirectUrl: string = ObjectPath.get<Express.Request, string>( redirectUrl = vars.config.default_redirection_url;
req, "headers.x-target-url", undefined); }
if (!redirectUrl && vars.config.default_redirection_url) { if ((redirectUrl && !IsRedirectionSafe(vars, new URLParse(redirectUrl)))
redirectUrl = vars.config.default_redirection_url; || !redirectUrl) {
} res.status(204);
res.send();
return;
}
const authSession = AuthenticationSessionHandler.get(req, vars.logger); res.json({redirect: redirectUrl});
if ((redirectUrl && !IsRedirectionSafe(vars, authSession, new URLParse(redirectUrl)))
|| !redirectUrl) {
res.status(204);
res.send();
return resolve();
}
res.json({redirect: redirectUrl});
return resolve();
})
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
UserMessages.OPERATION_FAILED));
}; };
} }

View File

@ -1,7 +1,4 @@
import Sinon = require("sinon");
import RegistrationHandler from "./RegistrationHandler"; import RegistrationHandler from "./RegistrationHandler";
import { Identity } from "../../../../../../types/Identity";
import { UserDataStore } from "../../../../storage/UserDataStore";
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import ExpressMock = require("../../../../stubs/express.spec"); import ExpressMock = require("../../../../stubs/express.spec");
import { ServerVariablesMock, ServerVariablesMockBuilder } import { ServerVariablesMock, ServerVariablesMockBuilder }
@ -10,7 +7,7 @@ import { ServerVariables } from "../../../../ServerVariables";
import Assert = require("assert"); import Assert = require("assert");
describe("routes/secondfactor/totp/identity/RegistrationHandler", function () { describe("routes/secondfactor/totp/identity/RegistrationHandler", function () {
let req: ExpressMock.RequestMock; let req: Express.Request;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let vars: ServerVariables; let vars: ServerVariables;
@ -22,6 +19,7 @@ describe("routes/secondfactor/totp/identity/RegistrationHandler", function () {
req = ExpressMock.RequestMock(); req = ExpressMock.RequestMock();
req.session = { req.session = {
...req.session,
auth: { auth: {
userid: "user", userid: "user",
email: "user@example.com", email: "user@example.com",
@ -33,12 +31,6 @@ describe("routes/secondfactor/totp/identity/RegistrationHandler", function () {
} }
} }
}; };
req.headers = {};
req.headers.host = "localhost";
const options = {
inMemoryOnly: true
};
mocks.userDataStore.saveU2FRegistrationStub mocks.userDataStore.saveU2FRegistrationStub
.returns(BluebirdPromise.resolve({})); .returns(BluebirdPromise.resolve({}));

View File

@ -1,5 +1,5 @@
import express = require("express"); import * as Express from "express";
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import { Identity } from "../../../../../../types/Identity"; import { Identity } from "../../../../../../types/Identity";
@ -35,7 +35,7 @@ export default class RegistrationHandler implements IdentityValidable {
return Constants.CHALLENGE; return Constants.CHALLENGE;
} }
private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> { private retrieveIdentity(req: Express.Request): BluebirdPromise<Identity> {
const that = this; const that = this;
return new BluebirdPromise(function (resolve, reject) { return new BluebirdPromise(function (resolve, reject) {
const authSession = AuthenticationSessionHandler.get(req, that.logger); const authSession = AuthenticationSessionHandler.get(req, that.logger);
@ -54,7 +54,7 @@ export default class RegistrationHandler implements IdentityValidable {
}); });
} }
preValidationInit(req: express.Request): BluebirdPromise<Identity> { preValidationInit(req: Express.Request): BluebirdPromise<Identity> {
const that = this; const that = this;
return FirstFactorValidator.validate(req, this.logger) return FirstFactorValidator.validate(req, this.logger)
.then(function () { .then(function () {
@ -62,16 +62,16 @@ export default class RegistrationHandler implements IdentityValidable {
}); });
} }
preValidationResponse(req: express.Request, res: express.Response) { preValidationResponse(req: Express.Request, res: Express.Response) {
res.status(204); res.status(204);
res.send(); res.send();
} }
postValidationInit(req: express.Request) { postValidationInit(req: Express.Request) {
return FirstFactorValidator.validate(req, this.logger); return FirstFactorValidator.validate(req, this.logger);
} }
postValidationResponse(req: express.Request, res: express.Response) postValidationResponse(req: Express.Request, res: Express.Response)
: BluebirdPromise<void> { : BluebirdPromise<void> {
const that = this; const that = this;
let secret: TOTPSecret; let secret: TOTPSecret;

View File

@ -1,20 +1,17 @@
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import Sinon = require("sinon");
import Assert = require("assert"); import Assert = require("assert");
import Exceptions = require("../../../../Exceptions"); import * as Express from "express";
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler"; import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession"; import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
import SignPost = require("./post"); import SignPost = require("./post");
import { ServerVariables } from "../../../../ServerVariables"; import { ServerVariables } from "../../../../ServerVariables";
import ExpressMock = require("../../../../stubs/express.spec"); import ExpressMock = require("../../../../stubs/express.spec");
import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec";
import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec"; import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec";
import { Level } from "../../../../authentication/Level"; import { Level } from "../../../../authentication/Level";
describe("routes/secondfactor/totp/sign/post", function () { describe("routes/secondfactor/totp/sign/post", function () {
let req: ExpressMock.RequestMock; let req: Express.Request;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let authSession: AuthenticationSession; let authSession: AuthenticationSession;
let vars: ServerVariables; let vars: ServerVariables;
@ -24,17 +21,9 @@ describe("routes/secondfactor/totp/sign/post", function () {
const s = ServerVariablesMockBuilder.build(); const s = ServerVariablesMockBuilder.build();
vars = s.variables; vars = s.variables;
mocks = s.mocks; mocks = s.mocks;
const app_get = Sinon.stub(); req = ExpressMock.RequestMock();
req = { req.body = {
originalUrl: "/api/totp-register", token: "abc",
app: {},
body: {
token: "abc"
},
session: {},
query: {
redirect: "http://redirect"
}
}; };
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();

View File

@ -1,16 +1,13 @@
import * as Express from "express";
import Sinon = require("sinon"); import Sinon = require("sinon");
import Assert = require("assert");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import { Identity } from "../../../../../../types/Identity";
import RegistrationHandler from "./RegistrationHandler"; import RegistrationHandler from "./RegistrationHandler";
import ExpressMock = require("../../../../stubs/express.spec"); import ExpressMock = require("../../../../stubs/express.spec");
import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec";
import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec"; import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec";
import { ServerVariables } from "../../../../ServerVariables"; import { ServerVariables } from "../../../../ServerVariables";
describe("routes/secondfactor/u2f/identity/RegistrationHandler", function () { describe("routes/secondfactor/u2f/identity/RegistrationHandler", function () {
let req: ExpressMock.RequestMock; let req: Express.Request;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let vars: ServerVariables; let vars: ServerVariables;
@ -21,8 +18,8 @@ describe("routes/secondfactor/u2f/identity/RegistrationHandler", function () {
vars = s.variables; vars = s.variables;
req = ExpressMock.RequestMock(); req = ExpressMock.RequestMock();
req.app = {};
req.session = { req.session = {
...req.session,
auth: { auth: {
userid: "user", userid: "user",
email: "user@example.com", email: "user@example.com",
@ -33,10 +30,6 @@ describe("routes/secondfactor/u2f/identity/RegistrationHandler", function () {
req.headers = {}; req.headers = {};
req.headers.host = "localhost"; req.headers.host = "localhost";
const options = {
inMemoryOnly: true
};
mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({})); mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({})); mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({})); mocks.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));

View File

@ -1,4 +1,4 @@
import * as Express from "express";
import sinon = require("sinon"); import sinon = require("sinon");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import assert = require("assert"); import assert = require("assert");
@ -6,13 +6,12 @@ import U2FRegisterPost = require("./post");
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler"; import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession"; import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
import ExpressMock = require("../../../../stubs/express.spec"); import ExpressMock = require("../../../../stubs/express.spec");
import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec";
import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../ServerVariablesMockBuilder.spec"; import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../ServerVariablesMockBuilder.spec";
import { ServerVariables } from "../../../../ServerVariables"; import { ServerVariables } from "../../../../ServerVariables";
describe("routes/secondfactor/u2f/register/post", function () { describe("routes/secondfactor/u2f/register/post", function () {
let req: ExpressMock.RequestMock; let req: Express.Request;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let vars: ServerVariables; let vars: ServerVariables;
@ -21,8 +20,8 @@ describe("routes/secondfactor/u2f/register/post", function () {
beforeEach(function () { beforeEach(function () {
req = ExpressMock.RequestMock(); req = ExpressMock.RequestMock();
req.originalUrl = "/api/xxxx"; req.originalUrl = "/api/xxxx";
req.app = {};
req.session = { req.session = {
...req.session,
auth: { auth: {
userid: "user", userid: "user",
first_factor: true, first_factor: true,
@ -40,10 +39,6 @@ describe("routes/secondfactor/u2f/register/post", function () {
mocks = s.mocks; mocks = s.mocks;
vars = s.variables; vars = s.variables;
const options = {
inMemoryOnly: true
};
mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({})); mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({})); mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));

View File

@ -1,15 +1,14 @@
import * as Express from "express";
import sinon = require("sinon"); import sinon = require("sinon");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import Assert = require("assert"); import Assert = require("assert");
import U2FRegisterRequestGet = require("./get"); import U2FRegisterRequestGet = require("./get");
import ExpressMock = require("../../../../stubs/express.spec"); import ExpressMock = require("../../../../stubs/express.spec");
import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec";
import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../ServerVariablesMockBuilder.spec"; import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../ServerVariablesMockBuilder.spec";
import { ServerVariables } from "../../../../ServerVariables"; import { ServerVariables } from "../../../../ServerVariables";
describe("routes/secondfactor/u2f/register_request/get", function () { describe("routes/secondfactor/u2f/register_request/get", function () {
let req: ExpressMock.RequestMock; let req: Express.Request;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let vars: ServerVariables; let vars: ServerVariables;
@ -17,8 +16,8 @@ describe("routes/secondfactor/u2f/register_request/get", function () {
beforeEach(function () { beforeEach(function () {
req = ExpressMock.RequestMock(); req = ExpressMock.RequestMock();
req.originalUrl = "/api/xxxx"; req.originalUrl = "/api/xxxx";
req.app = {};
req.session = { req.session = {
...req.session,
auth: { auth: {
userid: "user", userid: "user",
first_factor: true, first_factor: true,

View File

@ -1,4 +1,4 @@
import * as Express from "express";
import sinon = require("sinon"); import sinon = require("sinon");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import Assert = require("assert"); import Assert = require("assert");
@ -10,14 +10,13 @@ import ExpressMock = require("../../../../stubs/express.spec");
import { Level } from "../../../../authentication/Level"; import { Level } from "../../../../authentication/Level";
describe("routes/secondfactor/u2f/sign/post", function () { describe("routes/secondfactor/u2f/sign/post", function () {
let req: ExpressMock.RequestMock; let req: Express.Request;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let vars: ServerVariables; let vars: ServerVariables;
beforeEach(function () { beforeEach(function () {
req = ExpressMock.RequestMock(); req = ExpressMock.RequestMock();
req.app = {};
req.originalUrl = "/api/xxxx"; req.originalUrl = "/api/xxxx";
const s = ServerVariablesMockBuilder.build(); const s = ServerVariablesMockBuilder.build();
@ -25,6 +24,7 @@ describe("routes/secondfactor/u2f/sign/post", function () {
vars = s.variables; vars = s.variables;
req.session = { req.session = {
...req.session,
auth: { auth: {
userid: "user", userid: "user",
authentication_level: Level.ONE_FACTOR, authentication_level: Level.ONE_FACTOR,

View File

@ -1,4 +1,4 @@
import * as Express from "express";
import sinon = require("sinon"); import sinon = require("sinon");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import assert = require("assert"); import assert = require("assert");
@ -8,10 +8,8 @@ import { Request } from "u2f";
import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec"; import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec";
import { ServerVariables } from "../../../../ServerVariables"; import { ServerVariables } from "../../../../ServerVariables";
import { SignMessage } from "../../../../../../../shared/SignMessage";
describe("routes/secondfactor/u2f/sign_request/get", function () { describe("routes/secondfactor/u2f/sign_request/get", function () {
let req: ExpressMock.RequestMock; let req: Express.Request;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let vars: ServerVariables; let vars: ServerVariables;
@ -19,8 +17,8 @@ describe("routes/secondfactor/u2f/sign_request/get", function () {
beforeEach(function () { beforeEach(function () {
req = ExpressMock.RequestMock(); req = ExpressMock.RequestMock();
req.originalUrl = "/api/xxxx"; req.originalUrl = "/api/xxxx";
req.app = {};
req.session = { req.session = {
...req.session,
auth: { auth: {
userid: "user", userid: "user",
first_factor: true, first_factor: true,

View File

@ -1,9 +1,7 @@
import Assert = require("assert"); import * as Assert from "assert";
import BluebirdPromise = require("bluebird"); import * as Express from "express";
import Express = require("express"); import * as Sinon from "sinon";
import Sinon = require("sinon");
import winston = require("winston");
import VerifyGet = require("./get"); import VerifyGet = require("./get");
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler"; import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
@ -13,9 +11,10 @@ import { ServerVariables } from "../../ServerVariables";
import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../ServerVariablesMockBuilder.spec"; import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../ServerVariablesMockBuilder.spec";
import { Level } from "../../authentication/Level"; import { Level } from "../../authentication/Level";
import { Level as AuthorizationLevel } from "../../authorization/Level"; import { Level as AuthorizationLevel } from "../../authorization/Level";
import { HEADER_X_ORIGINAL_URL } from "../../../../../shared/constants";
describe("routes/verify/get", function () { describe("routes/verify/get", function () {
let req: ExpressMock.RequestMock; let req: Express.Request;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let vars: ServerVariables; let vars: ServerVariables;
@ -29,7 +28,7 @@ describe("routes/verify/get", function () {
redirect: "undefined" redirect: "undefined"
}; };
AuthenticationSessionHandler.reset(req as any); AuthenticationSessionHandler.reset(req as any);
req.headers["x-original-url"] = "https://secret.example.com/"; req.headers[HEADER_X_ORIGINAL_URL] = "https://secret.example.com/";
const s = ServerVariablesMockBuilder.build(false); const s = ServerVariablesMockBuilder.build(false);
mocks = s.mocks; mocks = s.mocks;
vars = s.variables; vars = s.variables;
@ -37,40 +36,37 @@ describe("routes/verify/get", function () {
}); });
describe("with session cookie", function () { describe("with session cookie", function () {
it("should be already authenticated", function () { it("should be already authenticated", async function () {
mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR); mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
authSession.authentication_level = Level.TWO_FACTOR; authSession.authentication_level = Level.TWO_FACTOR;
authSession.userid = "myuser"; authSession.userid = "myuser";
authSession.groups = ["mygroup", "othergroup"]; authSession.groups = ["mygroup", "othergroup"];
return VerifyGet.default(vars)(req as Express.Request, res as any) await VerifyGet.default(vars)(req as Express.Request, res as any);
.then(function () { res.setHeader.calledWith("Remote-User", "myuser");
Sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "myuser"); res.setHeader.calledWith("Remote-Groups", "mygroup,othergroup");
Sinon.assert.calledWithExactly(res.setHeader, "Remote-Groups", "mygroup,othergroup"); Assert.equal(204, res.status.getCall(0).args[0]);
Assert.equal(204, res.status.getCall(0).args[0]);
});
}); });
function test_session(_authSession: AuthenticationSession, status_code: number) { function test_session(_authSession: AuthenticationSession, status_code: number) {
const GetMock = Sinon.stub(AuthenticationSessionHandler, 'get');
GetMock.returns(_authSession);
return VerifyGet.default(vars)(req as Express.Request, res as any) return VerifyGet.default(vars)(req as Express.Request, res as any)
.then(function () { .then(function () {
Assert.equal(status_code, res.status.getCall(0).args[0]); Assert.equal(status_code, res.status.getCall(0).args[0]);
}); GetMock.restore();
})
} }
function test_non_authenticated_401(authSession: AuthenticationSession) { function test_non_authenticated_401(_authSession: AuthenticationSession) {
return test_session(authSession, 401); return test_session(_authSession, 401);
} }
function test_unauthorized_403(authSession: AuthenticationSession) { function test_unauthorized_403(_authSession: AuthenticationSession) {
return test_session(authSession, 403); return test_session(_authSession, 403);
}
function test_authorized(authSession: AuthenticationSession) {
return test_session(authSession, 204);
} }
describe("given user tries to access a 2-factor endpoint", function () { describe("given user tries to access a 2-factor endpoint", function () {
before(function () { beforeEach(function () {
mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR); mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
}); });
@ -115,7 +111,7 @@ describe("routes/verify/get", function () {
it("should not be authenticated when domain is not allowed for user", function () { it("should not be authenticated when domain is not allowed for user", function () {
authSession.authentication_level = Level.TWO_FACTOR; authSession.authentication_level = Level.TWO_FACTOR;
authSession.userid = "myuser"; authSession.userid = "myuser";
req.headers["x-original-url"] = "https://test.example.com/"; req.headers[HEADER_X_ORIGINAL_URL] = "https://test.example.com/";
mocks.authorizer.authorizationMock.returns(AuthorizationLevel.DENY); mocks.authorizer.authorizationMock.returns(AuthorizationLevel.DENY);
return test_unauthorized_403({ return test_unauthorized_403({
@ -132,7 +128,7 @@ describe("routes/verify/get", function () {
describe("given user tries to access a single factor endpoint", function () { describe("given user tries to access a single factor endpoint", function () {
beforeEach(function () { beforeEach(function () {
req.headers["x-original-url"] = "https://redirect.url/"; req.headers[HEADER_X_ORIGINAL_URL] = "https://redirect.url/";
}); });
it("should be authenticated when first factor is validated", function () { it("should be authenticated when first factor is validated", function () {
@ -227,7 +223,7 @@ describe("routes/verify/get", function () {
}); });
describe("with basic auth", function () { describe("with basic auth", function () {
it("should authenticate correctly", function () { it("should authenticate correctly", async function () {
mocks.authorizer.authorizationMock.returns(AuthorizationLevel.ONE_FACTOR); mocks.authorizer.authorizationMock.returns(AuthorizationLevel.ONE_FACTOR);
mocks.config.access_control.default_policy = "one_factor"; mocks.config.access_control.default_policy = "one_factor";
mocks.usersDatabase.checkUserPasswordStub.returns({ mocks.usersDatabase.checkUserPasswordStub.returns({
@ -235,12 +231,10 @@ describe("routes/verify/get", function () {
}); });
req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA=="; req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
return VerifyGet.default(vars)(req as Express.Request, res as any) await VerifyGet.default(vars)(req as Express.Request, res as any)
.then(function () { res.setHeader.calledWith("Remote-User", "john");
Sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "john"); res.setHeader.calledWith("Remote-Groups", "mygroup,othergroup");
Sinon.assert.calledWithExactly(res.setHeader, "Remote-Groups", "mygroup,othergroup"); Assert.equal(204, res.status.getCall(0).args[0]);
Assert.equal(204, res.status.getCall(0).args[0]);
});
}); });
it("should fail when endpoint is protected by two factors", function () { it("should fail when endpoint is protected by two factors", function () {

View File

@ -6,34 +6,44 @@ import { ServerVariables } from "../../ServerVariables";
import GetWithSessionCookieMethod from "./get_session_cookie"; import GetWithSessionCookieMethod from "./get_session_cookie";
import GetWithBasicAuthMethod from "./get_basic_auth"; import GetWithBasicAuthMethod from "./get_basic_auth";
import Constants = require("../../../../../shared/constants"); import Constants = require("../../../../../shared/constants");
import ObjectPath = require("object-path");
import { AuthenticationSessionHandler } import { AuthenticationSessionHandler }
from "../../AuthenticationSessionHandler"; from "../../AuthenticationSessionHandler";
import { AuthenticationSession } import { AuthenticationSession }
from "../../../../types/AuthenticationSession"; from "../../../../types/AuthenticationSession";
import GetHeader from "../../utils/GetHeader";
const REMOTE_USER = "Remote-User"; const REMOTE_USER = "Remote-User";
const REMOTE_GROUPS = "Remote-Groups"; const REMOTE_GROUPS = "Remote-Groups";
function verifyWithSelectedMethod(req: Express.Request, res: Express.Response, function verifyWithSelectedMethod(req: Express.Request, res: Express.Response,
vars: ServerVariables, authSession: AuthenticationSession) vars: ServerVariables, authSession: AuthenticationSession | undefined)
: () => BluebirdPromise<{ username: string, groups: string[] }> { : () => BluebirdPromise<{ username: string, groups: string[] }> {
return function () { return function () {
const authorization: string = "" + req.headers["proxy-authorization"]; const authorization = GetHeader(req, Constants.HEADER_PROXY_AUTHORIZATION);
if (authorization && authorization.startsWith("Basic ")) if (authorization) {
return GetWithBasicAuthMethod(req, res, vars, authorization); if (authorization.startsWith("Basic ")) {
return GetWithBasicAuthMethod(req, res, vars, authorization);
return GetWithSessionCookieMethod(req, res, vars, authSession); }
else {
throw new Error("The authorization header should be of the form 'Basic XXXXXX'");
}
}
else {
if (authSession) {
return GetWithSessionCookieMethod(req, res, vars, authSession);
}
else {
throw new Error("No cookie detected.");
}
}
}; };
} }
function setRedirectHeader(req: Express.Request, res: Express.Response) { function setRedirectHeader(req: Express.Request, res: Express.Response) {
return function () { return function () {
const originalUrl = ObjectPath.get<Express.Request, string>( const originalUrl = GetHeader(req, Constants.HEADER_X_ORIGINAL_URL);
req, "headers.x-original-url"); res.set(Constants.HEADER_REDIRECT, originalUrl);
res.set("Redirect", originalUrl);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}; };
} }
@ -62,7 +72,7 @@ function getRedirectParam(req: Express.Request) {
export default function (vars: ServerVariables) { export default function (vars: ServerVariables) {
return function (req: Express.Request, res: Express.Response) return function (req: Express.Request, res: Express.Response)
: BluebirdPromise<void> { : BluebirdPromise<void> {
let authSession: AuthenticationSession; let authSession: AuthenticationSession | undefined;
return new BluebirdPromise(function (resolve, reject) { return new BluebirdPromise(function (resolve, reject) {
authSession = AuthenticationSessionHandler.get(req, vars.logger); authSession = AuthenticationSessionHandler.get(req, vars.logger);
resolve(); resolve();
@ -78,6 +88,7 @@ export default function (vars: ServerVariables) {
ErrorReplies.replyWithError401(req, res, vars.logger)) ErrorReplies.replyWithError401(req, res, vars.logger))
// The user is not yet authenticated -> 401 // The user is not yet authenticated -> 401
.catch((err) => { .catch((err) => {
console.error(err);
// This redirect parameter is used in Kubernetes to annotate the ingress with // This redirect parameter is used in Kubernetes to annotate the ingress with
// the url to the authentication portal. // the url to the authentication portal.
const redirectUrl = getRedirectParam(req); const redirectUrl = getRedirectParam(req);

View File

@ -1,18 +1,17 @@
import Express = require("express"); import Express = require("express");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import ObjectPath = require("object-path");
import { ServerVariables } from "../../ServerVariables"; import { ServerVariables } from "../../ServerVariables";
import { AuthenticationSession }
from "../../../../types/AuthenticationSession";
import AccessControl from "./access_control"; import AccessControl from "./access_control";
import { URLDecomposer } from "../../utils/URLDecomposer"; import { URLDecomposer } from "../../utils/URLDecomposer";
import { Level } from "../../authentication/Level"; import { Level } from "../../authentication/Level";
import GetHeader from "../../utils/GetHeader";
import { HEADER_X_ORIGINAL_URL } from "../../../../../shared/constants";
export default function (req: Express.Request, res: Express.Response, export default function (req: Express.Request, res: Express.Response,
vars: ServerVariables, authorizationHeader: string) vars: ServerVariables, authorizationHeader: string)
: BluebirdPromise<{ username: string, groups: string[] }> { : BluebirdPromise<{ username: string, groups: string[] }> {
let username: string; let username: string;
const uri = ObjectPath.get<Express.Request, string>(req, "headers.x-original-url"); const uri = GetHeader(req, HEADER_X_ORIGINAL_URL);
const urlDecomposition = URLDecomposer.fromUrl(uri); const urlDecomposition = URLDecomposer.fromUrl(uri);
return BluebirdPromise.resolve() return BluebirdPromise.resolve()

View File

@ -1,8 +1,5 @@
import Express = require("express"); import Express = require("express");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import Util = require("util");
import ObjectPath = require("object-path");
import Exceptions = require("../../Exceptions"); import Exceptions = require("../../Exceptions");
import { Configuration } from "../../configuration/schema/Configuration"; import { Configuration } from "../../configuration/schema/Configuration";
import { ServerVariables } from "../../ServerVariables"; import { ServerVariables } from "../../ServerVariables";
@ -13,6 +10,8 @@ import { AuthenticationSessionHandler }
from "../../AuthenticationSessionHandler"; from "../../AuthenticationSessionHandler";
import AccessControl from "./access_control"; import AccessControl from "./access_control";
import { URLDecomposer } from "../../utils/URLDecomposer"; import { URLDecomposer } from "../../utils/URLDecomposer";
import GetHeader from "../../utils/GetHeader";
import { HEADER_X_ORIGINAL_URL } from "../../../../../shared/constants";
function verify_inactivity(req: Express.Request, function verify_inactivity(req: Express.Request,
authSession: AuthenticationSession, authSession: AuthenticationSession,
@ -54,8 +53,7 @@ export default function (req: Express.Request, res: Express.Response,
"userid is missing")); "userid is missing"));
} }
const originalUrl = ObjectPath.get<Express.Request, string>( const originalUrl = GetHeader(req, HEADER_X_ORIGINAL_URL);
req, "headers.x-original-url");
const d = URLDecomposer.fromUrl(originalUrl); const d = URLDecomposer.fromUrl(originalUrl);
vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", d.domain, vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", d.domain,

View File

@ -1,103 +1,117 @@
import sinon = require("sinon"); import * as Sinon from "sinon";
import express = require("express"); import * as Express from "express";
import { GET_VARIABLE_KEY } from "../../../../shared/constants";
export interface RequestMock { import { RequestLoggerStub } from "../logging/RequestLoggerStub.spec";
app?: any;
body?: any;
session?: any;
headers?: any;
get?: any;
query?: any;
originalUrl: string;
}
export interface ResponseMock { export interface ResponseMock {
send: sinon.SinonStub | sinon.SinonSpy; send: Sinon.SinonStub | Sinon.SinonSpy;
sendStatus: sinon.SinonStub; sendStatus: Sinon.SinonStub;
sendFile: sinon.SinonStub; sendFile: Sinon.SinonStub;
sendfile: sinon.SinonStub; sendfile: Sinon.SinonStub;
status: sinon.SinonStub | sinon.SinonSpy; status: Sinon.SinonStub | Sinon.SinonSpy;
json: sinon.SinonStub | sinon.SinonSpy; json: Sinon.SinonStub | Sinon.SinonSpy;
links: sinon.SinonStub; links: Sinon.SinonStub;
jsonp: sinon.SinonStub; jsonp: Sinon.SinonStub;
download: sinon.SinonStub; download: Sinon.SinonStub;
contentType: sinon.SinonStub; contentType: Sinon.SinonStub;
type: sinon.SinonStub; type: Sinon.SinonStub;
format: sinon.SinonStub; format: Sinon.SinonStub;
attachment: sinon.SinonStub; attachment: Sinon.SinonStub;
set: sinon.SinonStub; set: Sinon.SinonStub;
header: sinon.SinonStub; header: Sinon.SinonStub;
headersSent: boolean; headersSent: boolean;
get: sinon.SinonStub; get: Sinon.SinonStub;
clearCookie: sinon.SinonStub; clearCookie: Sinon.SinonStub;
cookie: sinon.SinonStub; cookie: Sinon.SinonStub;
location: sinon.SinonStub; location: Sinon.SinonStub;
redirect: sinon.SinonStub | sinon.SinonSpy; redirect: Sinon.SinonStub | Sinon.SinonSpy;
render: sinon.SinonStub | sinon.SinonSpy; render: Sinon.SinonStub | Sinon.SinonSpy;
locals: sinon.SinonStub; locals: Sinon.SinonStub;
charset: string; charset: string;
vary: sinon.SinonStub; vary: Sinon.SinonStub;
app: any; app: any;
write: sinon.SinonStub; write: Sinon.SinonStub;
writeContinue: sinon.SinonStub; writeContinue: Sinon.SinonStub;
writeHead: sinon.SinonStub; writeHead: Sinon.SinonStub;
statusCode: number; statusCode: number;
statusMessage: string; statusMessage: string;
setHeader: sinon.SinonStub; setHeader: Sinon.SinonStub;
setTimeout: sinon.SinonStub; setTimeout: Sinon.SinonStub;
sendDate: boolean; sendDate: boolean;
getHeader: sinon.SinonStub; getHeader: Sinon.SinonStub;
} }
export function RequestMock(): RequestMock { export function RequestMock(): Express.Request {
const getMock = Sinon.mock()
.withArgs(GET_VARIABLE_KEY).atLeast(0).returns({
logger: new RequestLoggerStub()
});
return { return {
originalUrl: "/non-api/xxx", id: '1234',
headers: {},
app: { app: {
get: sinon.stub() get: getMock,
set: Sinon.mock(),
}, },
headers: { body: {},
"x-forwarded-for": "127.0.0.1" query: {},
}, session: {
session: {} id: '1234',
}; regenerate: function() {},
reload: function() {},
destroy: function() {},
save: function() {},
touch: function() {},
cookie: {
domain: 'example.com',
expires: true,
httpOnly: true,
maxAge: 36000,
originalMaxAge: 36000,
path: '/',
secure: true,
serialize: () => '',
}
}
} as any;
} }
export function ResponseMock(): ResponseMock { export function ResponseMock(): ResponseMock {
return { return {
send: sinon.stub(), send: Sinon.stub(),
status: sinon.stub(), status: Sinon.stub(),
json: sinon.stub(), json: Sinon.stub(),
sendStatus: sinon.stub(), sendStatus: Sinon.stub(),
links: sinon.stub(), links: Sinon.stub(),
jsonp: sinon.stub(), jsonp: Sinon.stub(),
sendFile: sinon.stub(), sendFile: Sinon.stub(),
sendfile: sinon.stub(), sendfile: Sinon.stub(),
download: sinon.stub(), download: Sinon.stub(),
contentType: sinon.stub(), contentType: Sinon.stub(),
type: sinon.stub(), type: Sinon.stub(),
format: sinon.stub(), format: Sinon.stub(),
attachment: sinon.stub(), attachment: Sinon.stub(),
set: sinon.stub(), set: Sinon.stub(),
header: sinon.stub(), header: Sinon.stub(),
headersSent: true, headersSent: true,
get: sinon.stub(), get: Sinon.stub(),
clearCookie: sinon.stub(), clearCookie: Sinon.stub(),
cookie: sinon.stub(), cookie: Sinon.stub(),
location: sinon.stub(), location: Sinon.stub(),
redirect: sinon.stub(), redirect: Sinon.stub(),
render: sinon.stub(), render: Sinon.stub(),
locals: sinon.stub(), locals: Sinon.stub(),
charset: "utf-8", charset: "utf-8",
vary: sinon.stub(), vary: Sinon.stub(),
app: sinon.stub(), app: Sinon.stub(),
write: sinon.stub(), write: Sinon.stub(),
writeContinue: sinon.stub(), writeContinue: Sinon.stub(),
writeHead: sinon.stub(), writeHead: Sinon.stub(),
statusCode: 200, statusCode: 200,
statusMessage: "message", statusMessage: "message",
setHeader: sinon.stub(), setHeader: Sinon.stub(),
setTimeout: sinon.stub(), setTimeout: Sinon.stub(),
sendDate: true, sendDate: true,
getHeader: sinon.stub() getHeader: Sinon.stub()
}; };
} }

View File

@ -0,0 +1,20 @@
import * as Express from "express";
import GetHeader from "./GetHeader";
import { RequestMock } from "../stubs/express.spec";
import * as Assert from "assert";
describe('GetHeader', function() {
let req: Express.Request;
beforeEach(() => {
req = RequestMock();
});
it('should return the header if it exists', function() {
req.headers["x-target-url"] = 'www.example.com';
Assert.equal(GetHeader(req, 'x-target-url'), 'www.example.com');
});
it('should return undefined if header does not exist', function() {
Assert.equal(GetHeader(req, 'x-target-url'), undefined);
});
});

View File

@ -0,0 +1,19 @@
import * as Express from "express";
import * as ObjectPath from "object-path";
import { ServerVariables } from "../ServerVariables";
import { GET_VARIABLE_KEY } from "../../../../shared/constants";
/**
*
* @param req The express request to extract headers from
* @param header The name of the header to extract in lowercase.
* @returns The header if found, otherwise undefined.
*/
export default function(req: Express.Request, header: string): string | undefined {
const variables: ServerVariables = req.app.get(GET_VARIABLE_KEY);
if (!variables) return undefined;
const value = ObjectPath.get<Express.Request, string>(req, "headers." + header, undefined);
variables.logger.debug(req, "Header %s is set to %s", header, value);
return value;
}

View File

@ -1,11 +1,8 @@
import { ServerVariables } from "../ServerVariables"; import { ServerVariables } from "../ServerVariables";
import { Level } from "../authentication/Level";
import * as URLParse from "url-parse"; import * as URLParse from "url-parse";
import { AuthenticationSession } from "AuthenticationSession";
export default function IsRedirectionSafe( export default function IsRedirectionSafe(
vars: ServerVariables, vars: ServerVariables,
authSession: AuthenticationSession,
url: URLParse): boolean { url: URLParse): boolean {
const urlInDomain = url.hostname.endsWith(vars.config.session.domain); const urlInDomain = url.hostname.endsWith(vars.config.session.domain);

View File

@ -1,7 +1,7 @@
import { DomainExtractor } from "./DomainExtractor"; import { DomainExtractor } from "./DomainExtractor";
import Assert = require("assert"); import Assert = require("assert");
describe.only("shared/DomainExtractor", function () { describe("shared/DomainExtractor", function () {
describe("test fromUrl", function () { describe("test fromUrl", function () {
it("should return domain from https url", function () { it("should return domain from https url", function () {
const domain = DomainExtractor.fromUrl("https://www.example.com/test/abc"); const domain = DomainExtractor.fromUrl("https://www.example.com/test/abc");

View File

@ -1 +1,11 @@
export const REDIRECT_QUERY_PARAM = "rd"; export const REDIRECT_QUERY_PARAM = "rd";
// Used as a first factor script parameter for knowing the authorizations
// related to the domain and resource.
export const HEADER_X_TARGET_URL = "x-target-url";
export const HEADER_X_ORIGINAL_URL = "x-original-url";
export const HEADER_PROXY_AUTHORIZATION = "proxy-authorization";
export const HEADER_REDIRECT = "redirect";
export const GET_VARIABLE_KEY = "variables";

3
spec-helper.js Normal file
View File

@ -0,0 +1,3 @@
var colors = require('mocha/lib/reporters/base').colors;
colors['pass'] = 32;
colors['error stack'] = 34;