Migrate more Cucumber tests into Mocha.

This commit is contained in:
Clement Michaud 2019-02-12 23:23:43 +01:00
parent efceb66ffa
commit c579355c5b
39 changed files with 267 additions and 105 deletions

2
.gitignore vendored
View File

@ -36,3 +36,5 @@ example/ldap/private.ldif
Configuration.schema.json Configuration.schema.json
users_database.test.yml users_database.test.yml
.suite

View File

@ -1,7 +1,7 @@
import { Dispatch } from "redux"; import { Dispatch } from "redux";
import * as AutheliaService from '../services/AutheliaService'; import * as AutheliaService from '../services/AutheliaService';
export default async function(url: string, dispatch: Dispatch) { export default async function(url: string) {
try { try {
// Check the url against the backend before redirecting. // Check the url against the backend before redirecting.
await AutheliaService.checkRedirection(url); await AutheliaService.checkRedirection(url);

View File

@ -10,6 +10,10 @@ import Notification from "../../components/Notification/Notification";
import styles from '../../assets/scss/components/FirstFactorForm/FirstFactorForm.module.scss'; import styles from '../../assets/scss/components/FirstFactorForm/FirstFactorForm.module.scss';
export interface OwnProps {
redirectionUrl: string | null;
}
export interface StateProps { export interface StateProps {
formDisabled: boolean; formDisabled: boolean;
error: string | null; error: string | null;
@ -19,7 +23,7 @@ export interface DispatchProps {
onAuthenticationRequested(username: string, password: string, rememberMe: boolean): void; onAuthenticationRequested(username: string, password: string, rememberMe: boolean): void;
} }
export type Props = StateProps & DispatchProps; export type Props = OwnProps & StateProps & DispatchProps;
interface State { interface State {
username: string; username: string;

View File

@ -10,7 +10,7 @@ import Notification from '../Notification/Notification';
export interface OwnProps { export interface OwnProps {
username: string; username: string;
redirection: string | null; redirectionUrl: string | null;
} }
export interface StateProps { export interface StateProps {

View File

@ -1,11 +1,12 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import { authenticateFailure, authenticateSuccess, authenticate } from '../../../reducers/Portal/FirstFactor/actions'; import { authenticateFailure, authenticateSuccess, authenticate } from '../../../reducers/Portal/FirstFactor/actions';
import FirstFactorForm, { StateProps } from '../../../components/FirstFactorForm/FirstFactorForm'; import FirstFactorForm, { StateProps, OwnProps } from '../../../components/FirstFactorForm/FirstFactorForm';
import { RootState } from '../../../reducers'; import { RootState } from '../../../reducers';
import * as AutheliaService from '../../../services/AutheliaService'; import * as AutheliaService from '../../../services/AutheliaService';
import to from 'await-to-js'; import to from 'await-to-js';
import FetchStateBehavior from '../../../behaviors/FetchStateBehavior'; import FetchStateBehavior from '../../../behaviors/FetchStateBehavior';
import SafelyRedirectBehavior from '../../../behaviors/SafelyRedirectBehavior';
const mapStateToProps = (state: RootState): StateProps => { const mapStateToProps = (state: RootState): StateProps => {
return { return {
@ -14,13 +15,14 @@ const mapStateToProps = (state: RootState): StateProps => {
}; };
} }
function onAuthenticationRequested(dispatch: Dispatch) { function onAuthenticationRequested(dispatch: Dispatch, redirectionUrl: string | null) {
return async (username: string, password: string, rememberMe: boolean) => { return async (username: string, password: string, rememberMe: boolean) => {
let err, res; let err, res;
// Validate first factor // Validate first factor
dispatch(authenticate()); dispatch(authenticate());
[err, res] = await to(AutheliaService.postFirstFactorAuth(username, password, rememberMe)); [err, res] = await to(AutheliaService.postFirstFactorAuth(
username, password, rememberMe, redirectionUrl));
if (err) { if (err) {
await dispatch(authenticateFailure(err.message)); await dispatch(authenticateFailure(err.message));
@ -32,24 +34,31 @@ function onAuthenticationRequested(dispatch: Dispatch) {
return; return;
} }
if (res.status !== 204) { if (res.status === 200) {
const json = await res.json(); const json = await res.json();
if ('error' in json) { if ('error' in json) {
await dispatch(authenticateFailure(json['error'])); await dispatch(authenticateFailure(json['error']));
return; return;
} }
if ('redirect' in json) {
window.location.href = json['redirect'];
return;
}
} else if (res.status === 204) {
dispatch(authenticateSuccess());
// fetch state to move to next stage
FetchStateBehavior(dispatch);
} else {
dispatch(authenticateFailure('Unknown error'));
} }
dispatch(authenticateSuccess());
// fetch state
FetchStateBehavior(dispatch);
} }
} }
const mapDispatchToProps = (dispatch: Dispatch) => { const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps) => {
return { return {
onAuthenticationRequested: onAuthenticationRequested(dispatch), onAuthenticationRequested: onAuthenticationRequested(dispatch, ownProps.redirectionUrl),
} }
} }

View File

@ -55,9 +55,9 @@ async function triggerSecurityKeySigning(dispatch: Dispatch) {
async function handleSuccess(dispatch: Dispatch, ownProps: OwnProps, duration?: number) { async function handleSuccess(dispatch: Dispatch, ownProps: OwnProps, duration?: number) {
async function handle() { async function handle() {
if (ownProps.redirection) { if (ownProps.redirectionUrl) {
try { try {
await SafelyRedirectBehavior(ownProps.redirection, dispatch); await SafelyRedirectBehavior(ownProps.redirectionUrl);
} catch (e) { } catch (e) {
await fetchState(dispatch); await fetchState(dispatch);
} }

View File

@ -37,11 +37,9 @@ const mapStateToProps = (state: RootState, ownProps: OwnProps): StateProps => {
}; };
} }
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => { const mapDispatchToProps = (dispatch: Dispatch) => {
return { return {
onInit: async () => { onInit: async () => await FetchStateBehavior(dispatch)
await FetchStateBehavior(dispatch);
}
} }
} }

View File

@ -6,6 +6,7 @@ function getReturnType<R> (f: (...args: any[]) => R): R {
} }
const t = getReturnType(PortalReducer) const t = getReturnType(PortalReducer)
export type RootState = StateType<typeof t>; export type RootState = StateType<typeof t>;
export default PortalReducer; export default PortalReducer;

View File

@ -23,13 +23,20 @@ export async function fetchState() {
} }
export async function postFirstFactorAuth(username: string, password: string, export async function postFirstFactorAuth(username: string, password: string,
rememberMe: boolean) { rememberMe: boolean, redirectionUrl: string | null) {
const headers: Record<string, string> = {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
if (redirectionUrl) {
headers['X-Target-Url'] = redirectionUrl;
}
return fetchSafe('/api/firstfactor', { return fetchSafe('/api/firstfactor', {
method: 'POST', method: 'POST',
headers: { headers: headers,
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ body: JSON.stringify({
username: username, username: username,
password: password, password: password,

View File

@ -36,12 +36,12 @@ class AuthenticationView extends Component<Props> {
if (this.props.stage === Stage.SECOND_FACTOR) { if (this.props.stage === Stage.SECOND_FACTOR) {
return <SecondFactorForm return <SecondFactorForm
username={this.props.remoteState.username} username={this.props.remoteState.username}
redirection={this.props.redirectionUrl} />; redirectionUrl={this.props.redirectionUrl} />;
} else if (this.props.stage === Stage.ALREADY_AUTHENTICATED) { } else if (this.props.stage === Stage.ALREADY_AUTHENTICATED) {
return <AlreadyAuthenticated return <AlreadyAuthenticated
username={this.props.remoteState.username}/>; username={this.props.remoteState.username}/>;
} }
return <FirstFactorForm />; return <FirstFactorForm redirectionUrl={this.props.redirectionUrl} />;
} }
} }

View File

@ -1,7 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
var program = require('commander'); var program = require('commander');
var exec = require('child_process').execSync;
var spawn = require('child_process').spawn; var spawn = require('child_process').spawn;
var chokidar = require('chokidar'); var chokidar = require('chokidar');
var fs = require('fs'); var fs = require('fs');
@ -101,15 +100,39 @@ function reload(path) {
reloadServer(); reloadServer();
} }
fs.writeFileSync(ENVIRONMENT_FILENAME, program.suite); function exec(command, args) {
exec('./example/compose/nginx/portal/render.js'); return new Promise((resolve, reject) => {
exec('./scripts/utils/prepare-environment.sh'); const cmd = spawn(command, args);
console.log('Start watching'); cmd.stdout.pipe(process.stdout);
tsWatcher.on('add', reload); cmd.stderr.pipe(process.stderr);
tsWatcher.on('remove', reload); cmd.on('close', (code) => {
tsWatcher.on('change', reload); if (code == 0) {
resolve();
return;
}
reject(new Error('Status code ' + code));
});
});
}
startServer(); async function main() {
startClient(); console.log(`Create suite file ${ENVIRONMENT_FILENAME}.`);
fs.writeFileSync(ENVIRONMENT_FILENAME, program.suite);
console.log(`Render nginx configuration...`);
await exec('./example/compose/nginx/portal/render.js');
console.log(`Prepare environment with docker-compose...`);
await exec('./scripts/utils/prepare-environment.sh');
console.log('Start watching...');
tsWatcher.on('add', reload);
tsWatcher.on('remove', reload);
tsWatcher.on('change', reload);
startServer();
startClient();
}
main()

View File

@ -1,5 +1,5 @@
import { ACLConfiguration, ACLPolicy, ACLRule } from "../configuration/schema/AclConfiguration"; import { ACLConfiguration, ACLRule } from "../configuration/schema/AclConfiguration";
import { IAuthorizer } from "./IAuthorizer"; import { IAuthorizer } from "./IAuthorizer";
import { Winston } from "../../../types/Dependencies"; import { Winston } from "../../../types/Dependencies";
import { MultipleDomainMatcher } from "./MultipleDomainMatcher"; import { MultipleDomainMatcher } from "./MultipleDomainMatcher";

View File

@ -1,5 +1,6 @@
import Exceptions = require("../../Exceptions"); import Exceptions = require("../../Exceptions");
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");
@ -9,6 +10,11 @@ import { ServerVariables } from "../../ServerVariables";
import { AuthenticationSession } from "../../../../types/AuthenticationSession"; import { AuthenticationSession } from "../../../../types/AuthenticationSession";
import { GroupsAndEmails } from "../../authentication/backends/GroupsAndEmails"; import { GroupsAndEmails } from "../../authentication/backends/GroupsAndEmails";
import { Level } from "../../authentication/Level"; import { Level } from "../../authentication/Level";
import { Level as AuthorizationLevel } from "../../authorization/Level";
import { BelongToDomain } from "../../../../../shared/BelongToDomain";
import { URLDecomposer } from "../..//utils/URLDecomposer";
import { Object } from "../../../lib/authorization/Object";
import { Subject } from "../../../lib/authorization/Subject";
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)
@ -54,6 +60,37 @@ export default function (vars: ServerVariables) {
vars.logger.debug(req, "Mark successful authentication to regulator."); vars.logger.debug(req, "Mark successful authentication to regulator.");
vars.regulator.mark(username, true); vars.regulator.mark(username, true);
})
.then(function() {
const targetUrl = ObjectPath.get(req, 'headers.x-target-url', null);
if (!targetUrl) {
res.status(204);
res.send();
return BluebirdPromise.resolve();
}
if (BelongToDomain(targetUrl, vars.config.session.domain)) {
const resource = URLDecomposer.fromUrl(targetUrl);
const resObject: Object = {
domain: resource.domain,
resource: resource.path,
}
const subject: Subject = {
user: authSession.userid,
groups: authSession.groups
}
const authorizationLevel = vars.authorizer.authorization(resObject, subject);
if (authorizationLevel <= AuthorizationLevel.ONE_FACTOR) {
res.json({
redirect: targetUrl
});
return BluebirdPromise.resolve();
}
}
res.status(204); res.status(204);
res.send(); res.send();
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();

View File

@ -56,8 +56,6 @@ export default function (req: Express.Request, res: Express.Response,
const originalUrl = ObjectPath.get<Express.Request, string>( const originalUrl = ObjectPath.get<Express.Request, string>(
req, "headers.x-original-url"); req, "headers.x-original-url");
const originalUri =
ObjectPath.get<Express.Request, string>(req, "headers.x-original-uri");
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,5 +1,4 @@
import Express = require("express"); import Express = require("express");
import { DomainExtractor } from "../../../../shared/DomainExtractor";
import { BelongToDomain } from "../../../../shared/BelongToDomain"; import { BelongToDomain } from "../../../../shared/BelongToDomain";

View File

@ -1,3 +1,4 @@
// TODO: replace this decompose by third party library.
export class URLDecomposer { export class URLDecomposer {
static fromUrl(url: string): {domain: string, path: string} { static fromUrl(url: string): {domain: string, path: string} {
if (!url) return; if (!url) return;

View File

@ -1,11 +0,0 @@
Feature: Headers are correctly forwarded to backend
@need-authenticated-user-john
Scenario: Custom-Forwarded-User and Custom-Forwarded-Groups are correctly forwarded to protected backend
When I visit "https://public.example.com:8080/headers"
Then I see header "Custom-Forwarded-User" set to "john"
Then I see header "Custom-Forwarded-Groups" set to "dev,admin"
Scenario: Custom-Forwarded-User and Custom-Forwarded-Groups are correctly forwarded to protected backend when basic auth is used
When I request "https://single_factor.example.com:8080/headers" with username "john" and password "password" using basic authentication
Then I received header "Custom-Forwarded-User" set to "john"
And I received header "Custom-Forwarded-Groups" set to "dev,admin"

View File

@ -1,14 +0,0 @@
import SeleniumWebdriver from "selenium-webdriver";
export default async function(driver: any) {
const content = await driver.wait(
SeleniumWebdriver.until.elementLocated(
SeleniumWebdriver.By.tagName('body')), 5000).getText();
if (content.indexOf('This is a very important secret') > - 1) {
return;
}
else {
throw new Error('Secret page is not accessible.');
}
}

View File

@ -3,6 +3,5 @@ import SeleniumWebdriver, { WebDriver, Locator } from "selenium-webdriver";
export default async function(driver: WebDriver, locator: Locator) { export default async function(driver: WebDriver, locator: Locator) {
const el = await driver.wait( const el = await driver.wait(
SeleniumWebdriver.until.elementLocated(locator), 5000); SeleniumWebdriver.until.elementLocated(locator), 5000);
await el.click(); await el.click();
}; };

View File

@ -1,13 +1,13 @@
import VisitPage from "./VisitPage"; import VisitPage from "./VisitPage";
import FillLoginPageWithUserAndPasswordAndClick from "./FillLoginPageAndClick"; import FillLoginPageAndClick from "./FillLoginPageAndClick";
import ValidateTotp from "./ValidateTotp"; import ValidateTotp from "./ValidateTotp";
import WaitRedirected from "./WaitRedirected"; import VerifyUrlIs from "./assertions/VerifyUrlIs";
import { WebDriver } from "selenium-webdriver"; import { WebDriver } from "selenium-webdriver";
// Validate the two factors! // Validate the two factors!
export default async function(driver: WebDriver, url: string, user: string, secret: string) { export default async function(driver: WebDriver, user: string, secret: string, url: string) {
await VisitPage(driver, `https://login.example.com:8080/?rd=${url}`); await VisitPage(driver, `https://login.example.com:8080/?rd=${url}`);
await FillLoginPageWithUserAndPasswordAndClick(driver, user, 'password'); await FillLoginPageAndClick(driver, user, 'password');
await ValidateTotp(driver, secret); await ValidateTotp(driver, secret);
await WaitRedirected(driver, "https://admin.example.com:8080/secret.html"); await VerifyUrlIs(driver, url);
} }

View File

@ -1,10 +1,10 @@
import RegisterTotp from './RegisterTotp'; import RegisterTotp from './RegisterTotp';
import LoginAs from './LoginAs'; import LoginAs from './LoginAs';
import { WebDriver } from 'selenium-webdriver'; import { WebDriver } from 'selenium-webdriver';
import IsSecondFactorStage from './IsSecondFactorStage'; import VerifyIsSecondFactorStage from './assertions/VerifyIsSecondFactorStage';
export default async function(driver: WebDriver, user: string, email?: boolean) { export default async function(driver: WebDriver, user: string, email: boolean = false) {
await LoginAs(driver, user); await LoginAs(driver, user);
await IsSecondFactorStage(driver); await VerifyIsSecondFactorStage(driver);
return await RegisterTotp(driver, email); return await RegisterTotp(driver, email);
} }

View File

@ -2,7 +2,7 @@ import VisitPage from "./VisitPage";
import FillLoginPageAndClick from './FillLoginPageAndClick'; import FillLoginPageAndClick from './FillLoginPageAndClick';
import { WebDriver } from "selenium-webdriver"; import { WebDriver } from "selenium-webdriver";
export default async function(driver: WebDriver, user: string) { export default async function(driver: WebDriver, user: string, password: string = "password") {
await VisitPage(driver, "https://login.example.com:8080/"); await VisitPage(driver, "https://login.example.com:8080/");
await FillLoginPageAndClick(driver, user, "password"); await FillLoginPageAndClick(driver, user, password);
} }

View File

@ -0,0 +1,13 @@
import SeleniumWebDriver, { WebDriver } from "selenium-webdriver";
import Util from "util";
export default async function(driver: WebDriver, header: string, expectedValue: string) {
const el = await driver.wait(SeleniumWebDriver.until.elementLocated(SeleniumWebDriver.By.tagName("body")), 5000);
const text = await el.getText();
const expectedLine = Util.format("\"%s\": \"%s\"", header, expectedValue);
if (text.indexOf(expectedLine) < 0) {
throw new Error("Header not found.");
}
}

View File

@ -0,0 +1,5 @@
import SeleniumWebdriver, { WebDriver } from "selenium-webdriver";
export default async function(driver: WebDriver, url: string, timeout: number = 5000) {
await driver.wait(SeleniumWebdriver.until.urlIs(url), timeout);
}

View File

@ -0,0 +1,17 @@
import { WebDriver } from "selenium-webdriver";
import LoginAndRegisterTotp from "../LoginAndRegisterTotp";
import FullLogin from "../FullLogin";
import VisitPage from "../VisitPage";
import FillLoginPageAndClick from "../FillLoginPageAndClick";
import VerifyUrlIs from "../assertions/VerifyUrlIs";
export default async function(
driver: WebDriver,
username: string,
password: string,
targetUrl: string) {
await VisitPage(driver, `https://login.example.com:8080/?rd=${targetUrl}`);
await FillLoginPageAndClick(driver, username, password);
await VerifyUrlIs(driver, targetUrl);
};

View File

@ -0,0 +1,13 @@
import { WebDriver } from "selenium-webdriver";
import LoginAndRegisterTotp from "../LoginAndRegisterTotp";
import FullLogin from "../FullLogin";
export default async function(
driver: WebDriver,
username: string,
email: boolean = false,
targetUrl: string = "https://login.example.com:8080/") {
const secret = await LoginAndRegisterTotp(driver, username, email);
await FullLogin(driver, username, secret, targetUrl);
};

View File

@ -1,31 +1,39 @@
require("chromedriver"); require("chromedriver");
import chrome from 'selenium-webdriver/chrome'; import chrome from 'selenium-webdriver/chrome';
import SeleniumWebdriver from "selenium-webdriver"; import SeleniumWebdriver, { WebDriver } from "selenium-webdriver";
export default function(forEach: boolean = false) { export async function StartDriver() {
let options = new chrome.Options(); let options = new chrome.Options();
if (process.env['HEADLESS'] == 'y') { if (process.env['HEADLESS'] == 'y') {
options = options.headless(); options = options.headless();
} }
function beforeBlock(this: Mocha.IHookCallbackContext) { const driver = new SeleniumWebdriver.Builder()
const driver = new SeleniumWebdriver.Builder() .forBrowser("chrome")
.forBrowser("chrome") .setChromeOptions(options)
.setChromeOptions(options) .build();
.build(); return driver;
this.driver = driver; }
}
function afterBlock(this: Mocha.IHookCallbackContext) { export async function StopDriver(driver: WebDriver) {
return this.driver.quit(); return await driver.quit();
} }
export default function(forEach: boolean = false) {
if (forEach) { if (forEach) {
beforeEach(beforeBlock); beforeEach(async function() {
afterEach(afterBlock); this.driver = await StartDriver();
});
afterEach(async function() {
await StopDriver(this.driver);
});
} else { } else {
before(beforeBlock); before(async function() {
after(afterBlock); this.driver = await StartDriver();
});
after(async function() {
await StopDriver(this.driver)
});
} }
} }

View File

@ -2,10 +2,12 @@ import AutheliaSuite from "../../helpers/context/AutheliaSuite";
import MongoConnectionRecovery from "./scenarii/MongoConnectionRecovery"; import MongoConnectionRecovery from "./scenarii/MongoConnectionRecovery";
import EnforceInternalRedirectionsOnly from "./scenarii/EnforceInternalRedirectionsOnly"; import EnforceInternalRedirectionsOnly from "./scenarii/EnforceInternalRedirectionsOnly";
import AccessControl from "./scenarii/AccessControl"; import AccessControl from "./scenarii/AccessControl";
import CustomHeadersForwarded from "./scenarii/CustomHeadersForwarded";
AutheliaSuite('Complete configuration', __dirname + '/config.yml', function() { AutheliaSuite('Complete configuration', __dirname + '/config.yml', function() {
this.timeout(10000); this.timeout(10000);
describe('Custom headers forwarded to backend', CustomHeadersForwarded);
describe('Access control', AccessControl); describe('Access control', AccessControl);
describe('Mongo broken connection recovery', MongoConnectionRecovery); describe('Mongo broken connection recovery', MongoConnectionRecovery);

View File

@ -1,23 +1,23 @@
import LoginAndRegisterTotp from "../../../helpers/LoginAndRegisterTotp"; import LoginAndRegisterTotp from "../../../helpers/LoginAndRegisterTotp";
import VisitPage from "../../../helpers/VisitPage"; import VisitPage from "../../../helpers/VisitPage";
import ObserveSecret from "../../../helpers/assertions/ObserveSecret"; import VerifySecretObserved from "../../../helpers/assertions/VerifySecretObserved";
import WithDriver from "../../../helpers/context/WithDriver"; import WithDriver from "../../../helpers/context/WithDriver";
import FillLoginPageAndClick from "../../../helpers/FillLoginPageAndClick"; import FillLoginPageAndClick from "../../../helpers/FillLoginPageAndClick";
import ValidateTotp from "../../../helpers/ValidateTotp"; import ValidateTotp from "../../../helpers/ValidateTotp";
import WaitRedirected from "../../../helpers/WaitRedirected"; import VerifyUrlIs from "../../../helpers/assertions/VerifyUrlIs";
import Logout from "../../../helpers/Logout"; import Logout from "../../../helpers/Logout";
async function ShouldHaveAccessTo(url: string) { async function ShouldHaveAccessTo(url: string) {
it('should have access to ' + url, async function() { it('should have access to ' + url, async function() {
await VisitPage(this.driver, url); await VisitPage(this.driver, url);
await ObserveSecret(this.driver); await VerifySecretObserved(this.driver);
}) })
} }
async function ShouldNotHaveAccessTo(url: string) { async function ShouldNotHaveAccessTo(url: string) {
it('should not have access to ' + url, async function() { it('should not have access to ' + url, async function() {
await this.driver.get(url); await this.driver.get(url);
await WaitRedirected(this.driver, 'https://login.example.com:8080/'); await VerifyUrlIs(this.driver, 'https://login.example.com:8080/');
}) })
} }

View File

@ -0,0 +1,51 @@
import Logout from "../../../helpers/Logout";
import { StartDriver, StopDriver } from "../../../helpers/context/WithDriver";
import RegisterAndLoginWith2FA from "../../../helpers/behaviors/RegisterAndLoginTwoFactor";
import VerifyForwardedHeaderIs from "../../../helpers/assertions/VerifyForwardedHeaderIs";
import LoginOneFactor from "../../../helpers/behaviors/LoginOneFactor";
export default function() {
describe("Custom-Forwarded-User and Custom-Forwarded-Groups are correctly forwarded to protected backend", function() {
this.timeout(10000);
describe("With single factor", function() {
before(async function() {
this.driver = await StartDriver();
await LoginOneFactor(this.driver, "john", "password", "https://single_factor.example.com:8080/headers");
});
after(async function() {
await Logout(this.driver);
await StopDriver(this.driver);
});
it("should see header 'Custom-Forwarded-User' set to 'john'", async function() {
await VerifyForwardedHeaderIs(this.driver, 'Custom-Forwarded-User', 'john');
});
it("should see header 'Custom-Forwarded-Groups' set to 'dev,admin'", async function() {
await VerifyForwardedHeaderIs(this.driver, 'Custom-Forwarded-Groups', 'dev,admin');
});
});
describe("With two factors", function() {
before(async function() {
this.driver = await StartDriver();
await RegisterAndLoginWith2FA(this.driver, "john", true, "https://public.example.com:8080/headers");
});
after(async function() {
await Logout(this.driver);
await StopDriver(this.driver);
});
it("should see header 'Custom-Forwarded-User' set to 'john'", async function() {
await VerifyForwardedHeaderIs(this.driver, 'Custom-Forwarded-User', 'john');
});
it("should see header 'Custom-Forwarded-Groups' set to 'dev,admin'", async function() {
await VerifyForwardedHeaderIs(this.driver, 'Custom-Forwarded-Groups', 'dev,admin');
});
});
});
}

View File

@ -4,8 +4,8 @@ import FillLoginPageWithUserAndPasswordAndClick from '../../../helpers/FillLogin
import ValidateTotp from "../../../helpers/ValidateTotp"; import ValidateTotp from "../../../helpers/ValidateTotp";
import Logout from "../../../helpers/Logout"; import Logout from "../../../helpers/Logout";
import WaitRedirected from "../../../helpers/WaitRedirected"; import WaitRedirected from "../../../helpers/WaitRedirected";
import IsAlreadyAuthenticatedStage from "../../../helpers/IsAlreadyAuthenticatedStage"; import IsAlreadyAuthenticatedStage from "../../../helpers/assertions/VerifyIsAlreadyAuthenticatedStage";
import WithDriver from "../../../helpers/context/WithDriver"; import { StartDriver, StopDriver } from "../../../helpers/context/WithDriver";
/* /*
* Authelia should not be vulnerable to open redirection. Otherwise it would aid an * Authelia should not be vulnerable to open redirection. Otherwise it would aid an
@ -15,17 +15,18 @@ import WithDriver from "../../../helpers/context/WithDriver";
* the URL is pointing to an external domain. * the URL is pointing to an external domain.
*/ */
export default function() { export default function() {
WithDriver(true);
describe("Only redirection to a subdomain of the protected domain should be allowed", function() { describe("Only redirection to a subdomain of the protected domain should be allowed", function() {
this.timeout(10000); this.timeout(10000);
let secret: string; let secret: string;
beforeEach(async function() { beforeEach(async function() {
this.driver = await StartDriver();
secret = await LoginAndRegisterTotp(this.driver, "john", true) secret = await LoginAndRegisterTotp(this.driver, "john", true)
}); });
afterEach(async function() { afterEach(async function() {
await Logout(this.driver); await Logout(this.driver);
await StopDriver(this.driver);
}) })
function CannotRedirectTo(url: string) { function CannotRedirectTo(url: string) {

View File

@ -16,6 +16,6 @@ export default function() {
const secret = await LoginAndRegisterTotp(this.driver, "john", true); const secret = await LoginAndRegisterTotp(this.driver, "john", true);
child_process.execSync("./scripts/dc-dev.sh restart mongo"); child_process.execSync("./scripts/dc-dev.sh restart mongo");
await FullLogin(this.driver, "https://admin.example.com:8080/secret.html", "john", secret); await FullLogin(this.driver, "john", secret, "https://admin.example.com:8080/secret.html");
}); });
} }

View File

@ -1,4 +1,3 @@
import Bluebird = require("bluebird");
import LoginAndRegisterTotp from "../../../helpers/LoginAndRegisterTotp"; import LoginAndRegisterTotp from "../../../helpers/LoginAndRegisterTotp";
import VisitPage from "../../../helpers/VisitPage"; import VisitPage from "../../../helpers/VisitPage";
import FillLoginPageWithUserAndPasswordAndClick from "../../../helpers/FillLoginPageAndClick"; import FillLoginPageWithUserAndPasswordAndClick from "../../../helpers/FillLoginPageAndClick";

View File

@ -7,7 +7,7 @@ import WaitRedirected from '../../../helpers/WaitRedirected';
import FillField from "../../../helpers/FillField"; import FillField from "../../../helpers/FillField";
import {GetLinkFromEmail} from "../../../helpers/GetIdentityLink"; import {GetLinkFromEmail} from "../../../helpers/GetIdentityLink";
import FillLoginPageAndClick from "../../../helpers/FillLoginPageAndClick"; import FillLoginPageAndClick from "../../../helpers/FillLoginPageAndClick";
import IsSecondFactorStage from "../../../helpers/IsSecondFactorStage"; import IsSecondFactorStage from "../../../helpers/assertions/VerifyIsSecondFactorStage";
import SeeNotification from '../../../helpers/SeeNotification'; import SeeNotification from '../../../helpers/SeeNotification';
export default function() { export default function() {

View File

@ -2,7 +2,7 @@ import FillLoginPageWithUserAndPasswordAndClick from '../../../helpers/FillLogin
import WaitRedirected from '../../../helpers/WaitRedirected'; import WaitRedirected from '../../../helpers/WaitRedirected';
import VisitPage from '../../../helpers/VisitPage'; import VisitPage from '../../../helpers/VisitPage';
import ValidateTotp from '../../../helpers/ValidateTotp'; import ValidateTotp from '../../../helpers/ValidateTotp';
import AccessSecret from "../../../helpers/AccessSecret"; import VerifySecretObserved from "../../../helpers/assertions/VerifySecretObserved";
import LoginAndRegisterTotp from '../../../helpers/LoginAndRegisterTotp'; import LoginAndRegisterTotp from '../../../helpers/LoginAndRegisterTotp';
import SeeNotification from '../../../helpers/SeeNotification'; import SeeNotification from '../../../helpers/SeeNotification';
import { AUTHENTICATION_TOTP_FAILED } from '../../../../shared/UserMessages'; import { AUTHENTICATION_TOTP_FAILED } from '../../../../shared/UserMessages';
@ -25,7 +25,7 @@ export default function() {
}); });
it("should access the secret", async function() { it("should access the secret", async function() {
await AccessSecret(this.driver); await VerifySecretObserved(this.driver);
}); });
}); });