Fix e2e tests for complete configuration.

pull/330/head
Clement Michaud 2019-01-30 22:44:03 +01:00
parent 387187b152
commit d2a547eca6
23 changed files with 254 additions and 127 deletions

View File

@ -0,0 +1,15 @@
import { Dispatch } from "redux";
import * as AutheliaService from '../services/AutheliaService';
export default async function(url: string, dispatch: Dispatch) {
try {
// Check the url against the backend before redirecting.
await AutheliaService.checkRedirection(url);
window.location.href = url;
} catch (e) {
console.error(
'Cannot redirect since the URL is not in the protected domain.' +
'This behavior could be malicious so please the issue to an administrator.');
throw e;
}
}

View File

@ -9,6 +9,7 @@ import * as AutheliaService from '../../../services/AutheliaService';
import { push } from 'connected-react-router'; import { push } from 'connected-react-router';
import fetchState from '../../../behaviors/FetchStateBehavior'; import fetchState from '../../../behaviors/FetchStateBehavior';
import LogoutBehavior from '../../../behaviors/LogoutBehavior'; import LogoutBehavior from '../../../behaviors/LogoutBehavior';
import SafelyRedirectBehavior from '../../../behaviors/SafelyRedirectBehavior';
const mapStateToProps = (state: RootState): StateProps => ({ const mapStateToProps = (state: RootState): StateProps => ({
securityKeySupported: state.secondFactor.securityKeySupported, securityKeySupported: state.secondFactor.securityKeySupported,
@ -52,19 +53,23 @@ async function triggerSecurityKeySigning(dispatch: Dispatch) {
await dispatch(securityKeySignSuccess()); await dispatch(securityKeySignSuccess());
} }
function redirectOnSuccess(dispatch: Dispatch, ownProps: OwnProps, duration?: number) { async function handleSuccess(dispatch: Dispatch, ownProps: OwnProps, duration?: number) {
function redirect() { async function handle() {
if (ownProps.redirection) { if (ownProps.redirection) {
window.location.href = ownProps.redirection; try {
await SafelyRedirectBehavior(ownProps.redirection, dispatch);
} catch (e) {
await fetchState(dispatch);
}
} else { } else {
fetchState(dispatch); await fetchState(dispatch);
} }
} }
if (duration) { if (duration) {
setTimeout(redirect, duration); setTimeout(handle, duration);
} else { } else {
redirect(); await handle();
} }
} }
@ -84,7 +89,7 @@ const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps) => {
if (isU2FSupported) { if (isU2FSupported) {
await dispatch(setSecurityKeySupported(true)); await dispatch(setSecurityKeySupported(true));
await triggerSecurityKeySigning(dispatch); await triggerSecurityKeySigning(dispatch);
redirectOnSuccess(dispatch, ownProps, 1000); await handleSuccess(dispatch, ownProps, 1000);
} }
}, },
onOneTimePasswordValidationRequested: async (token: string) => { onOneTimePasswordValidationRequested: async (token: string) => {
@ -106,7 +111,7 @@ const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps) => {
throw body['error']; throw body['error'];
} }
dispatch(oneTimePasswordVerificationSuccess()); dispatch(oneTimePasswordVerificationSuccess());
redirectOnSuccess(dispatch, ownProps); await handleSuccess(dispatch, ownProps);
}, },
} }
} }

View File

@ -1,10 +1,10 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import AuthenticationView, {StateProps, Stage, DispatchProps} from '../../../views/AuthenticationView/AuthenticationView'; import QueryString from 'query-string';
import AuthenticationView, {StateProps, Stage, DispatchProps, OwnProps} from '../../../views/AuthenticationView/AuthenticationView';
import { RootState } from '../../../reducers'; import { RootState } from '../../../reducers';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import AuthenticationLevel from '../../../types/AuthenticationLevel'; import AuthenticationLevel from '../../../types/AuthenticationLevel';
import FetchStateBehavior from '../../../behaviors/FetchStateBehavior'; import FetchStateBehavior from '../../../behaviors/FetchStateBehavior';
import { setRedirectionUrl } from '../../../reducers/Portal/Authentication/actions';
function authenticationLevelToStage(level: AuthenticationLevel): Stage { function authenticationLevelToStage(level: AuthenticationLevel): Stage {
switch (level) { switch (level) {
@ -17,12 +17,21 @@ function authenticationLevelToStage(level: AuthenticationLevel): Stage {
} }
} }
const mapStateToProps = (state: RootState): StateProps => { const mapStateToProps = (state: RootState, ownProps: OwnProps): StateProps => {
const stage = (state.authentication.remoteState) const stage = (state.authentication.remoteState)
? authenticationLevelToStage(state.authentication.remoteState.authentication_level) ? authenticationLevelToStage(state.authentication.remoteState.authentication_level)
: Stage.FIRST_FACTOR; : Stage.FIRST_FACTOR;
let url: string | null = null;
if (ownProps.location) {
const params = QueryString.parse(ownProps.location.search);
if ('rd' in params) {
url = params['rd'] as string;
}
}
return { return {
redirectionUrl: state.authentication.redirectionUrl, redirectionUrl: url,
remoteState: state.authentication.remoteState, remoteState: state.authentication.remoteState,
stage: stage, stage: stage,
}; };
@ -30,11 +39,8 @@ const mapStateToProps = (state: RootState): StateProps => {
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => { const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => {
return { return {
onInit: async (redirectionUrl?: string) => { onInit: async () => {
await FetchStateBehavior(dispatch); await FetchStateBehavior(dispatch);
if (redirectionUrl) {
await dispatch(setRedirectionUrl(redirectionUrl));
}
} }
} }
} }

View File

@ -114,4 +114,24 @@ export async function resetPassword(newPassword: string) {
}, },
body: JSON.stringify({password: newPassword}) body: JSON.stringify({password: newPassword})
}); });
}
export async function checkRedirection(url: string) {
const res = await fetch('/api/redirect', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({url})
})
if (res.status !== 200) {
throw new Error('Status code ' + res.status);
}
const text = await res.text();
if (text !== 'OK') {
throw new Error('Cannot redirect');
}
return;
} }

View File

@ -3,8 +3,7 @@ import AlreadyAuthenticated from "../../containers/components/AlreadyAuthenticat
import FirstFactorForm from "../../containers/components/FirstFactorForm/FirstFactorForm"; import FirstFactorForm from "../../containers/components/FirstFactorForm/FirstFactorForm";
import SecondFactorForm from "../../containers/components/SecondFactorForm/SecondFactorForm"; import SecondFactorForm from "../../containers/components/SecondFactorForm/SecondFactorForm";
import RemoteState from "./RemoteState"; import RemoteState from "./RemoteState";
import { RouterProps } from "react-router"; import { RouterProps, RouteProps } from "react-router";
import queryString from 'query-string';
export enum Stage { export enum Stage {
FIRST_FACTOR, FIRST_FACTOR,
@ -12,6 +11,8 @@ export enum Stage {
ALREADY_AUTHENTICATED, ALREADY_AUTHENTICATED,
} }
export interface OwnProps extends RouteProps {}
export interface StateProps { export interface StateProps {
stage: Stage; stage: Stage;
remoteState: RemoteState | null; remoteState: RemoteState | null;
@ -19,19 +20,13 @@ export interface StateProps {
} }
export interface DispatchProps { export interface DispatchProps {
onInit: (redirectionUrl?: string) => void; onInit: () => void;
} }
export type Props = StateProps & DispatchProps & RouterProps; export type Props = StateProps & DispatchProps & RouterProps;
class AuthenticationView extends Component<Props> { class AuthenticationView extends Component<Props> {
componentDidMount() { componentWillMount() {
if (this.props.history.location) {
const params = queryString.parse(this.props.history.location.search);
if ('rd' in params) {
this.props.onInit(params['rd'] as string);
}
}
this.props.onInit(); this.props.onInit();
} }

View File

@ -4,6 +4,8 @@
port: 9091 port: 9091
log_level: debug
authentication_backend: authentication_backend:
file: file:
path: ./users_database.yml path: ./users_database.yml

24
package-lock.json generated
View File

@ -532,6 +532,11 @@
"integrity": "sha512-vOVmaruQG5EatOU/jM6yU2uCp3Lz6mK1P5Ztu4iJjfM4SVHU9XYktPUQtKlIXuahqXHdEyUarMrBEwg5Cwu+bA==", "integrity": "sha512-vOVmaruQG5EatOU/jM6yU2uCp3Lz6mK1P5Ztu4iJjfM4SVHU9XYktPUQtKlIXuahqXHdEyUarMrBEwg5Cwu+bA==",
"dev": true "dev": true
}, },
"@types/url-parse": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.2.tgz",
"integrity": "sha512-Z25Ef2iI0lkiBAoe8qATRHQWy+BWFWuWasD6HB5prsWx2QjLmB/ngXv8v9dePw2jDwdSvET4/6J5HxmeqhslmQ=="
},
"@types/winston": { "@types/winston": {
"version": "2.3.9", "version": "2.3.9",
"resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.3.9.tgz", "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.3.9.tgz",
@ -7053,6 +7058,11 @@
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
"dev": true "dev": true
}, },
"querystringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz",
"integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg=="
},
"random-bytes": { "random-bytes": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
@ -7424,6 +7434,11 @@
"semver": "5.5.0" "semver": "5.5.0"
} }
}, },
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"resolve": { "resolve": {
"version": "1.7.1", "version": "1.7.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz",
@ -8924,6 +8939,15 @@
} }
} }
}, },
"url-parse": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz",
"integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==",
"requires": {
"querystringify": "2.1.0",
"requires-port": "1.0.0"
}
},
"url-parse-lax": { "url-parse-lax": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",

View File

@ -27,6 +27,7 @@
"title": "Authelia API documentation" "title": "Authelia API documentation"
}, },
"dependencies": { "dependencies": {
"@types/url-parse": "^1.4.2",
"ajv": "^6.3.0", "ajv": "^6.3.0",
"bluebird": "^3.5.0", "bluebird": "^3.5.0",
"body-parser": "^1.15.2", "body-parser": "^1.15.2",
@ -50,6 +51,7 @@
"speakeasy": "^2.0.0", "speakeasy": "^2.0.0",
"u2f": "^0.1.2", "u2f": "^0.1.2",
"u2f-api": "^1.0.7", "u2f-api": "^1.0.7",
"url-parse": "^1.4.4",
"winston": "^2.3.1", "winston": "^2.3.1",
"yamljs": "^0.3.0" "yamljs": "^0.3.0"
}, },

View File

@ -0,0 +1,31 @@
import * as Express from "express";
import { ServerVariables } from "../../ServerVariables";
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import { Level } from "../../authentication/Level";
import * as URLParse from "url-parse";
export default function (vars: ServerVariables) {
return function (req: Express.Request, res: Express.Response) {
if (!req.body.url) {
res.status(400);
vars.logger.error(req, "Provide url for verification to be done.");
return;
}
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
const url = new URLParse(req.body.url);
const urlInDomain = url.hostname.endsWith(vars.config.session.domain);
vars.logger.debug(req, "Check domain %s is in url %s.", vars.config.session.domain, url.hostname);
const sufficientPermissions = authSession.authentication_level >= Level.TWO_FACTOR;
vars.logger.debug(req, "Check that protocol %s is HTTPS.", url.protocol);
const protocolIsHttps = url.protocol === "https:";
if (sufficientPermissions && urlInDomain && protocolIsHttps) {
res.send("OK");
return;
}
res.send("NOK");
};
}

View File

@ -3,6 +3,7 @@ import Express = require("express");
import FirstFactorPost = require("../routes/firstfactor/post"); import FirstFactorPost = require("../routes/firstfactor/post");
import LogoutPost from "../routes/logout/post"; import LogoutPost from "../routes/logout/post";
import StateGet from "../routes/state/get"; import StateGet from "../routes/state/get";
import RedirectPost from "../routes/redirect/post";
import VerifyGet = require("../routes/verify/get"); import VerifyGet = require("../routes/verify/get");
import TOTPSignGet = require("../routes/secondfactor/totp/sign/post"); import TOTPSignGet = require("../routes/secondfactor/totp/sign/post");
@ -86,6 +87,7 @@ function setupResetPassword(app: Express.Application, vars: ServerVariables) {
export class RestApi { export class RestApi {
static setup(app: Express.Application, vars: ServerVariables): void { static setup(app: Express.Application, vars: ServerVariables): void {
app.get(Endpoints.STATE_GET, StateGet(vars)); app.get(Endpoints.STATE_GET, StateGet(vars));
app.post(Endpoints.REDIRECT_POST, RedirectPost(vars));
app.post(Endpoints.LOGOUT_POST, LogoutPost(vars)); app.post(Endpoints.LOGOUT_POST, LogoutPost(vars));

View File

@ -287,3 +287,17 @@ export const VERIFY_GET = "/api/verify";
*/ */
export const LOGOUT_POST = "/api/logout"; export const LOGOUT_POST = "/api/logout";
/**
* @api {post} /api/redirect Url redirection checking endpoint
* @apiName Redirect
* @apiGroup Authentication
* @apiVersion 1.0.0
* @apiDescription Check if the user can be redirected to the url provided.
* The level of permissions for this user are checked and the url must be
* in the domain protected by authelia.
*
* @apiSuccess (Success 200)
*
* @apiDescription Resets the session to logout the user.
*/
export const REDIRECT_POST = "/api/redirect";

View File

@ -1,28 +0,0 @@
require("chromedriver");
import Environment = require('../environment');
const includes = [
"docker-compose.yml",
"example/compose/docker-compose.base.yml",
"example/compose/mongo/docker-compose.yml",
"example/compose/redis/docker-compose.yml",
"example/compose/nginx/backend/docker-compose.yml",
"example/compose/nginx/portal/docker-compose.yml",
"example/compose/smtp/docker-compose.yml",
"example/compose/httpbin/docker-compose.yml",
"example/compose/ldap/docker-compose.yml"
];
before(function() {
this.timeout(20000);
this.environment = new Environment.Environment(includes);
return this.environment.setup(5000);
});
after(function() {
this.timeout(30000);
if(process.env.KEEP_ENV != "true") {
return this.environment.cleanup();
}
});

View File

@ -1,41 +0,0 @@
import WithDriver from "../helpers/context/WithDriver";
import LoginAndRegisterTotp from "../helpers/LoginAndRegisterTotp";
import SeeNotification from "../helpers/SeeNotification";
import VisitPage from "../helpers/VisitPage";
import FillLoginPageWithUserAndPasswordAndClick from '../helpers/FillLoginPageAndClick';
import ValidateTotp from "../helpers/ValidateTotp";
import {CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN} from '../../shared/UserMessages';
/*
* Authelia should not be vulnerable to open redirection. Otherwise it would aid an
* attacker in conducting a phishing attack.
*
* To avoid the issue, Authelia's client scans the URL and prevent any redirection if
* the URL is pointing to an external domain.
*/
describe("Redirection should be performed only if in domain", function() {
this.timeout(10000);
WithDriver();
before(function() {
const that = this;
return LoginAndRegisterTotp(this.driver, "john", true)
.then((secret: string) => that.secret = secret)
});
function DoNotRedirect(url: string) {
it(`should see an error message instead of redirecting to ${url}`, function() {
const driver = this.driver;
const secret = this.secret;
return VisitPage(driver, `https://login.example.com:8080/?rd=${url}`)
.then(() => FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password'))
.then(() => ValidateTotp(driver, secret))
.then(() => SeeNotification(driver, "error", CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN))
.then(() => driver.get(`https://login.example.com:8080/logout`));
});
}
DoNotRedirect("www.google.fr");
DoNotRedirect("http://www.google.fr");
DoNotRedirect("https://www.google.fr");
})

View File

@ -1,17 +0,0 @@
import WithDriver from '../helpers/context/WithDriver';
import fullLogin from '../helpers/FullLogin';
import loginAndRegisterTotp from '../helpers/LoginAndRegisterTotp';
describe("Connection retry when mongo fails or restarts", function() {
this.timeout(30000);
WithDriver();
it("should be able to login after mongo restarts", function() {
const that = this;
let secret;
return loginAndRegisterTotp(that.driver, "john", true)
.then(_secret => secret = _secret)
.then(() => that.environment.restart_service("mongo", 1000))
.then(() => fullLogin(that.driver, "https://admin.example.com:8080/secret.html", "john", secret));
})
});

View File

@ -0,0 +1,5 @@
import SeleniumWebDriver, { WebDriver } from "selenium-webdriver";
export default async function(driver: WebDriver) {
await driver.wait(SeleniumWebDriver.until.elementLocated(SeleniumWebDriver.By.className('already-authenticated-step')));
}

View File

@ -0,0 +1,5 @@
import { WebDriver } from "selenium-webdriver";
export default async function(driver: WebDriver) {
await driver.get(`https://login.example.com:8080/logout`);
}

View File

@ -4,15 +4,15 @@ import WithDriver from "./WithDriver";
let running = false; let running = false;
interface AutheliaSuiteType { interface AutheliaSuiteType {
(description: string, cb: (this: Mocha.ISuiteCallbackContext) => void): Mocha.ISuite; (description: string, configPath: string, cb: (this: Mocha.ISuiteCallbackContext) => void): Mocha.ISuite;
only: (description: string, cb: (this: Mocha.ISuiteCallbackContext) => void) => Mocha.ISuite; only: (description: string, configPath: string, cb: (this: Mocha.ISuiteCallbackContext) => void) => Mocha.ISuite;
} }
function AutheliaSuiteBase(description: string, function AutheliaSuiteBase(description: string, configPath: string,
context: (description: string, ctx: (this: Mocha.ISuiteCallbackContext) => void) => Mocha.ISuite, cb: (this: Mocha.ISuiteCallbackContext) => void,
cb: (this: Mocha.ISuiteCallbackContext) => void) { context: (description: string, ctx: (this: Mocha.ISuiteCallbackContext) => void) => Mocha.ISuite) {
if (!running && process.env['WITH_SERVER'] == 'y') { if (!running && process.env['WITH_SERVER'] == 'y') {
WithAutheliaRunning(); WithAutheliaRunning(configPath);
running = true; running = true;
} }
@ -22,13 +22,16 @@ function AutheliaSuiteBase(description: string,
}); });
} }
const AutheliaSuite = <AutheliaSuiteType>function(description: string, cb: (this: Mocha.ISuiteCallbackContext) => void) { const AutheliaSuite = <AutheliaSuiteType>function(
return AutheliaSuiteBase(description, describe, cb); description: string, configPath: string,
cb: (this: Mocha.ISuiteCallbackContext) => void) {
return AutheliaSuiteBase(description, configPath, cb, describe);
} }
AutheliaSuite.only = function(description: string, cb: (this: Mocha.ISuiteCallbackContext) => void) { AutheliaSuite.only = function(description: string, configPath: string,
return AutheliaSuiteBase(description, describe.only, cb); cb: (this: Mocha.ISuiteCallbackContext) => void) {
return AutheliaSuiteBase(description, configPath, cb, describe.only);
} }
export default AutheliaSuite as AutheliaSuiteType; export default AutheliaSuite as AutheliaSuiteType;

View File

@ -1,12 +1,12 @@
import ChildProcess from 'child_process'; import ChildProcess from 'child_process';
export default function WithAutheliaRunning(waitTimeout: number = 3000) { export default function WithAutheliaRunning(configPath: string, waitTimeout: number = 3000) {
before(function() { before(function() {
this.timeout(5000); this.timeout(5000);
const authelia = ChildProcess.spawn( const authelia = ChildProcess.spawn(
'./scripts/authelia-scripts', './scripts/authelia-scripts',
['serve', '--no-watch', '--config', 'config.minimal.yml'], ['serve', '--no-watch', '--config', configPath],
{detached: true}); {detached: true});
this.authelia = authelia; this.authelia = authelia;

View File

@ -8,8 +8,7 @@ export default function() {
beforeEach(function() { beforeEach(function() {
const driver = new SeleniumWebdriver.Builder() const driver = new SeleniumWebdriver.Builder()
.forBrowser("chrome") .forBrowser("chrome")
.setChromeOptions( .setChromeOptions(new chrome.Options().headless())
new chrome.Options().headless())
.build(); .build();
this.driver = driver; this.driver = driver;
}); });

View File

@ -0,0 +1,10 @@
import AutheliaSuite from "../../helpers/context/AutheliaSuite";
import MongoConnectionRecovery from "./scenarii/MongoConnectionRecovery";
import EnforceInternalRedirectionsOnly from "./scenarii/EnforceInternalRedirectionsOnly";
AutheliaSuite('Complete configuration', 'config.template.yml', function() {
this.timeout(10000);
describe('Mongo broken connection recovery', MongoConnectionRecovery);
describe('Enforce internal redirections only', EnforceInternalRedirectionsOnly);
});

View File

@ -0,0 +1,63 @@
import LoginAndRegisterTotp from "../../../helpers/LoginAndRegisterTotp";
import VisitPage from "../../../helpers/VisitPage";
import FillLoginPageWithUserAndPasswordAndClick from '../../../helpers/FillLoginPageAndClick';
import ValidateTotp from "../../../helpers/ValidateTotp";
import Logout from "../../../helpers/Logout";
import WaitRedirected from "../../../helpers/WaitRedirected";
import IsAlreadyAuthenticatedStage from "../../../helpers/IsAlreadyAuthenticatedStage";
/*
* Authelia should not be vulnerable to open redirection. Otherwise it would aid an
* attacker in conducting a phishing attack.
*
* To avoid the issue, Authelia's client scans the URL and prevent any redirection if
* the URL is pointing to an external domain.
*/
export default function() {
describe("Only redirection to a subdomain of the protected domain should be allowed", function() {
this.timeout(10000);
let secret: string;
beforeEach(async function() {
secret = await LoginAndRegisterTotp(this.driver, "john", true)
});
afterEach(async function() {
await Logout(this.driver);
})
function CannotRedirectTo(url: string) {
it(`should redirect to already authenticated page when requesting ${url}`, async function() {
await VisitPage(this.driver, `https://login.example.com:8080/?rd=${url}`);
await FillLoginPageWithUserAndPasswordAndClick(this.driver, 'john', 'password');
await ValidateTotp(this.driver, secret);
await IsAlreadyAuthenticatedStage(this.driver);
});
}
function CanRedirectTo(url: string) {
it(`should redirect to ${url}`, async function() {
await VisitPage(this.driver, `https://login.example.com:8080/?rd=${url}`);
await FillLoginPageWithUserAndPasswordAndClick(this.driver, 'john', 'password');
await ValidateTotp(this.driver, secret);
await WaitRedirected(this.driver, url);
});
}
describe('blocked redirection', function() {
// Do not redirect to another domain than example.com
CannotRedirectTo("https://www.google.fr");
// Do not redirect to rogue domain
CannotRedirectTo("https://public.example.com.a:8080");
// Do not redirect to http website
CannotRedirectTo("http://public.example.com:8080");
});
describe('allowed redirection', function() {
// Can redirect to any subdomain of the domain protected by Authelia.
CanRedirectTo("https://public.example.com:8080/");
});
});
}

View File

@ -0,0 +1,12 @@
import LoginAndRegisterTotp from "../../../helpers/LoginAndRegisterTotp";
import FullLogin from "../../../helpers/FullLogin";
import child_process from 'child_process';
export default function() {
it("should be able to login after mongo restarts", async function() {
this.timeout(30000);
const secret = await LoginAndRegisterTotp(this.driver, "john", true);
child_process.execSync("./scripts/dc-dev.sh restart mongo");
await FullLogin(this.driver, "https://admin.example.com:8080/secret.html", "john", secret);
});
}

View File

@ -9,7 +9,7 @@ import TOTPValidation from './scenarii/TOTPValidation';
const execAsync = Bluebird.promisify(ChildProcess.exec); const execAsync = Bluebird.promisify(ChildProcess.exec);
AutheliaSuite('Minimal configuration', function() { AutheliaSuite('Minimal configuration', 'config.minimal.yml', function() {
this.timeout(10000); this.timeout(10000);
beforeEach(function() { beforeEach(function() {
return execAsync("cp users_database.example.yml users_database.yml"); return execAsync("cp users_database.example.yml users_database.yml");