Migrate more Cucumber tests into Mocha.

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

2
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
#!/usr/bin/env node
var program = require('commander');
var exec = require('child_process').execSync;
var spawn = require('child_process').spawn;
var chokidar = require('chokidar');
var fs = require('fs');
@ -101,15 +100,39 @@ function reload(path) {
reloadServer();
}
fs.writeFileSync(ENVIRONMENT_FILENAME, program.suite);
exec('./example/compose/nginx/portal/render.js');
exec('./scripts/utils/prepare-environment.sh');
function exec(command, args) {
return new Promise((resolve, reject) => {
const cmd = spawn(command, args);
console.log('Start watching');
tsWatcher.on('add', reload);
tsWatcher.on('remove', reload);
tsWatcher.on('change', reload);
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));
});
});
}
startServer();
startClient();
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('remove', reload);
tsWatcher.on('change', reload);
startServer();
startClient();
}
main()

View File

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

View File

@ -1,5 +1,6 @@
import Exceptions = require("../../Exceptions");
import * as ObjectPath from "object-path";
import BluebirdPromise = require("bluebird");
import express = require("express");
import ErrorReplies = require("../../ErrorReplies");
@ -9,6 +10,11 @@ import { ServerVariables } from "../../ServerVariables";
import { AuthenticationSession } from "../../../../types/AuthenticationSession";
import { GroupsAndEmails } from "../../authentication/backends/GroupsAndEmails";
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) {
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.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.send();
return BluebirdPromise.resolve();

View File

@ -56,8 +56,6 @@ export default function (req: Express.Request, res: Express.Response,
const originalUrl = ObjectPath.get<Express.Request, string>(
req, "headers.x-original-url");
const originalUri =
ObjectPath.get<Express.Request, string>(req, "headers.x-original-uri");
const d = URLDecomposer.fromUrl(originalUrl);
vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", d.domain,

View File

@ -1,5 +1,4 @@
import Express = require("express");
import { DomainExtractor } from "../../../../shared/DomainExtractor";
import { BelongToDomain } from "../../../../shared/BelongToDomain";

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,13 @@
import VisitPage from "./VisitPage";
import FillLoginPageWithUserAndPasswordAndClick from "./FillLoginPageAndClick";
import FillLoginPageAndClick from "./FillLoginPageAndClick";
import ValidateTotp from "./ValidateTotp";
import WaitRedirected from "./WaitRedirected";
import VerifyUrlIs from "./assertions/VerifyUrlIs";
import { WebDriver } from "selenium-webdriver";
// 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 FillLoginPageWithUserAndPasswordAndClick(driver, user, 'password');
await FillLoginPageAndClick(driver, user, 'password');
await ValidateTotp(driver, secret);
await WaitRedirected(driver, "https://admin.example.com:8080/secret.html");
await VerifyUrlIs(driver, url);
}

View File

@ -1,10 +1,10 @@
import RegisterTotp from './RegisterTotp';
import LoginAs from './LoginAs';
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 IsSecondFactorStage(driver);
await VerifyIsSecondFactorStage(driver);
return await RegisterTotp(driver, email);
}

View File

@ -2,7 +2,7 @@ import VisitPage from "./VisitPage";
import FillLoginPageAndClick from './FillLoginPageAndClick';
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 FillLoginPageAndClick(driver, user, "password");
await FillLoginPageAndClick(driver, user, password);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,8 +4,8 @@ import FillLoginPageWithUserAndPasswordAndClick from '../../../helpers/FillLogin
import ValidateTotp from "../../../helpers/ValidateTotp";
import Logout from "../../../helpers/Logout";
import WaitRedirected from "../../../helpers/WaitRedirected";
import IsAlreadyAuthenticatedStage from "../../../helpers/IsAlreadyAuthenticatedStage";
import WithDriver from "../../../helpers/context/WithDriver";
import IsAlreadyAuthenticatedStage from "../../../helpers/assertions/VerifyIsAlreadyAuthenticatedStage";
import { StartDriver, StopDriver } from "../../../helpers/context/WithDriver";
/*
* 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.
*/
export default function() {
WithDriver(true);
describe("Only redirection to a subdomain of the protected domain should be allowed", function() {
this.timeout(10000);
let secret: string;
beforeEach(async function() {
this.driver = await StartDriver();
secret = await LoginAndRegisterTotp(this.driver, "john", true)
});
afterEach(async function() {
await Logout(this.driver);
await StopDriver(this.driver);
})
function CannotRedirectTo(url: string) {

View File

@ -16,6 +16,6 @@ export default function() {
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);
await FullLogin(this.driver, "john", secret, "https://admin.example.com:8080/secret.html");
});
}

View File

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

View File

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

View File

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