Finish migrating integration tests to mocha.

pull/330/head
Clement Michaud 2019-02-14 00:27:43 +01:00
parent 85d3adc3e3
commit 50d4ab1368
39 changed files with 131 additions and 886 deletions

View File

@ -1,34 +0,0 @@
Feature: User is redirected when factors are already validated
@need-registered-user-john
Scenario: User has validated first factor and tries to access service protected by second factor. He is then redirect to second factor step.
When I visit "https://single_factor.example.com:8080/secret.html"
And I'm redirected to "https://login.example.com:8080/?rd=https://single_factor.example.com:8080/secret.html"
And I login with user "john" and password "password"
And I'm redirected to "https://single_factor.example.com:8080/secret.html"
And I visit "https://public.example.com:8080/secret.html"
Then I'm redirected to "https://login.example.com:8080/secondfactor?rd=https://public.example.com:8080/secret.html"
@need-registered-user-john
Scenario: User who has validated second factor and access auth portal should be redirected to "Already logged in page" and redirected to default URL declared in configuration
When I visit "https://public.example.com:8080/secret.html"
And I'm redirected to "https://login.example.com:8080/?rd=https://public.example.com:8080/secret.html"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "Sign in"
And I'm redirected to "https://public.example.com:8080/secret.html"
And I visit "https://login.example.com:8080"
Then I'm redirected to "https://login.example.com:8080/loggedin"
And I sleep for 5 seconds
And I'm redirected to "https://home.example.com:8080/"
@need-registered-user-john
Scenario: User who has validated second factor and access auth portal with rediction param should be redirected to that URL
When I visit "https://public.example.com:8080/secret.html"
And I'm redirected to "https://login.example.com:8080/?rd=https://public.example.com:8080/secret.html"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "Sign in"
And I'm redirected to "https://public.example.com:8080/secret.html"
And I visit "https://login.example.com:8080?rd=https://public.example.com:8080/secret.html"
Then I'm redirected to "https://public.example.com:8080/secret.html"

View File

@ -1,5 +0,0 @@
Feature: Authentication scenarii
Scenario: Logout redirects user to redirect URL given in parameter
When I visit "https://login.example.com:8080/logout?rd=https://home.example.com:8080/"
Then I'm redirected to "https://home.example.com:8080/"

View File

@ -1,71 +0,0 @@
Feature: User is correctly redirected
Scenario: User is redirected to authelia when he is not authenticated
When I visit "https://public.example.com:8080"
Then I'm redirected to "https://login.example.com:8080/?rd=https://public.example.com:8080/"
@need-registered-user-john
Scenario: User is redirected to home page after several authentication tries
When I visit "https://public.example.com:8080/secret.html"
And I login with user "john" and password "badpassword"
And I wait for notification to disappear
And I clear field "username"
And I clear field "password"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "Sign in"
Then I'm redirected to "https://public.example.com:8080/secret.html"
Scenario: User Harry does not have access to admin domain and thus he must get an error 403
When I register TOTP and login with user "harry" and password "password"
And I visit "https://admin.example.com:8080/secret.html"
Then I get an error 403
Scenario: Redirection URL is propagated from restricted page to first factor
When I visit "https://public.example.com:8080/secret.html"
Then I'm redirected to "https://login.example.com:8080/?rd=https://public.example.com:8080/secret.html"
Scenario: Redirection URL is propagated from first factor to second factor
Given I visit "https://login.example.com:8080/"
And I login with user "john" and password "password"
And I register a TOTP secret called "Sec0"
When I visit "https://public.example.com:8080/secret.html"
And I login with user "john" and password "password"
Then I'm redirected to "https://login.example.com:8080/secondfactor?rd=https://public.example.com:8080/secret.html"
Scenario: Redirection URL is used to send user from second factor to target page
Given I visit "https://login.example.com:8080/"
And I login with user "john" and password "password"
And I register a TOTP secret called "Sec0"
When I visit "https://public.example.com:8080/secret.html"
And I login with user "john" and password "password"
And I use "Sec0" as TOTP token handle
And I click on "Sign in"
Then I'm redirected to "https://public.example.com:8080/secret.html"
@need-registered-user-john
Scenario: User is redirected to default URL defined in configuration when authentication is successful
When I visit "https://login.example.com:8080"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "Sign in"
Then I'm redirected to "https://home.example.com:8080/"
Scenario: User is redirected when hitting an error 401
When I visit "https://login.example.com:8080/secondfactor/u2f/identity/finish"
Then I'm redirected to "https://login.example.com:8080/error/401"
And I sleep for 5 seconds
And I'm redirected to "https://home.example.com:8080/"
@need-registered-user-harry
Scenario: User is redirected when hitting an error 403
When I visit "https://login.example.com:8080"
And I login with user "harry" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "Sign in"
And I'm redirected to "https://home.example.com:8080/"
When I visit "https://admin.example.com:8080/secret.html"
Then I'm redirected to "https://login.example.com:8080/error/403"
And I sleep for 5 seconds
And I'm redirected to "https://home.example.com:8080/"

View File

@ -1,17 +0,0 @@
import {Then} from "cucumber";
Then("I have access to {string}", function(url: string) {
const that = this;
return this.driver.get(url)
.then(function () {
return that.waitUntilUrlContains(url);
});
});
Then("I have no access to {string}", function(url: string) {
const that = this;
return this.driver.get(url)
.then(function () {
return that.getErrorPage(403);
});
});

View File

@ -1,44 +0,0 @@
import {Before, When, Then} from "cucumber";
import seleniumWebdriver = require("selenium-webdriver");
import Assert = require("assert");
import Request = require("request-promise");
import Bluebird = require("bluebird");
When("I query {string}", function (url: string) {
const that = this;
return Request(url, { followRedirect: false })
.then(function(response) {
that.response = response;
})
.catch(function(err: Error) {
that.error = err;
})
});
Then("I get error code 401", function() {
const that = this;
return new Bluebird(function(resolve, reject) {
if(that.error && that.error.statusCode == 401) {
resolve();
}
else {
if(that.response)
reject(new Error("No error thrown"));
else if(that.error.statusCode != 401)
reject(new Error(`Error code (${that.error.statusCode}) != 401`));
}
});
});
Then("I get redirected to {string}", function(url: string) {
const that = this;
return new Bluebird(function(resolve, reject) {
if(that.error && that.error.statusCode == 302
&& that.error.message.indexOf(url) > -1) {
resolve();
}
else {
reject(new Error("Not redirected"));
}
});
});

View File

@ -1,99 +0,0 @@
import {Given, When, Then, TableDefinition} from "cucumber";
import seleniumWebdriver = require("selenium-webdriver");
import Assert = require("assert");
import Fs = require("fs");
import Speakeasy = require("speakeasy");
import CustomWorld = require("../support/world");
import BluebirdPromise = require("bluebird");
import Request = require("request-promise");
When(/^I visit "(https:\/\/[a-zA-Z0-9:%&._\/=?-]+)"$/, function (link: string) {
return this.visit(link);
});
When("I wait for notification to disappear", function () {
const that = this;
const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification"));
return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 15000)
.then(function () {
return that.driver.wait(seleniumWebdriver.until.elementIsNotVisible(notificationEl), 15000);
})
})
When("I set field {string} to {string}", function (fieldName: string, content: string) {
return this.setFieldTo(fieldName, content);
});
When("I clear field {string}", function (fieldName: string) {
return this.clearField(fieldName);
});
When("I click on {string}", function (text: string) {
return this.clickOnButton(text);
});
Given("I login with user {string} and password {string}",
function (username: string, password: string) {
return this.loginWithUserPassword(username, password);
});
Given("I login with user {string} and password {string} \
and I use TOTP token handle {string}",
function (username: string, password: string, totpTokenHandle: string) {
const that = this;
return this.loginWithUserPassword(username, password)
.then(function () {
return that.useTotpTokenHandle(totpTokenHandle);
});
});
Given("I register a TOTP secret called {string}", function (handle: string) {
return this.registerTotpSecret(handle);
});
Given("I use {string} as TOTP token", function (token: string) {
return this.useTotpToken(token);
});
Given("I use {string} as TOTP token handle", function (handle) {
return this.useTotpTokenHandle(handle);
});
When("I visit {string} and get redirected {string}",
function (url: string, redirectUrl: string) {
const that = this;
return this.driver.get(url)
.then(function () {
return that.driver.wait(seleniumWebdriver.until.urlIs(redirectUrl), 5000);
});
});
Given("I register TOTP and login with user {string} and password {string}",
function (username: string, password: string) {
return this.registerTotpAndSignin(username, password);
});
function endpointReplyWith(context: any, link: string, method: string,
returnCode: number) {
return Request(link, {
method: method
})
.then(function (response: string) {
Assert(response.indexOf("Error " + returnCode) >= 0);
return BluebirdPromise.resolve();
}, function (response: any) {
Assert.equal(response.statusCode, returnCode);
return BluebirdPromise.resolve();
});
}
Then("the following endpoints reply with:", function (dataTable: TableDefinition) {
const promises = [];
for (let i = 0; i < dataTable.rows().length; i++) {
const url: string = (dataTable.hashes() as any)[i].url;
const method: string = (dataTable.hashes() as any)[i].method;
const code: number = (dataTable.hashes() as any)[i].code;
promises.push(endpointReplyWith(this, url, method, code));
}
return BluebirdPromise.all(promises);
});

View File

@ -1,19 +0,0 @@
import {Then} from "cucumber";
import seleniumWebdriver = require("selenium-webdriver");
import CustomWorld = require("../support/world");
import Util = require("util");
import Bluebird = require("bluebird");
import Request = require("request-promise");
Then("I see header {string} set to {string}",
{ timeout: 5000 },
function (expectedHeaderName: string, expectedValue: string) {
return this.driver.findElement(seleniumWebdriver.By.tagName("body")).getText()
.then(function (txt: string) {
const expectedLine = Util.format("\"%s\": \"%s\"", expectedHeaderName, expectedValue);
if (txt.indexOf(expectedLine) > 0)
return Bluebird.resolve();
else
return Bluebird.reject(new Error(Util.format("No such header or with unexpected value.")));
});
})

View File

@ -1,173 +0,0 @@
import {setDefaultTimeout, After, Before, BeforeAll, AfterAll} from "cucumber";
import fs = require("fs");
import BluebirdPromise = require("bluebird");
import ChildProcess = require("child_process");
import { UserDataStore } from "../../../server/src/lib/storage/UserDataStore";
import { CollectionFactoryFactory } from "../../../server/src/lib/storage/CollectionFactoryFactory";
import { IMongoClient } from "../../../server/src/lib/connectors/mongo/IMongoClient";
import { TotpHandler } from "../../../server/src/lib/authentication/totp/TotpHandler";
import Speakeasy = require("speakeasy");
import Request = require("request-promise");
import { TOTPSecret } from "../../../server/types/TOTPSecret";
import Environment = require("../../environment");
import { MongoClient } from "../../../server/src/lib/connectors/mongo/MongoClient";
import { GlobalLogger } from "../../../server/src/lib/logging/GlobalLogger";
import { GlobalLoggerStub } from "../../../server/src/lib/logging/GlobalLoggerStub.spec";
setDefaultTimeout(30 * 1000);
const exec = BluebirdPromise.promisify<any, any>(ChildProcess.exec);
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"
]
const environment = new Environment.Environment(includes);
BeforeAll(function() {
return environment.setup(10000);
});
AfterAll(function() {
return environment.cleanup()
});
Before(function () {
this.jar = Request.jar();
})
After(function () {
return this.driver.quit();
});
function createRegulationConfiguration(): BluebirdPromise<void> {
return exec("\
cat config.template.yml | \
sed 's/find_time: [0-9]\\+/find_time: 15/' | \
sed 's/ban_time: [0-9]\\+/ban_time: 4/' > config.test.yml \
");
}
function createInactivityConfiguration(): BluebirdPromise<void> {
return exec("\
cat config.template.yml | \
sed 's/expiration: [0-9]\\+/expiration: 10000/' | \
sed 's/inactivity: [0-9]\\+/inactivity: 5000/' > config.test.yml \
");
}
function createSingleFactorConfiguration(): BluebirdPromise<void> {
return exec("\
cat config.template.yml | \
sed 's/default_method: two_factor/default_method: single_factor/' > config.test.yml \
");
}
function createCustomTotpIssuerConfiguration(): BluebirdPromise<void> {
return exec("\
cat config.template.yml | \
sed 's/issuer: authelia.com/issuer: custom.com/' > config.test.yml \
");
}
function declareNeedsConfiguration(tag: string, cb: () => BluebirdPromise<void>) {
Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () {
return cb()
.then(function () {
return exec("./scripts/example-commit/dc-example.sh -f " +
"./example/compose/authelia/docker-compose.test.yml up -d authelia &&" +
" sleep 3");
})
});
After({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () {
return exec("rm config.test.yml")
.then(function () {
return exec("./scripts/example-commit/dc-example.sh up -d authelia && sleep 3");
});
});
}
declareNeedsConfiguration("regulation", createRegulationConfiguration);
declareNeedsConfiguration("inactivity", createInactivityConfiguration);
declareNeedsConfiguration("single_factor", createSingleFactorConfiguration);
declareNeedsConfiguration("totp_issuer", createCustomTotpIssuerConfiguration);
function registerUser(context: any, username: string) {
let secret: TOTPSecret;
const mongoClient = new MongoClient({
url: "mongodb://localhost:27017",
database: "authelia",
auth: {
username: "authelia",
password: "authelia"
}
}, new GlobalLoggerStub());
const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient);
const userDataStore = new UserDataStore(collectionFactory);
const generator = new TotpHandler(Speakeasy);
secret = generator.generate("user", "authelia.com");
return userDataStore.saveTOTPSecret(username, secret)
.then(function () {
context.totpSecrets["REGISTERED"] = secret.base32;
return mongoClient.close();
});
}
function declareNeedRegisteredUserHooks(username: string) {
Before({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () {
return registerUser(this, username);
});
After({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () {
this.totpSecrets["REGISTERED"] = undefined;
return BluebirdPromise.resolve();
});
}
function needAuthenticatedUser(context: any, username: string): BluebirdPromise<void> {
return context.visit("https://login.example.com:8080/logout")
.then(function () {
return context.visit("https://login.example.com:8080/");
})
.then(function () {
return registerUser(context, username);
})
.then(function () {
return context.loginWithUserPassword(username, "password");
})
.then(function () {
return context.useTotpTokenHandle("REGISTERED");
})
.then(function () {
return context.clickOnButton("Sign in");
});
}
function declareNeedAuthenticatedUserHooks(username: string) {
Before({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () {
return needAuthenticatedUser(this, username);
});
After({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () {
this.totpSecrets["REGISTERED"] = undefined;
return BluebirdPromise.resolve();
});
}
function declareHooksForUser(username: string) {
declareNeedRegisteredUserHooks(username);
declareNeedAuthenticatedUserHooks(username);
}
const users = ["harry", "john", "bob", "blackhat"];
users.forEach(declareHooksForUser);

View File

@ -1,23 +0,0 @@
import {Then} from "cucumber";
import seleniumWebdriver = require("selenium-webdriver");
import Assert = require("assert");
import Fs = require("fs");
import CustomWorld = require("../support/world");
Then("I get a notification of type {string} with message {string}", { timeout: 10 * 1000 },
function (notificationType: string, notificationMessage: string) {
const that = this;
const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification"));
return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 5000)
.then(function () {
return notificationEl.getText();
})
.then(function (txt: string) {
Assert.equal(notificationMessage, txt);
return notificationEl.getAttribute("class");
})
.then(function (classes: string) {
Assert(classes.indexOf(notificationType) > -1, "Class '" + notificationType + "' not found in notification element.");
return that.driver.sleep(500);
});
});

View File

@ -1,15 +0,0 @@
import {Given, When, Then} from "cucumber";
import seleniumWebdriver = require("selenium-webdriver");
import Assert = require("assert");
Given("I'm on {string}", function (link: string) {
return this.driver.get(link);
});
When("I click on the link to {string}", function (link: string) {
return this.driver.findElement(seleniumWebdriver.By.linkText(link)).click();
});
Then("I'm redirected to {string}", function (link: string) {
return this.waitUntilUrlContains(link);
});

View File

@ -1,13 +0,0 @@
import {When} from "cucumber";
import seleniumWebdriver = require("selenium-webdriver");
import Assert = require("assert");
When("the otpauth url has label {string} and issuer \
{string}", function (label: string, issuer: string) {
return this.driver.findElement(seleniumWebdriver.By.id("qrcode"))
.getAttribute("title")
.then(function (title: string) {
const re = `^otpauth://totp/${label}\\?secret=[A-Z0-9]+&issuer=${issuer}$`;
Assert(new RegExp(re).test(title));
})
});

View File

@ -1,9 +0,0 @@
import {When} from "cucumber";
import seleniumWebdriver = require("selenium-webdriver");
import Assert = require("assert");
import Fs = require("fs");
import CustomWorld = require("../support/world");
When("I wait {int} seconds", { timeout: 10 * 1000 }, function (seconds: number) {
return this.driver.sleep(seconds * 1000);
});

View File

@ -1,16 +0,0 @@
import {When} from "cucumber";
import seleniumWebdriver = require("selenium-webdriver");
import Assert = require("assert");
import Fs = require("fs");
When("I click on the link {string}", function (text: string) {
return this.driver.findElement(seleniumWebdriver.By.linkText(text)).click();
});
When("I click on the link of the email", function () {
const that = this;
return this.retrieveLatestMail()
.then(function (link: string) {
return that.driver.get(link);
});
});

View File

@ -1,10 +0,0 @@
import {When} from "cucumber";
import seleniumWebdriver = require("selenium-webdriver");
import Assert = require("assert");
import ChildProcess = require("child_process");
import BluebirdPromise = require("bluebird");
When(/^the application restarts$/, {timeout: 15 * 1000}, function () {
const exec = BluebirdPromise.promisify(ChildProcess.exec);
return exec("./scripts/example-commit/dc-example.sh restart authelia && sleep 3");
});

View File

@ -1,63 +0,0 @@
import {Before, When, Then, TableDefinition} from "cucumber";
import seleniumWebdriver = require("selenium-webdriver");
import Assert = require("assert");
import Request = require("request-promise");
import Bluebird = require("bluebird");
Before(function () {
this.jar = Request.jar();
});
Then("I get an error {int}", function (code: number) {
return this.getErrorPage(code);
});
function requestAndExpectStatusCode(ctx: any, url: string, method: string,
expectedStatusCode: number) {
return Request(url, {
method: method,
jar: ctx.jar
})
.then(function (body: string) {
return Bluebird.resolve(parseInt(body.match(/Error ([0-9]{3})/)[1]));
}, function (response: any) {
return Bluebird.resolve(response.statusCode)
})
.then(function (statusCode: number) {
try {
Assert.equal(statusCode, expectedStatusCode);
}
catch (e) {
console.log("%s (actual) != %s (expected)", statusCode,
expectedStatusCode);
throw e;
}
})
}
Then("I get the following status code when requesting:",
function (dataTable: TableDefinition) {
const promises: Bluebird<void>[] = [];
for (let i = 0; i < dataTable.rows().length; i++) {
const url: string = (dataTable.hashes() as any)[i].url;
const method: string = (dataTable.hashes() as any)[i].method;
const code: number = (dataTable.hashes() as any)[i].code;
promises.push(requestAndExpectStatusCode(this, url, method, code));
}
return Bluebird.all(promises);
})
When("I post {string} with body:", function (url: string,
dataTable: TableDefinition) {
const body = {};
for (let i = 0; i < dataTable.rows().length; i++) {
const key = (dataTable.hashes() as any)[i].key;
const value = (dataTable.hashes() as any)[i].value;
body[key] = value;
}
return Request.post(url, {
body: body,
jar: this.jar,
json: true
});
});

View File

@ -1,6 +0,0 @@
import {When} from "cucumber";
import seleniumWebdriver = require("selenium-webdriver");
When("I sleep for {int} seconds", function (seconds: number) {
return this.driver.sleep(seconds * 1000);
});

View File

@ -1,37 +0,0 @@
import {When, Then} from "cucumber";
import seleniumWebdriver = require("selenium-webdriver");
import Request = require("request-promise");
import BluebirdPromise = require("bluebird");
import Util = require("util");
When("I request {string} with username {string}" +
" and password {string} using basic authentication",
function (url: string, username: string, password: string) {
const that = this;
return Request(url, {
auth: {
username: username,
password: password
},
resolveWithFullResponse: true
})
.then(function (response: any) {
that.response = response;
});
});
Then("I receive the secret page", function () {
if (this.response.body.match("This is a very important secret!"))
return BluebirdPromise.resolve();
return BluebirdPromise.reject(new Error("Secret page not received."));
});
Then("I received header {string} set to {string}",
function (expectedHeaderName: string, expectedValue: string) {
const expectedLine = Util.format("\"%s\": \"%s\"", expectedHeaderName,
expectedValue);
if (this.response.body.indexOf(expectedLine) > 0)
return BluebirdPromise.resolve();
return BluebirdPromise.reject(new Error(
Util.format("No such header or with unexpected value.")));
});

View File

@ -1,183 +0,0 @@
require("chromedriver");
import seleniumWebdriver = require("selenium-webdriver");
import {setWorldConstructor, After} from "cucumber";
import Fs = require("fs");
import Speakeasy = require("speakeasy");
import Assert = require("assert");
import Request = require("request-promise");
import BluebirdPromise = require("bluebird");
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
function CustomWorld() {
const that = this;
this.driver = new seleniumWebdriver.Builder()
.forBrowser("chrome")
.build();
this.totpSecrets = {};
this.configuration = {};
this.visit = function (link: string) {
return this.driver.get(link);
};
this.setFieldTo = function (fieldName: string, content: string) {
const that = this;
return this.driver.findElement(seleniumWebdriver.By.id(fieldName))
.sendKeys(content);
};
this.clearField = function (fieldName: string) {
return this.driver.findElement(seleniumWebdriver.By.id(fieldName)).clear();
};
this.getErrorPage = function (code: number) {
const that = this;
return this.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.tagName("h1")), 5000)
.then(function () {
return that.driver
.findElement(seleniumWebdriver.By.tagName("h1")).getText();
})
.then(function (txt: string) {
try {
Assert.equal(txt, "Error " + code);
} catch (e) {
console.log(txt);
throw e;
}
})
};
this.clickOnButton = function (buttonText: string) {
const that = this;
return this.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.tagName("button")), 5000)
.then(function () {
return that.driver
.findElement(seleniumWebdriver.By.tagName("button"))
.findElement(seleniumWebdriver.By.xpath("//button[contains(.,'" + buttonText + "')]"))
.click();
});
};
this.waitUntilUrlContains = function (url: string) {
const that = this;
return this.driver.wait(seleniumWebdriver.until.urlIs(url), 15000)
.then(function () {return BluebirdPromise.resolve(); }, function (err: Error) {
that.driver.getCurrentUrl()
.then(function (current: string) {
console.error("====> Error due to: %s (current) != %s (expected)", current, url);
});
return BluebirdPromise.reject(err);
});
};
this.loginWithUserPassword = function (username: string, password: string) {
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id("username")), 5000)
.then(function () {
return that.driver.findElement(seleniumWebdriver.By.id("username"))
.sendKeys(username);
})
.then(function () {
return that.driver.findElement(seleniumWebdriver.By.id("password"))
.clear();
})
.then(function () {
return that.driver.findElement(seleniumWebdriver.By.id("password"))
.sendKeys(password);
})
.then(function () {
return that.driver.findElement(seleniumWebdriver.By.tagName("button"))
.click();
});
};
this.retrieveLatestMail = function () {
return Request({
method: "GET",
uri: "http://localhost:8085/messages",
json: true
})
.then(function (data: any) {
const messageId = data[data.length - 1].id;
return Request({
method: "GET",
uri: `http://localhost:8085/messages/${messageId}.html`
});
})
.then(function (data: any) {
const regexp = new RegExp(/<a href="(.+)" class="button">Continue<\/a>/);
const match = regexp.exec(data);
const link = match[1];
return BluebirdPromise.resolve(link);
});
};
this.registerTotpSecret = function (totpSecretHandle: string) {
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.className("register-totp")), 5000)
.then(function () {
return that.driver.findElement(seleniumWebdriver.By.className("register-totp")).click();
})
.then(function () {
return that.retrieveLatestMail();
})
.then(function (url: string) {
return that.driver.get(url);
})
.then(function () {
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id("secret")), 5000);
})
.then(function () {
return that.driver.findElement(seleniumWebdriver.By.id("secret")).getText();
})
.then(function (secret: string) {
that.totpSecrets[totpSecretHandle] = secret;
});
};
this.useTotpTokenHandle = function (totpSecretHandle: string) {
if (!this.totpSecrets[totpSecretHandle])
throw new Error("No available TOTP token handle " + totpSecretHandle);
const token = Speakeasy.totp({
secret: this.totpSecrets[totpSecretHandle],
encoding: "base32"
});
return this.useTotpToken(token);
};
this.useTotpToken = function (totpSecret: string) {
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id("token")), 5000)
.then(function () {
return that.driver.findElement(seleniumWebdriver.By.id("token"))
.sendKeys(totpSecret);
});
};
this.registerTotpAndSignin = function (username: string, password: string) {
const totpHandle = "HANDLE";
const authUrl = "https://login.example.com:8080/";
const that = this;
return this.visit(authUrl)
.then(function () {
return that.loginWithUserPassword(username, password);
})
.then(function () {
return that.registerTotpSecret(totpHandle);
})
.then(function () {
return that.visit(authUrl);
})
.then(function () {
return that.loginWithUserPassword(username, password);
})
.then(function () {
return that.useTotpTokenHandle(totpHandle);
})
.then(function () {
return that.clickOnButton("Sign in");
});
};
}
setWorldConstructor(CustomWorld);

View File

@ -1,6 +1,5 @@
import FillLoginPageAndClick from "./FillLoginPageAndClick"; import FillLoginPageAndClick from "./FillLoginPageAndClick";
import ValidateTotp from "./ValidateTotp"; import ValidateTotp from "./ValidateTotp";
import VerifyUrlIs from "./assertions/VerifyUrlIs";
import { WebDriver } from "selenium-webdriver"; import { WebDriver } from "selenium-webdriver";
import VisitPageAndWaitUrlIs from "./behaviors/VisitPageAndWaitUrlIs"; import VisitPageAndWaitUrlIs from "./behaviors/VisitPageAndWaitUrlIs";
@ -9,5 +8,4 @@ export default async function(driver: WebDriver, user: string, secret: string, u
await VisitPageAndWaitUrlIs(driver, `https://login.example.com:8080/?rd=${url}`); await VisitPageAndWaitUrlIs(driver, `https://login.example.com:8080/?rd=${url}`);
await FillLoginPageAndClick(driver, user, 'password'); await FillLoginPageAndClick(driver, user, 'password');
await ValidateTotp(driver, secret); await ValidateTotp(driver, secret);
await VerifyUrlIs(driver, url);
} }

View File

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

View File

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

View File

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

View File

@ -1,8 +1,9 @@
import SeleniumWebDriver, { WebDriver } from "selenium-webdriver"; import SeleniumWebDriver, { WebDriver } from "selenium-webdriver";
import Util from "util"; import Util from "util";
export default async function(driver: WebDriver, header: string, expectedValue: string) { export default async function(driver: WebDriver, header: string, expectedValue: string, timeout: number = 5000) {
const el = await driver.wait(SeleniumWebDriver.until.elementLocated(SeleniumWebDriver.By.tagName("body")), 5000); const el = await driver.wait(SeleniumWebDriver.until.elementLocated(
SeleniumWebDriver.By.tagName("body")), timeout);
const text = await el.getText(); const text = await el.getText();
const expectedLine = Util.format("\"%s\": \"%s\"", header, expectedValue); const expectedLine = Util.format("\"%s\": \"%s\"", header, expectedValue);

View File

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

View File

@ -1,5 +1,6 @@
import SeleniumWebDriver, { WebDriver } from "selenium-webdriver"; import SeleniumWebDriver, { WebDriver } from "selenium-webdriver";
export default async function(driver: WebDriver) { export default async function(driver: WebDriver, timeout: number = 5000) {
await driver.wait(SeleniumWebDriver.until.elementLocated(SeleniumWebDriver.By.className('second-factor-step'))); await driver.wait(SeleniumWebDriver.until.elementLocated(
SeleniumWebDriver.By.className('second-factor-step')), timeout);
} }

View File

@ -1,8 +1,9 @@
import SeleniumWebdriver, { WebDriver } from "selenium-webdriver"; import SeleniumWebdriver, { WebDriver } from "selenium-webdriver";
import Assert = require("assert"); import Assert = require("assert");
export default async function(driver: WebDriver, message: string) { export default async function(driver: WebDriver, message: string, timeout: number = 5000) {
await driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.className("notification")), 5000) await driver.wait(SeleniumWebdriver.until.elementLocated(
SeleniumWebdriver.By.className("notification")), timeout)
const notificationEl = driver.findElement(SeleniumWebdriver.By.className("notification")); const notificationEl = driver.findElement(SeleniumWebdriver.By.className("notification"));
const txt = await notificationEl.getText(); const txt = await notificationEl.getText();
Assert.equal(message, txt); Assert.equal(message, txt);

View File

@ -5,9 +5,11 @@ import FullLogin from "../FullLogin";
export default async function( export default async function(
driver: WebDriver, driver: WebDriver,
username: string, username: string,
password: string,
email: boolean = false, email: boolean = false,
targetUrl: string = "https://login.example.com:8080/") { targetUrl: string = "https://login.example.com:8080/") {
const secret = await LoginAndRegisterTotp(driver, username, email); const secret = await LoginAndRegisterTotp(driver, username, password, email);
await FullLogin(driver, username, secret, targetUrl); await FullLogin(driver, username, secret, targetUrl);
return secret;
}; };

View File

@ -34,7 +34,7 @@ export default function() {
WithDriver(); WithDriver();
before(async function() { before(async function() {
const secret = await LoginAndRegisterTotp(this.driver, "john", true); const secret = await LoginAndRegisterTotp(this.driver, "john", "password", true);
await VisitPageAndWaitUrlIs(this.driver, 'https://login.example.com:8080/'); await VisitPageAndWaitUrlIs(this.driver, 'https://login.example.com:8080/');
await FillLoginPageAndClick(this.driver, 'john', 'password', false); await FillLoginPageAndClick(this.driver, 'john', 'password', false);
await ValidateTotp(this.driver, secret); await ValidateTotp(this.driver, secret);
@ -61,7 +61,7 @@ export default function() {
WithDriver(); WithDriver();
before(async function() { before(async function() {
const secret = await LoginAndRegisterTotp(this.driver, "bob", true); const secret = await LoginAndRegisterTotp(this.driver, "bob", "password", true);
await VisitPageAndWaitUrlIs(this.driver, 'https://login.example.com:8080/'); await VisitPageAndWaitUrlIs(this.driver, 'https://login.example.com:8080/');
await FillLoginPageAndClick(this.driver, 'bob', 'password', false); await FillLoginPageAndClick(this.driver, 'bob', 'password', false);
await ValidateTotp(this.driver, secret); await ValidateTotp(this.driver, secret);
@ -88,7 +88,7 @@ export default function() {
WithDriver(); WithDriver();
before(async function() { before(async function() {
const secret = await LoginAndRegisterTotp(this.driver, "harry", true); const secret = await LoginAndRegisterTotp(this.driver, "harry", "password", true);
await VisitPageAndWaitUrlIs(this.driver, 'https://login.example.com:8080/'); await VisitPageAndWaitUrlIs(this.driver, 'https://login.example.com:8080/');
await FillLoginPageAndClick(this.driver, 'harry', 'password', false); await FillLoginPageAndClick(this.driver, 'harry', 'password', false);
await ValidateTotp(this.driver, secret); await ValidateTotp(this.driver, secret);

View File

@ -15,7 +15,7 @@ export default function() {
ChildProcess.execSync('rm -f .authelia-interrupt'); ChildProcess.execSync('rm -f .authelia-interrupt');
this.driver = await StartDriver(); this.driver = await StartDriver();
await RegisterAndLoginTwoFactor(this.driver, 'john', true, 'https://admin.example.com:8080/secret.html'); await RegisterAndLoginTwoFactor(this.driver, 'john', "password", true, 'https://admin.example.com:8080/secret.html');
await VisitPageAndWaitUrlIs(this.driver, 'https://home.example.com:8080/'); await VisitPageAndWaitUrlIs(this.driver, 'https://home.example.com:8080/');
}); });
@ -46,7 +46,7 @@ export default function() {
ChildProcess.execSync('rm -f .authelia-interrupt'); ChildProcess.execSync('rm -f .authelia-interrupt');
this.driver = await StartDriver(); this.driver = await StartDriver();
this.secret = await LoginAndRegisterTotp(this.driver, 'john', true); this.secret = await LoginAndRegisterTotp(this.driver, 'john', "password", true);
await Logout(this.driver); await Logout(this.driver);
}); });

View File

@ -3,31 +3,14 @@ import LoginAs from "../../../helpers/LoginAs";
import VerifyNotificationDisplayed from "../../../helpers/assertions/VerifyNotificationDisplayed"; import VerifyNotificationDisplayed from "../../../helpers/assertions/VerifyNotificationDisplayed";
import VerifyIsSecondFactorStage from "../../../helpers/assertions/VerifyIsSecondFactorStage"; import VerifyIsSecondFactorStage from "../../../helpers/assertions/VerifyIsSecondFactorStage";
/*
Given I visit "https://login.example.com:8080/"
And I set field "username" to "blackhat"
And I set field "password" to "bad-password"
And I click on "Sign in"
And I get a notification of type "error" with message "Authentication failed. Please check your credentials."
And I set field "password" to "bad-password"
And I click on "Sign in"
And I get a notification of type "error" with message "Authentication failed. Please check your credentials."
And I set field "password" to "bad-password"
And I click on "Sign in"
And I get a notification of type "error" with message "Authentication failed. Please check your credentials."
When I set field "password" to "password"
And I click on "Sign in"
Then I get a notification of type "error" with message "Authentication failed. Please check your credentials."
*/
export default function() { export default function() {
describe('Authelia regulates authentications when a hacker is brute forcing', function() { describe('Authelia regulates authentications when a hacker is brute forcing', function() {
this.timeout(15000); this.timeout(15000);
before(async function() { beforeEach(async function() {
this.driver = await StartDriver(); this.driver = await StartDriver();
}); });
after(async function() { afterEach(async function() {
await StopDriver(this.driver); await StopDriver(this.driver);
}); });

View File

@ -31,7 +31,7 @@ export default function() {
describe("With two factors", function() { describe("With two factors", function() {
before(async function() { before(async function() {
this.driver = await StartDriver(); this.driver = await StartDriver();
await RegisterAndLoginWith2FA(this.driver, "john", true, "https://public.example.com:8080/headers"); await RegisterAndLoginWith2FA(this.driver, "john", "password", true, "https://public.example.com:8080/headers");
}); });
after(async function() { after(async function() {

View File

@ -21,7 +21,7 @@ export default function() {
beforeEach(async function() { beforeEach(async function() {
this.driver = await StartDriver(); this.driver = await StartDriver();
secret = await LoginAndRegisterTotp(this.driver, "john", true) secret = await LoginAndRegisterTotp(this.driver, "john", "password", true)
}); });
afterEach(async function() { afterEach(async function() {

View File

@ -9,6 +9,9 @@ import TOTPValidation from './scenarii/TOTPValidation';
import Inactivity from './scenarii/Inactivity'; import Inactivity from './scenarii/Inactivity';
import BackendProtection from './scenarii/BackendProtection'; import BackendProtection from './scenarii/BackendProtection';
import VerifyEndpoint from './scenarii/VerifyEndpoint'; import VerifyEndpoint from './scenarii/VerifyEndpoint';
import RequiredTwoFactor from './scenarii/RequiredTwoFactor';
import LogoutRedirectToAlreadyLoggedIn from './scenarii/LogoutRedirectToAlreadyLoggedIn';
import SimpleAuthentication from './scenarii/SimpleAuthentication';
const execAsync = Bluebird.promisify(ChildProcess.exec); const execAsync = Bluebird.promisify(ChildProcess.exec);
@ -18,14 +21,14 @@ AutheliaSuite('Minimal configuration', __dirname + '/config.yml', function() {
return execAsync("cp users_database.example.yml users_database.yml"); return execAsync("cp users_database.example.yml users_database.yml");
}); });
describe('Simple authentication', SimpleAuthentication);
describe('Backend protection', BackendProtection); describe('Backend protection', BackendProtection);
describe('Verify API endpoint', VerifyEndpoint); describe('Verify API endpoint', VerifyEndpoint);
describe('Bad password', BadPassword); describe('Bad password', BadPassword);
describe('Reset password', ResetPassword); describe('Reset password', ResetPassword);
describe('TOTP Registration', RegisterTotp); describe('TOTP Registration', RegisterTotp);
describe('TOTP Validation', TOTPValidation); describe('TOTP Validation', TOTPValidation);
describe('Inactivity period', Inactivity); describe('Inactivity period', Inactivity);
describe('Required two factor', RequiredTwoFactor);
describe('Logout endpoint redirect to already logged in page', LogoutRedirectToAlreadyLoggedIn);
}); });

View File

@ -12,7 +12,7 @@ export default function(this: Mocha.ISuiteCallbackContext) {
beforeEach(async function() { beforeEach(async function() {
this.driver = await StartDriver(); this.driver = await StartDriver();
this.secret = await LoginAndRegisterTotp(this.driver, "john", true); this.secret = await LoginAndRegisterTotp(this.driver, "john", "password", true);
}); });
afterEach(async function() { afterEach(async function() {

View File

@ -0,0 +1,23 @@
import { StartDriver, StopDriver } from "../../../helpers/context/WithDriver";
import VisitPage from "../../../helpers/VisitPage";
import VerifyIsAlreadyAuthenticatedStage from "../../../helpers/assertions/VerifyIsAlreadyAuthenticatedStage";
import RegisterAndLoginTwoFactor from "../../../helpers/behaviors/RegisterAndLoginTwoFactor";
export default function() {
describe('When visiting /logout the user is redirected to already logged in page to log out', function() {
before(async function() {
this.driver = await StartDriver();
await RegisterAndLoginTwoFactor(this.driver, 'john', "password", true);
});
after(async function() {
await StopDriver(this.driver);
});
it('should redirect the user', async function() {
await VisitPage(this.driver, 'https://login.example.com:8080/logout');
await VerifyIsAlreadyAuthenticatedStage(this.driver);
});
});
}

View File

@ -14,7 +14,7 @@ export default function() {
beforeEach(async function() { beforeEach(async function() {
this.driver = await StartDriver(); this.driver = await StartDriver();
await LoginAndRegisterTotp(this.driver, "john", true); await LoginAndRegisterTotp(this.driver, "john", "password", true);
}); });
afterEach(async function() { afterEach(async function() {

View File

@ -0,0 +1,30 @@
import LoginAndRegisterTotp from '../../../helpers/LoginAndRegisterTotp';
import VerifyUrlIs from '../../../helpers/assertions/VerifyUrlIs';
import { StartDriver, StopDriver } from '../../../helpers/context/WithDriver';
import VerifyIsSecondFactorStage from '../../../helpers/assertions/VerifyIsSecondFactorStage';
import VisitPage from '../../../helpers/VisitPage';
import FillLoginPageAndClick from '../../../helpers/FillLoginPageAndClick';
export default function() {
describe('User tries to access a page protected by second factor while he only passed first factor', function() {
before(async function() {
this.driver = await StartDriver();
const secret = await LoginAndRegisterTotp(this.driver, "john", "password", true);
if (!secret) throw new Error('No secret!');
await VisitPage(this.driver, "https://admin.example.com:8080/secret.html");
await VerifyUrlIs(this.driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html");
await FillLoginPageAndClick(this.driver, "john", "password");
await VerifyIsSecondFactorStage(this.driver);
});
after(async function() {
await StopDriver(this.driver);
});
it("should reach second factor page of login portal", async function() {
await VisitPage(this.driver, "https://admin.example.com:8080/secret.html");
await VerifyIsSecondFactorStage(this.driver);
});
});
}

View File

@ -0,0 +1,38 @@
import { StartDriver, StopDriver } from "../../../helpers/context/WithDriver";
import RegisterAndLoginTwoFactor from "../../../helpers/behaviors/RegisterAndLoginTwoFactor";
import VerifyUrlIs from "../../../helpers/assertions/VerifyUrlIs";
import VisitPage from "../../../helpers/VisitPage";
import VisitPageAndWaitUrlIs from "../../../helpers/behaviors/VisitPageAndWaitUrlIs";
export default function() {
describe('The user is redirected to target url upon successful authentication', function() {
before(async function() {
this.driver = await StartDriver();
await RegisterAndLoginTwoFactor(this.driver, 'john', "password", true, 'https://admin.example.com:8080/secret.html');
});
after(async function() {
await StopDriver(this.driver);
});
it('should redirect the user', async function() {
await VerifyUrlIs(this.driver, 'https://admin.example.com:8080/secret.html');
});
});
describe('The target url is in "rd" parameter of the portal URL', function() {
before(async function() {
this.driver = await StartDriver();
await VisitPage(this.driver, 'https://admin.example.com:8080/secret.html');
});
after(async function() {
await StopDriver(this.driver);
});
it('should redirect the user', async function() {
await VerifyUrlIs(this.driver, 'https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html');
});
})
}

View File

@ -17,7 +17,7 @@ export default function() {
describe('Successfully pass second factor with TOTP', function() { describe('Successfully pass second factor with TOTP', function() {
before(async function() { before(async function() {
this.driver = await StartDriver(); this.driver = await StartDriver();
const secret = await LoginAndRegisterTotp(this.driver, "john", true); const secret = await LoginAndRegisterTotp(this.driver, "john", "password", true);
if (!secret) throw new Error('No secret!'); if (!secret) throw new Error('No secret!');
await VisitPageAndWaitUrlIs(this.driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html"); await VisitPageAndWaitUrlIs(this.driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html");
@ -46,7 +46,7 @@ export default function() {
describe('Fail validation of second factor with TOTP', function() { describe('Fail validation of second factor with TOTP', function() {
before(async function() { before(async function() {
this.driver = await StartDriver(); this.driver = await StartDriver();
await LoginAndRegisterTotp(this.driver, "john", true); await LoginAndRegisterTotp(this.driver, "john", "password", true);
const BAD_TOKEN = "125478"; const BAD_TOKEN = "125478";
await VisitPageAndWaitUrlIs(this.driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html"); await VisitPageAndWaitUrlIs(this.driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html");