Merge pull request #98 from clems4ever/disable-second-factor

Allow basic auth for certain subdomains
This commit is contained in:
Clément Michaud 2017-09-26 23:25:07 +02:00 committed by GitHub
commit 89de19bb35
29 changed files with 683 additions and 532 deletions

View File

@ -15,13 +15,14 @@ addons:
- libgif-dev
- google-chrome-stable
hosts:
- auth.test.local
- home.test.local
- public.test.local
- admin.test.local
- dev.test.local
- mx1.mail.test.local
- mx2.mail.test.local
- admin.test.local
- auth.test.local
- basicauth.test.local
- dev.test.local
- home.test.local
- mx1.mail.test.local
- mx2.mail.test.local
- public.test.local
before_install:
- npm install -g npm@'>=2.13.5'

View File

@ -26,7 +26,7 @@ module.exports = function (grunt) {
},
"docker-restart": {
cmd: "./scripts/dc-dev.sh",
args: ['up', '-d']
args: ['restart', 'authelia']
},
"minify": {
cmd: "./node_modules/.bin/uglifyjs",

View File

@ -5,6 +5,6 @@ services:
- ./test:/usr/src/test
- ./dist/src/server:/usr/src
- ./node_modules:/usr/src/node_modules
- ./config.yml:/etc/authelia/config.yml:ro
- ./config.template.yml:/etc/authelia/config.yml:ro
networks:
- example-network

View File

@ -0,0 +1,10 @@
<html>
<head>
<title>Secret</title>
<link rel="icon" href="/icon.png" type="image/png" />
</head>
<body>
This is a very important secret!<br/>
Go back to <a href="https://home.test.local:8080/">home page</a>.
</body>
</html>

View File

@ -8,8 +8,8 @@
<body>
<h1>Access the secret</h1>
<span style="font-size: 1.2em; color: red">You need to log in to access the secret!</span><br/><br/>
Try to access it using one of the following links to test access control powered by Authelia.<br/>
<span style="font-size: 1.2em; color: red">You need to log in to access the secret!</span><br/><br/> Try to access it using
one of the following links to test access control powered by Authelia.<br/>
<ul>
<li>
public.test.local <a href="https://public.test.local:8080/"> / index.html</a>
@ -51,17 +51,19 @@
<li>
mx2.main.test.local <a href="https://mx2.mail.test.local:8080/secret.html"> / secret.html</a>
</li>
<li>
basicauth.test.local <a href="https://basicauth.test.local:8080/secret.html"> / secret.html</a>
</li>
</ul>
You can also log off by visiting the following <a href="https://auth.test.local:8080/logout?redirect=https://home.test.local:8080/">link</a>.
<h1>List of users</h1>
Here is the list of credentials you can log in with to test access control.<br/>
<br/>
Once first factor is passed, you will need to follow the links to register a secret for the second factor.<br/>
Authelia will send you a fictituous email that will be in the file
<strong>/tmp/notifications/notification.txt</strong>.<br/>
It will provide you with the link to complete the registration allowing you to authenticate with 2-factor.
<br/> Once first factor is passed, you will need to follow the links to register a secret for the second factor.<br/> Authelia
will send you a fictituous email that will be in the file
<strong>/tmp/notifications/notification.txt</strong>.<br/> It will provide you with the link to complete the registration
allowing you to authenticate with 2-factor.
<ul>
<li><strong>john / password</strong>: belongs to <em>admin</em> and <em>dev</em> groups.</li>
@ -70,7 +72,7 @@
</ul>
<h1>Access control rules</h1>
<p></p>These rules are extracted from the configuration file
<p></p>These rules are extracted from the configuration file
<a href="https://github.com/clems4ever/authelia/blob/master/config.template.yml">config.template.yml</a>.</p>
<pre id="rules" style="border: 1px grey solid; padding: 20px; display: inline-block;">
# Default policy can either be `allow` or `deny`.
@ -129,4 +131,5 @@ users:
resources:
- '^/users/harry/.*$'</pre>
</body>
</html>

View File

@ -203,5 +203,42 @@ http {
error_page 403 = https://auth.test.local:8080/error/403;
}
}
server {
listen 443 ssl;
root /usr/share/nginx/html/basicauth.test.local;
server_name basicauth.test.local;
ssl on;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
location /auth_verify {
internal;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header Content-Length "";
proxy_pass http://authelia/verify?only_basic_auth=true;
}
location / {
auth_request /auth_verify;
auth_request_set $redirect $upstream_http_redirect;
proxy_set_header Redirect $redirect;
auth_request_set $user $upstream_http_remote_user;
proxy_set_header X-Forwarded-User $user;
auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Remote-Groups $groups;
error_page 401 =302 https://auth.test.local:8080?redirect=$redirect&only_basic_auth=true;
error_page 403 = https://auth.test.local:8080/error/403;
}
}
}

View File

@ -1,18 +1,27 @@
import BluebirdPromise = require("bluebird");
import Endpoints = require("../../../server/endpoints");
import Constants = require("../../../server/constants");
export function validate(username: string, password: string, $: JQueryStatic): BluebirdPromise<void> {
return new BluebirdPromise<void>(function (resolve, reject) {
$.post(Endpoints.FIRST_FACTOR_POST, {
username: username,
password: password,
})
.done(function () {
resolve();
})
.fail(function (xhr: JQueryXHR, textStatus: string) {
reject(new Error("Authetication failed. Please check your credentials."));
});
});
export function validate(username: string, password: string,
redirectUrl: string, onlyBasicAuth: boolean, $: JQueryStatic): BluebirdPromise<string> {
return new BluebirdPromise<string>(function (resolve, reject) {
const url = Endpoints.FIRST_FACTOR_POST + "?" + Constants.REDIRECT_QUERY_PARAM + "=" + redirectUrl
+ "&" + Constants.ONLY_BASIC_AUTH_QUERY_PARAM + "=" + onlyBasicAuth;
$.ajax({
method: "POST",
url: url,
data: {
username: username,
password: password,
}
})
.done(function (data: { redirect: string }) {
resolve(data.redirect);
})
.fail(function (xhr: JQueryXHR, textStatus: string) {
reject(new Error("Authetication failed. Please check your credentials."));
});
});
}

View File

@ -3,7 +3,7 @@ import JSLogger = require("js-logger");
import UISelectors = require("./UISelectors");
import { Notifier } from "../Notifier";
import { QueryParametersRetriever } from "../QueryParametersRetriever";
import Constants = require("../../../server/constants");
import Endpoints = require("../../../server/endpoints");
export default function (window: Window, $: JQueryStatic,
@ -15,20 +15,17 @@ export default function (window: Window, $: JQueryStatic,
const username: string = $(UISelectors.USERNAME_FIELD_ID).val();
const password: string = $(UISelectors.PASSWORD_FIELD_ID).val();
$(UISelectors.PASSWORD_FIELD_ID).val("");
jslogger.debug("Form submitted");
firstFactorValidator.validate(username, password, $)
const redirectUrl = QueryParametersRetriever.get(Constants.REDIRECT_QUERY_PARAM);
const onlyBasicAuth = QueryParametersRetriever.get(Constants.ONLY_BASIC_AUTH_QUERY_PARAM) ? true : false;
firstFactorValidator.validate(username, password, redirectUrl, onlyBasicAuth, $)
.then(onFirstFactorSuccess, onFirstFactorFailure);
return false;
}
function onFirstFactorSuccess() {
function onFirstFactorSuccess(redirectUrl: string) {
jslogger.debug("First factor validated.");
const redirectUrl = QueryParametersRetriever.get("redirect");
if (redirectUrl)
window.location.href = Endpoints.SECOND_FACTOR_GET + "?redirect="
+ encodeURIComponent(redirectUrl);
else
window.location.href = Endpoints.SECOND_FACTOR_GET;
window.location.href = redirectUrl;
}
function onFirstFactorFailure(err: Error) {

View File

@ -8,22 +8,23 @@ import Endpoints = require("../../../server/endpoints");
import Constants = require("./constants");
import { Notifier } from "../Notifier";
import { QueryParametersRetriever } from "../QueryParametersRetriever";
import ServerConstants = require("../../../server/constants");
export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) {
const notifierTotp = new Notifier(".notification-totp", $);
const notifierU2f = new Notifier(".notification-u2f", $);
function onAuthenticationSuccess(data: any) {
const redirectUrl = QueryParametersRetriever.get("redirect");
function onAuthenticationSuccess(data: any, notifier: Notifier) {
const redirectUrl = QueryParametersRetriever.get(ServerConstants.REDIRECT_QUERY_PARAM);
if (redirectUrl)
window.location.href = redirectUrl;
else
window.location.href = Endpoints.FIRST_FACTOR_GET;
notifier.success("Authentication succeeded. You can now access your services.");
}
function onSecondFactorTotpSuccess(data: any) {
onAuthenticationSuccess(data);
onAuthenticationSuccess(data, notifierTotp);
}
function onSecondFactorTotpFailure(err: Error) {
@ -31,7 +32,7 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi)
}
function onU2fAuthenticationSuccess(data: any) {
onAuthenticationSuccess(data);
onAuthenticationSuccess(data, notifierU2f);
}
function onU2fAuthenticationFailure() {

4
src/server/constants.ts Normal file
View File

@ -0,0 +1,4 @@
export const ONLY_BASIC_AUTH_QUERY_PARAM = "only_basic_auth";
export const REDIRECT_QUERY_PARAM = "redirect";

View File

@ -4,7 +4,7 @@ import BluebirdPromise = require("bluebird");
function replyWithError(res: express.Response, code: number, logger: Winston): (err: Error) => void {
return function (err: Error): void {
logger.error("Reply with error %d: %s", code, err);
logger.error("Reply with error %d: %s", code, err.stack);
res.status(code);
res.send();
};

View File

@ -93,6 +93,8 @@ export class AccessController implements IAccessController {
}
isAccessAllowed(domain: string, resource: string, user: string, groups: string[]): boolean {
if (!this.configuration) return true;
const allRules = this.getMatchingAllRules(domain, resource);
const groupRules = this.getMatchingGroupRules(groups, domain, resource);
const userRules = this.getMatchingUserRules(user, domain, resource);

View File

@ -188,7 +188,7 @@ export class Client implements IClient {
this.logger.debug("LDAP: update password of user '%s'", username);
return this.searchUserDn(username)
.then(function (userDN: string) {
this.client.modifyAsync(userDN, change);
that.client.modifyAsync(userDN, change);
})
.then(function () {
return that.client.unbindAsync();

View File

@ -12,22 +12,9 @@ export default function (req: express.Request, res: express.Response): BluebirdP
logger.debug("First factor: headers are %s", JSON.stringify(req.headers));
return AuthenticationValidator.validate(req)
.then(function () {
const redirectUrl = req.query.redirect;
if (redirectUrl) {
res.redirect(redirectUrl);
return BluebirdPromise.resolve();
}
else {
res.render("already-logged-in", { logout_endpoint: Endpoints.LOGOUT_GET });
return BluebirdPromise.resolve();
}
}, function () {
res.render("firstfactor", {
first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST,
reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET
});
return BluebirdPromise.resolve();
});
res.render("firstfactor", {
first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST,
reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET
});
return BluebirdPromise.resolve();
}

View File

@ -10,6 +10,7 @@ import Endpoint = require("../../../endpoints");
import ErrorReplies = require("../../ErrorReplies");
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
import AuthenticationSession = require("../../AuthenticationSession");
import Constants = require("../../../constants");
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const username: string = req.body.username;
@ -47,6 +48,8 @@ export default function (req: express.Request, res: express.Response): BluebirdP
JSON.stringify(groupsAndEmails));
authSession.userid = username;
authSession.first_factor = true;
const redirectUrl = req.query[Constants.REDIRECT_QUERY_PARAM];
const onlyBasicAuth = req.query[Constants.ONLY_BASIC_AUTH_QUERY_PARAM] === "true";
const emails: string[] = groupsAndEmails.emails;
const groups: string[] = groupsAndEmails.groups;
@ -63,8 +66,25 @@ export default function (req: express.Request, res: express.Response): BluebirdP
logger.debug("1st factor: Mark successful authentication to regulator.");
regulator.mark(username, true);
res.status(204);
res.send();
logger.debug("1st factor: Redirect URL is %s", redirectUrl);
logger.debug("1st factor: %s? %s", Constants.ONLY_BASIC_AUTH_QUERY_PARAM, onlyBasicAuth);
if (onlyBasicAuth) {
res.send({
redirect: redirectUrl
});
logger.debug("1st factor: redirect to '%s'", redirectUrl);
}
else {
let newRedirectUrl = Endpoint.SECOND_FACTOR_GET;
if (redirectUrl !== "undefined") {
newRedirectUrl += "?redirect=" + encodeURIComponent(redirectUrl);
}
logger.debug("1st factor: redirect to '%s'", newRedirectUrl, typeof redirectUrl);
res.send({
redirect: newRedirectUrl
});
}
return BluebirdPromise.resolve();
})
.catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(res, logger))

View File

@ -10,33 +10,32 @@ import ErrorReplies = require("../../../ErrorReplies");
import Constants = require("./../constants");
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app);
const ldapPasswordUpdater = ServerVariablesHandler.getLdapPasswordUpdater(req.app);
let authSession: AuthenticationSession.AuthenticationSession;
const newPassword = objectPath.get<express.Request, string>(req, "body.password");
const logger = ServerVariablesHandler.getLogger(req.app);
const ldapPasswordUpdater = ServerVariablesHandler.getLdapPasswordUpdater(req.app);
let authSession: AuthenticationSession.AuthenticationSession;
const newPassword = objectPath.get<express.Request, string>(req, "body.password");
return AuthenticationSession.get(req)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
logger.info("POST reset-password: User %s wants to reset his/her password.",
authSession.identity_check.userid);
logger.info("POST reset-password: Challenge %s", authSession.identity_check.challenge);
return AuthenticationSession.get(req)
.then(function (_authSession) {
authSession = _authSession;
logger.info("POST reset-password: User %s wants to reset his/her password.",
authSession.identity_check.userid);
logger.info("POST reset-password: Challenge %s", authSession.identity_check.challenge);
if (authSession.identity_check.challenge != Constants.CHALLENGE) {
res.status(403);
res.send();
return BluebirdPromise.reject(new Error("Bad challenge."));
}
return ldapPasswordUpdater.updatePassword(authSession.identity_check.userid, newPassword);
})
.then(function () {
logger.info("POST reset-password: Password reset for user '%s'",
authSession.identity_check.userid);
AuthenticationSession.reset(req);
res.status(204);
res.send();
return BluebirdPromise.resolve();
})
.catch(ErrorReplies.replyWithError500(res, logger));
if (authSession.identity_check.challenge != Constants.CHALLENGE) {
res.status(403);
res.send();
return BluebirdPromise.reject(new Error("Bad challenge."));
}
return ldapPasswordUpdater.updatePassword(authSession.identity_check.userid, newPassword);
})
.then(function () {
logger.info("POST reset-password: Password reset for user '%s'",
authSession.identity_check.userid);
AuthenticationSession.reset(req);
res.status(204);
res.send();
return BluebirdPromise.resolve();
})
.catch(ErrorReplies.replyWithError500(res, logger));
}

View File

@ -8,35 +8,38 @@ import AuthenticationValidator = require("../../AuthenticationValidator");
import ErrorReplies = require("../../ErrorReplies");
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
import AuthenticationSession = require("../../AuthenticationSession");
import Constants = require("../../../constants");
function verify_filter(req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app);
const accessController = ServerVariablesHandler.getAccessController(req.app);
let authSession: AuthenticationSession.AuthenticationSession;
return AuthenticationSession.get(req)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
.then(function (authSession) {
logger.debug("Verify: headers are %s", JSON.stringify(req.headers));
res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] + req.headers["x-original-uri"]));
return AuthenticationValidator.validate(req);
})
.then(function () {
const username = authSession.userid;
const groups = authSession.groups;
const onlyBasicAuth = req.query[Constants.ONLY_BASIC_AUTH_QUERY_PARAM] === "true";
logger.debug("Verify: %s=%s", Constants.ONLY_BASIC_AUTH_QUERY_PARAM, onlyBasicAuth);
const host = objectPath.get<express.Request, string>(req, "headers.host");
const path = objectPath.get<express.Request, string>(req, "headers.x-original-uri");
const domain = host.split(":")[0];
logger.debug("Verify: domain=%s, path=%s", domain, path);
logger.debug("Verify: user=%s, groups=%s", username, groups.join(","));
if (!authSession.first_factor)
return BluebirdPromise.reject(new exceptions.AccessDeniedError("First factor not validated."));
const isAllowed = accessController.isAccessAllowed(domain, path, username, groups);
if (!isAllowed) return BluebirdPromise.reject(
new exceptions.DomainAccessDenied("User '" + username + "' does not have access to " + domain));
new exceptions.DomainAccessDenied("User '" + username + "' does not have access to " + domain));
if (!authSession.first_factor || !authSession.second_factor)
return BluebirdPromise.reject(new exceptions.AccessDeniedError("First or second factor not validated"));
if (!onlyBasicAuth && !authSession.second_factor)
return BluebirdPromise.reject(new exceptions.AccessDeniedError("Second factor not validated."));
res.setHeader("Remote-User", username);
res.setHeader("Remote-Groups", groups.join(","));

View File

@ -16,6 +16,7 @@ Feature: User has access restricted access to domains
| https://dev.test.local:8080/users/bob/secret.html |
| https://admin.test.local:8080/secret.html |
| https://mx1.mail.test.local:8080/secret.html |
| https://basicauth.test.local:8080/secret.html |
And I have no access to:
| url |
| https://mx2.mail.test.local:8080/secret.html |
@ -39,6 +40,7 @@ Feature: User has access restricted access to domains
| https://admin.test.local:8080/secret.html |
| https://dev.test.local:8080/users/john/secret.html |
| https://dev.test.local:8080/users/harry/secret.html |
| https://basicauth.test.local:8080/secret.html |
@need-registered-user-harry
Scenario: User harry has restricted access
@ -59,3 +61,4 @@ Feature: User has access restricted access to domains
| https://dev.test.local:8080/users/john/secret.html |
| https://mx1.mail.test.local:8080/secret.html |
| https://mx2.mail.test.local:8080/secret.html |
| https://basicauth.test.local:8080/secret.html |

View File

@ -0,0 +1,19 @@
Feature: User can access certain subdomains with basic auth
@need-registered-user-john
Scenario: User is redirected to service after first factor if allowed
When I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fbasicauth.test.local%3A8080%2Fsecret.html&only_basic_auth=true"
And I login with user "john" and password "password"
Then I'm redirected to "https://basicauth.test.local:8080/secret.html"
@need-registered-user-john
Scenario: Redirection after first factor fails if basic_auth not allowed. It redirects user to first factor.
When I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html&only_basic_auth=true"
And I login with user "john" and password "password"
Then I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
@need-registered-user-john
Scenario: User is redirected to second factor after first factor
When I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password"
Then I'm redirected to "https://auth.test.local:8080/secondfactor?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"

View File

@ -2,7 +2,7 @@ Feature: User is correctly redirected
Scenario: User is redirected to authelia when he is not authenticated
When I visit "https://public.test.local:8080"
Then I'm redirected to "https://auth.test.local:8080/"
Then I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2F"
@need-registered-user-john
Scenario: User is redirected to home page after several authentication tries

View File

@ -14,6 +14,4 @@ Feature: Authelia keeps user sessions despite the application restart
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
Then I have access to:
| url |
| https://admin.test.local:8080/secret.html |
Then I'm redirected to "https://admin.test.local:8080/secret.html"

View File

@ -6,7 +6,7 @@ import Speakeasy = require("speakeasy");
import CustomWorld = require("../support/world");
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
When(/^I visit "(https:\/\/[a-zA-Z0-9:%.\/=?-]+)"$/, function (link: string) {
When(/^I visit "(https:\/\/[a-zA-Z0-9:%&._\/=?-]+)"$/, function (link: string) {
return this.visit(link);
});
@ -66,10 +66,7 @@ and I use TOTP token handle {stringInDoubleQuotes}",
function hasAccessToSecret(link: string, that: any) {
return that.driver.get(link)
.then(function () {
return that.driver.findElement(seleniumWebdriver.By.tagName("body")).getText()
.then(function (body: string) {
Assert(body.indexOf("This is a very important secret!") > -1, body);
});
return that.waitUntilUrlContains(link);
});
}

View File

@ -12,6 +12,6 @@ Cucumber.defineSupportCode(function ({ Given, When, Then }) {
});
Then("I'm redirected to {stringInDoubleQuotes}", function (link: string) {
return this.driver.wait(seleniumWebdriver.until.urlContains(link), 15000);
return this.waitUntilUrlContains(link);
});
});

View File

@ -55,6 +55,10 @@ function CustomWorld() {
});
};
this.waitUntilUrlContains = function(url: string) {
return this.driver.wait(seleniumWebdriver.until.urlIs(url), 15000);
};
this.loginWithUserPassword = function (username: string, password: string) {
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id("username")), 4000)
.then(function () {

View File

@ -7,13 +7,13 @@ import Assert = require("assert");
describe("test FirstFactorValidator", function () {
it("should validate first factor successfully", () => {
const postPromise = JQueryMock.JQueryDeferredMock();
postPromise.done.yields();
postPromise.done.yields({ redirect: "http://redirect" });
postPromise.done.returns(postPromise);
const jqueryMock = JQueryMock.JQueryMock();
jqueryMock.jquery.post.returns(postPromise);
jqueryMock.jquery.ajax.returns(postPromise);
return FirstFactorValidator.validate("username", "password", jqueryMock.jquery as any);
return FirstFactorValidator.validate("username", "password", "http://redirect", false, jqueryMock.jquery as any);
});
function should_fail_first_factor_validation(errorMessage: string) {
@ -25,9 +25,9 @@ describe("test FirstFactorValidator", function () {
postPromise.done.returns(postPromise);
const jqueryMock = JQueryMock.JQueryMock();
jqueryMock.jquery.post.returns(postPromise);
jqueryMock.jquery.ajax.returns(postPromise);
return FirstFactorValidator.validate("username", "password", jqueryMock.jquery as any)
return FirstFactorValidator.validate("username", "password", "http://redirect", false, jqueryMock.jquery as any)
.then(function () {
return BluebirdPromise.reject(new Error("First factor validation successfully finished while it should have not."));
}, function (err: Error) {

View File

@ -8,346 +8,360 @@ describe("test access control manager", function () {
let accessController: AccessController;
let configuration: ACLConfiguration;
beforeEach(function () {
configuration = {
default_policy: "deny",
any: [],
users: {},
groups: {}
};
accessController = new AccessController(configuration, winston);
describe("configuration is null", function() {
it("should allow access to anything, anywhere for anybody", function() {
configuration = undefined;
accessController = new AccessController(configuration, winston);
Assert(accessController.isAccessAllowed("home.test.local", "/", "user1", ["group1", "group2"]));
Assert(accessController.isAccessAllowed("home.test.local", "/abc", "user1", ["group1", "group2"]));
Assert(accessController.isAccessAllowed("home.test.local", "/", "user2", ["group1", "group2"]));
Assert(accessController.isAccessAllowed("admin.test.local", "/", "user3", ["group3"]));
});
});
describe("check access control with default policy to deny", function () {
describe("configuration is not null", function () {
beforeEach(function () {
configuration.default_policy = "deny";
configuration = {
default_policy: "deny",
any: [],
users: {},
groups: {}
};
accessController = new AccessController(configuration, winston);
});
it("should deny access when no rule is provided", function () {
Assert(!accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
});
it("should control access when multiple domain matcher is provided", function () {
configuration.users["user1"] = [{
domain: "*.mail.example.com",
policy: "allow",
resources: [".*"]
}];
Assert(!accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("mx1.mail.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("mx1.server.mail.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("mail.example.com", "/", "user1", ["group1"]));
});
it("should allow access to all resources when resources is not provided", function () {
configuration.users["user1"] = [{
domain: "*.mail.example.com",
policy: "allow"
}];
Assert(!accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("mx1.mail.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("mx1.server.mail.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("mail.example.com", "/", "user1", ["group1"]));
});
describe("check user rules", function () {
it("should allow access when user has a matching allowing rule", function () {
configuration.users["user1"] = [{
domain: "home.example.com",
policy: "allow",
resources: [".*"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/another/resource", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("another.home.example.com", "/", "user1", ["group1"]));
describe("check access control with default policy to deny", function () {
beforeEach(function () {
configuration.default_policy = "deny";
});
it("should deny to other users", function () {
configuration.users["user1"] = [{
domain: "home.example.com",
policy: "allow",
resources: [".*"]
}];
Assert(!accessController.isAccessAllowed("home.example.com", "/", "user2", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/another/resource", "user2", ["group1"]));
Assert(!accessController.isAccessAllowed("another.home.example.com", "/", "user2", ["group1"]));
it("should deny access when no rule is provided", function () {
Assert(!accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
});
it("should allow user access only to specific resources", function () {
it("should control access when multiple domain matcher is provided", function () {
configuration.users["user1"] = [{
domain: "home.example.com",
domain: "*.mail.example.com",
policy: "allow",
resources: ["/private/.*", "^/begin", "/end$"]
resources: [".*"]
}];
Assert(!accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/private", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/class", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/middle/private/class", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/begin", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/not/begin", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/abc/end", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/abc/end/x", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("mx1.mail.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("mx1.server.mail.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("mail.example.com", "/", "user1", ["group1"]));
});
it("should allow access to multiple domains", function () {
it("should allow access to all resources when resources is not provided", function () {
configuration.users["user1"] = [{
domain: "home.example.com",
policy: "allow",
resources: [".*"]
}, {
domain: "home1.example.com",
policy: "allow",
resources: [".*"]
}, {
domain: "home2.example.com",
policy: "deny",
resources: [".*"]
domain: "*.mail.example.com",
policy: "allow"
}];
Assert(accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home1.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home2.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home3.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("mx1.mail.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("mx1.server.mail.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("mail.example.com", "/", "user1", ["group1"]));
});
it("should always apply latest rule", function () {
configuration.users["user1"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/my/.*"]
}, {
domain: "home.example.com",
policy: "deny",
resources: ["^/my/private/.*"]
}, {
domain: "home.example.com",
policy: "allow",
resources: ["/my/private/resource"]
}];
describe("check user rules", function () {
it("should allow access when user has a matching allowing rule", function () {
configuration.users["user1"] = [{
domain: "home.example.com",
policy: "allow",
resources: [".*"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/another/resource", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("another.home.example.com", "/", "user1", ["group1"]));
});
Assert(accessController.isAccessAllowed("home.example.com", "/my/poney", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/my/private/duck", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/my/private/resource", "user1", ["group1"]));
it("should deny to other users", function () {
configuration.users["user1"] = [{
domain: "home.example.com",
policy: "allow",
resources: [".*"]
}];
Assert(!accessController.isAccessAllowed("home.example.com", "/", "user2", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/another/resource", "user2", ["group1"]));
Assert(!accessController.isAccessAllowed("another.home.example.com", "/", "user2", ["group1"]));
});
it("should allow user access only to specific resources", function () {
configuration.users["user1"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["/private/.*", "^/begin", "/end$"]
}];
Assert(!accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/private", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/class", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/middle/private/class", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/begin", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/not/begin", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/abc/end", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/abc/end/x", "user1", ["group1"]));
});
it("should allow access to multiple domains", function () {
configuration.users["user1"] = [{
domain: "home.example.com",
policy: "allow",
resources: [".*"]
}, {
domain: "home1.example.com",
policy: "allow",
resources: [".*"]
}, {
domain: "home2.example.com",
policy: "deny",
resources: [".*"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home1.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home2.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home3.example.com", "/", "user1", ["group1"]));
});
it("should always apply latest rule", function () {
configuration.users["user1"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/my/.*"]
}, {
domain: "home.example.com",
policy: "deny",
resources: ["^/my/private/.*"]
}, {
domain: "home.example.com",
policy: "allow",
resources: ["/my/private/resource"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/my/poney", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/my/private/duck", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/my/private/resource", "user1", ["group1"]));
});
});
describe("check group rules", function () {
it("should allow access when user is in group having a matching allowing rule", function () {
configuration.groups["group1"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/$"]
}];
configuration.groups["group2"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/test$"]
}, {
domain: "home.example.com",
policy: "deny",
resources: ["^/private$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/", "user1",
["group1", "group2", "group3"]));
Assert(accessController.isAccessAllowed("home.example.com", "/test", "user1",
["group1", "group2", "group3"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/private", "user1",
["group1", "group2", "group3"]));
Assert(!accessController.isAccessAllowed("another.home.example.com", "/", "user1",
["group1", "group2", "group3"]));
});
});
});
describe("check group rules", function () {
it("should allow access when user is in group having a matching allowing rule", function () {
configuration.groups["group1"] = [{
describe("check all rules", function () {
it("should control access when all rules are defined", function () {
configuration.any = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/$"]
}];
configuration.groups["group2"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/test$"]
resources: ["^/public$"]
}, {
domain: "home.example.com",
policy: "deny",
resources: ["^/private$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/", "user1",
["group1", "group2", "group3"]));
Assert(accessController.isAccessAllowed("home.example.com", "/test", "user1",
Assert(accessController.isAccessAllowed("home.example.com", "/public", "user1",
["group1", "group2", "group3"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/private", "user1",
["group1", "group2", "group3"]));
Assert(!accessController.isAccessAllowed("another.home.example.com", "/", "user1",
["group1", "group2", "group3"]));
Assert(accessController.isAccessAllowed("home.example.com", "/public", "user4",
["group5"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/private", "user4",
["group5"]));
});
});
describe("check access control with default policy to allow", function () {
beforeEach(function () {
configuration.default_policy = "allow";
});
it("should allow access to anything when no rule is provided", function () {
Assert(accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/test", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev", "user1", ["group1"]));
});
it("should deny access to one resource when defined", function () {
configuration.users["user1"] = [{
domain: "home.example.com",
policy: "deny",
resources: ["/test"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/test", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev", "user1", ["group1"]));
});
});
describe("check access control with complete use case", function () {
beforeEach(function () {
configuration.default_policy = "deny";
});
it("should control access of multiple user (real use case)", function () {
// Let say we have three users: admin, john, harry.
// admin is in groups ["admins"]
// john is in groups ["dev", "admin-private"]
// harry is in groups ["dev"]
configuration.any = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/public$", "^/$"]
}];
configuration.groups["dev"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/dev/?.*$"]
}];
configuration.groups["admins"] = [{
domain: "home.example.com",
policy: "allow",
resources: [".*"]
}];
configuration.groups["admin-private"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/private/?.*"]
}];
configuration.users["john"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/private/john$"]
}];
configuration.users["harry"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/private/harry"]
}, {
domain: "home.example.com",
policy: "deny",
resources: ["^/dev/b.*$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/public", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev/bob", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/admin", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/josh", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/john", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/harry", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/public", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev/bob", "john", ["dev", "admin-private"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/admin", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/josh", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/john", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/harry", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/", "harry", ["dev"]));
Assert(accessController.isAccessAllowed("home.example.com", "/public", "harry", ["dev"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev", "harry", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/dev/bob", "harry", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/admin", "harry", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/private/josh", "harry", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/private/john", "harry", ["dev"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/harry", "harry", ["dev"]));
});
it("should control access when allowed at group level and denied at user level", function () {
configuration.groups["dev"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/dev/?.*$"]
}];
configuration.users["john"] = [{
domain: "home.example.com",
policy: "deny",
resources: ["^/dev/bob$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/dev/john", "john", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/dev/bob", "john", ["dev"]));
});
it("should control access when allowed at all level and denied at user level", function () {
configuration.any = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/dev/?.*$"]
}];
configuration.users["john"] = [{
domain: "home.example.com",
policy: "deny",
resources: ["^/dev/bob$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/dev/john", "john", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/dev/bob", "john", ["dev"]));
});
it("should control access when allowed at all level and denied at group level", function () {
configuration.any = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/dev/?.*$"]
}];
configuration.groups["dev"] = [{
domain: "home.example.com",
policy: "deny",
resources: ["^/dev/bob$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/dev/john", "john", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/dev/bob", "john", ["dev"]));
});
it("should respect rules precedence", function () {
// the priority from least to most is 'default_policy', 'all', 'group', 'user'
// and the first rules in each category as a lower priority than the latest.
// You can think of it that way: they override themselves inside each category.
configuration.any = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/dev/?.*$"]
}];
configuration.groups["dev"] = [{
domain: "home.example.com",
policy: "deny",
resources: ["^/dev/bob$"]
}];
configuration.users["john"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/dev/?.*$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/dev/john", "john", ["dev"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev/bob", "john", ["dev"]));
});
});
});
describe("check all rules", function () {
it("should control access when all rules are defined", function () {
configuration.any = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/public$"]
}, {
domain: "home.example.com",
policy: "deny",
resources: ["^/private$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/public", "user1",
["group1", "group2", "group3"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/private", "user1",
["group1", "group2", "group3"]));
Assert(accessController.isAccessAllowed("home.example.com", "/public", "user4",
["group5"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/private", "user4",
["group5"]));
});
});
describe("check access control with default policy to allow", function () {
beforeEach(function () {
configuration.default_policy = "allow";
});
it("should allow access to anything when no rule is provided", function () {
Assert(accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/test", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev", "user1", ["group1"]));
});
it("should deny access to one resource when defined", function () {
configuration.users["user1"] = [{
domain: "home.example.com",
policy: "deny",
resources: ["/test"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/", "user1", ["group1"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/test", "user1", ["group1"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev", "user1", ["group1"]));
});
});
describe("check access control with complete use case", function () {
beforeEach(function () {
configuration.default_policy = "deny";
});
it("should control access of multiple user (real use case)", function () {
// Let say we have three users: admin, john, harry.
// admin is in groups ["admins"]
// john is in groups ["dev", "admin-private"]
// harry is in groups ["dev"]
configuration.any = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/public$", "^/$"]
}];
configuration.groups["dev"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/dev/?.*$"]
}];
configuration.groups["admins"] = [{
domain: "home.example.com",
policy: "allow",
resources: [".*"]
}];
configuration.groups["admin-private"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/private/?.*"]
}];
configuration.users["john"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/private/john$"]
}];
configuration.users["harry"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/private/harry"]
}, {
domain: "home.example.com",
policy: "deny",
resources: ["^/dev/b.*$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/public", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev/bob", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/admin", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/josh", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/john", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/harry", "admin", ["admins"]));
Assert(accessController.isAccessAllowed("home.example.com", "/", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/public", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev/bob", "john", ["dev", "admin-private"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/admin", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/josh", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/john", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/harry", "john", ["dev", "admin-private"]));
Assert(accessController.isAccessAllowed("home.example.com", "/", "harry", ["dev"]));
Assert(accessController.isAccessAllowed("home.example.com", "/public", "harry", ["dev"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev", "harry", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/dev/bob", "harry", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/admin", "harry", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/private/josh", "harry", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/private/john", "harry", ["dev"]));
Assert(accessController.isAccessAllowed("home.example.com", "/private/harry", "harry", ["dev"]));
});
it("should control access when allowed at group level and denied at user level", function () {
configuration.groups["dev"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/dev/?.*$"]
}];
configuration.users["john"] = [{
domain: "home.example.com",
policy: "deny",
resources: ["^/dev/bob$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/dev/john", "john", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/dev/bob", "john", ["dev"]));
});
it("should control access when allowed at all level and denied at user level", function () {
configuration.any = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/dev/?.*$"]
}];
configuration.users["john"] = [{
domain: "home.example.com",
policy: "deny",
resources: ["^/dev/bob$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/dev/john", "john", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/dev/bob", "john", ["dev"]));
});
it("should control access when allowed at all level and denied at group level", function () {
configuration.any = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/dev/?.*$"]
}];
configuration.groups["dev"] = [{
domain: "home.example.com",
policy: "deny",
resources: ["^/dev/bob$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/dev/john", "john", ["dev"]));
Assert(!accessController.isAccessAllowed("home.example.com", "/dev/bob", "john", ["dev"]));
});
it("should respect rules precedence", function () {
// the priority from least to most is 'default_policy', 'all', 'group', 'user'
// and the first rules in each category as a lower priority than the latest.
// You can think of it that way: they override themselves inside each category.
configuration.any = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/dev/?.*$"]
}];
configuration.groups["dev"] = [{
domain: "home.example.com",
policy: "deny",
resources: ["^/dev/bob$"]
}];
configuration.users["john"] = [{
domain: "home.example.com",
policy: "allow",
resources: ["^/dev/?.*$"]
}];
Assert(accessController.isAccessAllowed("home.example.com", "/dev/john", "john", ["dev"]));
Assert(accessController.isAccessAllowed("home.example.com", "/dev/bob", "john", ["dev"]));
});
});
});

View File

@ -1,40 +1,40 @@
import sinon = require("sinon");
import Sinon = require("sinon");
import express = require("express");
import winston = require("winston");
import { UserDataStoreStub } from "./storage/UserDataStoreStub";
import { VARIABLES_KEY }  from "../../../../src/server/lib/ServerVariablesHandler";
import { VARIABLES_KEY } from "../../../../src/server/lib/ServerVariablesHandler";
export interface ServerVariablesMock {
logger: any;
ldapAuthenticator: any;
ldapEmailsRetriever: any;
ldapPasswordUpdater: any;
totpValidator: any;
totpGenerator: any;
u2f: any;
userDataStore: UserDataStoreStub;
notifier: any;
regulator: any;
config: any;
accessController: any;
logger: any;
ldapAuthenticator: any;
ldapEmailsRetriever: any;
ldapPasswordUpdater: any;
totpValidator: any;
totpGenerator: any;
u2f: any;
userDataStore: UserDataStoreStub;
notifier: any;
regulator: any;
config: any;
accessController: any;
}
export function mock(app: express.Application): ServerVariablesMock {
const mocks: ServerVariablesMock = {
accessController: sinon.stub(),
config: sinon.stub(),
ldapAuthenticator: sinon.stub() as any,
ldapEmailsRetriever: sinon.stub() as any,
ldapPasswordUpdater: sinon.stub() as any,
logger: winston,
notifier: sinon.stub(),
regulator: sinon.stub(),
totpGenerator: sinon.stub(),
totpValidator: sinon.stub(),
u2f: sinon.stub(),
userDataStore: new UserDataStoreStub()
};
app.get = sinon.stub().withArgs(VARIABLES_KEY).returns(mocks);
return mocks;
const mocks: ServerVariablesMock = {
accessController: Sinon.stub(),
config: Sinon.stub(),
ldapAuthenticator: Sinon.stub() as any,
ldapEmailsRetriever: Sinon.stub() as any,
ldapPasswordUpdater: Sinon.stub() as any,
logger: winston,
notifier: Sinon.stub(),
regulator: Sinon.stub(),
totpGenerator: Sinon.stub(),
totpValidator: Sinon.stub(),
u2f: Sinon.stub(),
userDataStore: new UserDataStoreStub()
};
app.get = Sinon.stub().withArgs(VARIABLES_KEY).returns(mocks);
return mocks;
}

View File

@ -51,6 +51,9 @@ describe("test the first factor validation route", function () {
username: "username",
password: "password"
},
query: {
redirect: "http://redirect.url"
},
session: {
},
headers: {
@ -87,7 +90,6 @@ describe("test the first factor validation route", function () {
.then(function () {
assert.equal("username", authSession.userid);
assert(res.send.calledOnce);
assert(res.status.calledWith(204));
});
});

View File

@ -1,9 +1,9 @@
import assert = require("assert");
import Assert = require("assert");
import VerifyGet = require("../../../../../src/server/lib/routes/verify/get");
import AuthenticationSession = require("../../../../../src/server/lib/AuthenticationSession");
import sinon = require("sinon");
import Sinon = require("sinon");
import winston = require("winston");
import BluebirdPromise = require("bluebird");
@ -24,10 +24,13 @@ describe("test authentication token verification", function () {
req = ExpressMock.RequestMock();
res = ExpressMock.ResponseMock();
req.app = {
get: sinon.stub().returns({ logger: winston })
};
req.session = {};
req.query = {
redirect: "http://redirect.url"
};
req.app = {
get: Sinon.stub().returns({ logger: winston })
};
AuthenticationSession.reset(req as any);
req.headers = {};
req.headers.host = "secret.example.com";
@ -49,95 +52,133 @@ describe("test authentication token verification", function () {
return VerifyGet.default(req as express.Request, res as any);
})
.then(function () {
sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "myuser");
sinon.assert.calledWithExactly(res.setHeader, "Remote-Groups", "mygroup,othergroup");
assert.equal(204, res.status.getCall(0).args[0]);
Sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "myuser");
Sinon.assert.calledWithExactly(res.setHeader, "Remote-Groups", "mygroup,othergroup");
Assert.equal(204, res.status.getCall(0).args[0]);
});
});
describe("given different cases of session", function () {
function test_session(auth_session: AuthenticationSession.AuthenticationSession, status_code: number) {
return VerifyGet.default(req as express.Request, res as any)
.then(function () {
assert.equal(status_code, res.status.getCall(0).args[0]);
function test_session(_authSession: AuthenticationSession.AuthenticationSession, status_code: number) {
return AuthenticationSession.get(req as any)
.then(function (authSession) {
authSession = _authSession;
return VerifyGet.default(req as express.Request, res as any);
})
.then(function () {
Assert.equal(status_code, res.status.getCall(0).args[0]);
});
}
function test_non_authenticated_401(auth_session: AuthenticationSession.AuthenticationSession) {
return test_session(auth_session, 401);
}
function test_unauthorized_403(auth_session: AuthenticationSession.AuthenticationSession) {
return test_session(auth_session, 403);
}
function test_authorized(auth_session: AuthenticationSession.AuthenticationSession) {
return test_session(auth_session, 204);
}
describe("given user tries to access a 2-factor endpoint", function () {
describe("given different cases of session", function () {
it("should not be authenticated when second factor is missing", function () {
return test_non_authenticated_401({
userid: "user",
first_factor: true,
second_factor: false,
email: undefined,
groups: [],
});
}
});
function test_non_authenticated_401(auth_session: AuthenticationSession.AuthenticationSession) {
return test_session(auth_session, 401);
}
it("should not be authenticated when first factor is missing", function () {
return test_non_authenticated_401({
userid: "user",
first_factor: false,
second_factor: true,
email: undefined,
groups: [],
});
});
function test_unauthorized_403(auth_session: AuthenticationSession.AuthenticationSession) {
return test_session(auth_session, 403);
}
it("should not be authenticated when userid is missing", function () {
return test_non_authenticated_401({
userid: undefined,
first_factor: true,
second_factor: false,
email: undefined,
groups: [],
});
});
function test_authorized(auth_session: AuthenticationSession.AuthenticationSession) {
return test_session(auth_session, 204);
}
it("should not be authenticated when first and second factor are missing", function () {
return test_non_authenticated_401({
userid: "user",
first_factor: false,
second_factor: false,
email: undefined,
groups: [],
});
});
it("should not be authenticated when second factor is missing", function () {
return test_non_authenticated_401({
userid: "user",
first_factor: true,
second_factor: false,
email: undefined,
groups: [],
it("should not be authenticated when session has not be initiated", function () {
return test_non_authenticated_401(undefined);
});
it("should not be authenticated when domain is not allowed for user", function () {
return AuthenticationSession.get(req as any)
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
authSession.first_factor = true;
authSession.second_factor = true;
authSession.userid = "myuser";
req.headers.host = "test.example.com";
accessController.isAccessAllowedMock.returns(false);
accessController.isAccessAllowedMock.withArgs("test.example.com", "user", ["group1", "group2"]).returns(true);
return test_unauthorized_403({
first_factor: true,
second_factor: true,
userid: "user",
groups: ["group1", "group2"],
email: undefined
});
});
});
});
});
it("should not be authenticated when first factor is missing", function () {
return test_non_authenticated_401({
userid: "user",
first_factor: false,
second_factor: true,
email: undefined,
groups: [],
});
describe("given user tries to access a basic auth endpoint", function () {
beforeEach(function () {
req.query = {
redirect: "http://redirect.url",
only_basic_auth: "true"
};
});
it("should not be authenticated when userid is missing", function () {
return test_non_authenticated_401({
userid: undefined,
first_factor: true,
second_factor: false,
email: undefined,
groups: [],
});
});
it("should not be authenticated when first and second factor are missing", function () {
return test_non_authenticated_401({
userid: "user",
first_factor: false,
second_factor: false,
email: undefined,
groups: [],
});
});
it("should not be authenticated when session has not be initiated", function () {
return test_non_authenticated_401(undefined);
});
it("should not be authenticated when domain is not allowed for user", function () {
it("should be authenticated when first factor is validated and not second factor", function () {
return AuthenticationSession.get(req as any)
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
authSession.first_factor = true;
authSession.second_factor = true;
authSession.userid = "myuser";
return VerifyGet.default(req as express.Request, res as any);
})
.then(function () {
Assert(res.status.calledWith(204));
Assert(res.send.calledOnce);
});
});
req.headers.host = "test.example.com";
accessController.isAccessAllowedMock.returns(false);
accessController.isAccessAllowedMock.withArgs("test.example.com", "user", ["group1", "group2"]).returns(true);
return test_unauthorized_403({
first_factor: true,
second_factor: true,
userid: "user",
groups: ["group1", "group2"],
email: undefined
});
it("should be rejected with 401 when first factor is not validated", function () {
return AuthenticationSession.get(req as any)
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
authSession.first_factor = false;
return VerifyGet.default(req as express.Request, res as any);
})
.then(function () {
Assert(res.status.calledWith(401));
});
});
});