mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Migrate more Cucumber tests into Mocha.
This commit is contained in:
parent
efceb66ffa
commit
c579355c5b
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -36,3 +36,5 @@ example/ldap/private.ldif
|
||||||
Configuration.schema.json
|
Configuration.schema.json
|
||||||
|
|
||||||
users_database.test.yml
|
users_database.test.yml
|
||||||
|
|
||||||
|
.suite
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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());
|
dispatch(authenticateSuccess());
|
||||||
|
|
||||||
// fetch state
|
// fetch state to move to next stage
|
||||||
FetchStateBehavior(dispatch);
|
FetchStateBehavior(dispatch);
|
||||||
|
} else {
|
||||||
|
dispatch(authenticateFailure('Unknown error'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => {
|
const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps) => {
|
||||||
return {
|
return {
|
||||||
onAuthenticationRequested: onAuthenticationRequested(dispatch),
|
onAuthenticationRequested: onAuthenticationRequested(dispatch, ownProps.redirectionUrl),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
@ -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) {
|
||||||
return fetchSafe('/api/firstfactor', {
|
|
||||||
method: 'POST',
|
const headers: Record<string, string> = {
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
}
|
||||||
|
|
||||||
|
if (redirectionUrl) {
|
||||||
|
headers['X-Target-Url'] = redirectionUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetchSafe('/api/firstfactor', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
|
|
@ -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} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
cmd.stderr.pipe(process.stderr);
|
||||||
|
cmd.on('close', (code) => {
|
||||||
|
if (code == 0) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(new Error('Status code ' + code));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
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('add', reload);
|
||||||
tsWatcher.on('remove', reload);
|
tsWatcher.on('remove', reload);
|
||||||
tsWatcher.on('change', reload);
|
tsWatcher.on('change', reload);
|
||||||
|
|
||||||
startServer();
|
startServer();
|
||||||
startClient();
|
startClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"
|
|
|
@ -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.');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
||||||
};
|
};
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
13
test/helpers/assertions/VerifyForwardedHeaderIs.ts
Normal file
13
test/helpers/assertions/VerifyForwardedHeaderIs.ts
Normal 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.");
|
||||||
|
}
|
||||||
|
}
|
5
test/helpers/assertions/VerifyUrlIs.ts
Normal file
5
test/helpers/assertions/VerifyUrlIs.ts
Normal 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);
|
||||||
|
}
|
17
test/helpers/behaviors/LoginOneFactor.ts
Normal file
17
test/helpers/behaviors/LoginOneFactor.ts
Normal 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);
|
||||||
|
};
|
13
test/helpers/behaviors/RegisterAndLoginTwoFactor.ts
Normal file
13
test/helpers/behaviors/RegisterAndLoginTwoFactor.ts
Normal 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);
|
||||||
|
};
|
|
@ -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();
|
||||||
this.driver = driver;
|
return 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)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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/');
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
51
test/suites/complete/scenarii/CustomHeadersForwarded.ts
Normal file
51
test/suites/complete/scenarii/CustomHeadersForwarded.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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");
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -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";
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user