[BREAKING] Flatten the ACL rules to enable some use cases.
With previous configuration format rules were not ordered between groups and thus not predictable. Also in some cases `any` must have been a higher precedence than `groups`. Flattening the rules let the user apply whatever policy he can think of. When several rules match the (subject, domain, resource), the first one is applied. NOTE: This commit changed the format for declaring ACLs. Be sure to update your configuration file before upgrading.pull/289/head
parent
2bc650fd97
commit
97bfafb6eb
|
@ -29,37 +29,37 @@ totp:
|
||||||
access_control:
|
access_control:
|
||||||
# Default policy can either be `bypass`, `one_factor`, `two_factor` or `deny`.
|
# Default policy can either be `bypass`, `one_factor`, `two_factor` or `deny`.
|
||||||
default_policy: deny
|
default_policy: deny
|
||||||
any:
|
|
||||||
|
rules:
|
||||||
- domain: single_factor.example.com
|
- domain: single_factor.example.com
|
||||||
policy: one_factor
|
policy: one_factor
|
||||||
groups:
|
|
||||||
admins:
|
|
||||||
# All resources in all domains
|
|
||||||
- domain: '*.example.com'
|
|
||||||
policy: two_factor
|
|
||||||
# Except mx2.mail.example.com (it restricts the first rule)
|
|
||||||
#- domain: 'mx2.mail.example.com'
|
|
||||||
# policy: deny
|
|
||||||
|
|
||||||
# User-based rules.
|
- domain: '*.example.com'
|
||||||
users:
|
subject: "group:admins"
|
||||||
john:
|
policy: two_factor
|
||||||
- domain: dev.example.com
|
|
||||||
policy: two_factor
|
- domain: dev.example.com
|
||||||
resources:
|
resources:
|
||||||
- '^/users/john/.*$'
|
- '^/users/john/.*$'
|
||||||
harry:
|
subject: "user:john"
|
||||||
- domain: dev.example.com
|
policy: two_factor
|
||||||
policy: two_factor
|
|
||||||
resources:
|
- domain: dev.example.com
|
||||||
- '^/users/harry/.*$'
|
resources:
|
||||||
bob:
|
- '^/users/harry/.*$'
|
||||||
- domain: '*.mail.example.com'
|
subject: "user:harry"
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
- domain: 'dev.example.com'
|
|
||||||
policy: two_factor
|
- domain: '*.mail.example.com'
|
||||||
resources:
|
subject: "user:bob"
|
||||||
- '^/users/bob/.*$'
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- '^/users/bob/.*$'
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
|
||||||
# Configuration of the authentication regulation mechanism.
|
# Configuration of the authentication regulation mechanism.
|
||||||
regulation:
|
regulation:
|
||||||
|
|
|
@ -86,112 +86,96 @@ authentication_backend:
|
||||||
## path: ./users_database.yml
|
## path: ./users_database.yml
|
||||||
|
|
||||||
|
|
||||||
# Authentication methods
|
|
||||||
#
|
|
||||||
# Authentication methods can be defined per subdomain.
|
|
||||||
# There are currently two available methods: "single_factor" and "two_factor"
|
|
||||||
#
|
|
||||||
# Note: by default a domain uses "two_factor" method.
|
|
||||||
#
|
|
||||||
# Note: 'per_subdomain_methods' is a dictionary where keys must be subdomains and
|
|
||||||
# values must be one of the two possible methods.
|
|
||||||
#
|
|
||||||
# Note: 'per_subdomain_methods' is optional.
|
|
||||||
#
|
|
||||||
# Note: authentication_methods is optional. If it is not set all sub-domains
|
|
||||||
# are protected by two factors.
|
|
||||||
authentication_methods:
|
|
||||||
default_method: two_factor
|
|
||||||
per_subdomain_methods:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Access Control
|
# Access Control
|
||||||
#
|
#
|
||||||
# Access control is a set of rules you can use to restrict user access to certain
|
# Access control is a list of rules defining the authorizations applied for one
|
||||||
# resources.
|
# resource to users or group of users.
|
||||||
# Any (apply to anyone), per-user or per-group rules can be defined.
|
|
||||||
#
|
#
|
||||||
# If 'access_control' is not defined, ACL rules are disabled and the `allow` default
|
# If 'access_control' is not defined, ACL rules are disabled and the `bypass`
|
||||||
# policy is applied, i.e., access is allowed to anyone. Otherwise restrictions follow
|
# rule is applied, i.e., access is allowed to anyone. Otherwise restrictions follow
|
||||||
# the rules defined.
|
# the rules defined.
|
||||||
#
|
#
|
||||||
# Note: One can use the wildcard * to match any subdomain.
|
# Note: One can use the wildcard * to match any subdomain.
|
||||||
# It must stand at the beginning of the pattern. (example: *.mydomain.com)
|
# It must stand at the beginning of the pattern. (example: *.mydomain.com)
|
||||||
#
|
#
|
||||||
# Note: You must put the pattern in simple quotes when using the wildcard for the YAML
|
# Note: You must put patterns containing wildcards between simple quotes for the YAML
|
||||||
# to be syntaxically correct.
|
# to be syntaxically correct.
|
||||||
#
|
#
|
||||||
# Definition: A `rule` is an object with the following keys: `domain`, `policy`
|
# Definition: A `rule` is an object with the following keys: `domain`, `subject`,
|
||||||
# and `resources`.
|
# `policy` and `resources`.
|
||||||
|
#
|
||||||
# - `domain` defines which domain or set of domains the rule applies to.
|
# - `domain` defines which domain or set of domains the rule applies to.
|
||||||
# - `policy` is the policy to apply to resources. It must be either `allow` or `deny`.
|
|
||||||
# - `resources` is a list of regular expressions that matches a set of resources to
|
|
||||||
# apply the policy to.
|
|
||||||
#
|
#
|
||||||
# Note: Rules follow an order of priority defined as follows:
|
# - `subject` defines the subject to apply authorizations to. This parameter is
|
||||||
# In each category (`any`, `groups`, `users`), the latest rules have the highest
|
# optional and matching any user if not provided. If provided, the parameter
|
||||||
# priority. In other words, it means that if a given resource matches two rules in the
|
# represents either a user or a group. It should be of the form 'user:<username>'
|
||||||
# same category, the latest one overrides the first one.
|
# or 'group:<groupname>'.
|
||||||
# Each category has also its own priority. That is, `users` has the highest priority, then
|
|
||||||
# `groups` and `any` has the lowest priority. It means if two rules in different categories
|
|
||||||
# match a given resource, the one in the category with the highest priority overrides the
|
|
||||||
# other one.
|
|
||||||
#
|
#
|
||||||
|
# - `policy` is the policy to apply to resources. It must be either `bypass`,
|
||||||
|
# `one_factor`, `two_factor` or `deny`.
|
||||||
|
#
|
||||||
|
# - `resources` is a list of regular expressions that matches a set of resources to
|
||||||
|
# apply the policy to. This parameter is optional and matches any resource if not
|
||||||
|
# provided.
|
||||||
|
#
|
||||||
|
# Note: the order of the rules is important. The first policy matching
|
||||||
|
# (domain, resource, subject) applies.
|
||||||
access_control:
|
access_control:
|
||||||
# Default policy can either be `allow` or `deny`.
|
# Default policy can either be `bypass`, `one_factor`, `two_factor` or `deny`.
|
||||||
# It is the policy applied to any resource if it has not been overriden
|
# It is the policy applied to any resource if there is no policy to be applied
|
||||||
# in the `any`, `groups` or `users` category.
|
# to the user.
|
||||||
default_policy: deny
|
default_policy: deny
|
||||||
|
|
||||||
# The rules that apply to anyone.
|
rules:
|
||||||
# The value is a list of rules.
|
# Rules applied to everyone
|
||||||
any:
|
|
||||||
- domain: public.example.com
|
- domain: public.example.com
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
- domain: single_factor.example.com
|
- domain: single_factor.example.com
|
||||||
policy: one_factor
|
policy: one_factor
|
||||||
|
|
||||||
# Group-based rules. The key is a group name and the value
|
# Rules applied to 'admin' group
|
||||||
# is a list of rules.
|
- domain: 'mx2.mail.example.com'
|
||||||
groups:
|
subject: 'group:admin'
|
||||||
admin:
|
policy: deny
|
||||||
# All resources in all domains
|
- domain: '*.example.com'
|
||||||
- domain: '*.example.com'
|
subject: 'group:admin'
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
# Except mx2.mail.example.com (it restricts the first rule)
|
|
||||||
- domain: 'mx2.mail.example.com'
|
# Rules applied to 'dev' group
|
||||||
policy: deny
|
- domain: dev.example.com
|
||||||
dev:
|
resources:
|
||||||
- domain: dev.example.com
|
- '^/groups/dev/.*$'
|
||||||
policy: two_factor
|
subject: 'group:dev'
|
||||||
resources:
|
policy: two_factor
|
||||||
- '^/groups/dev/.*$'
|
|
||||||
|
# Rules applied to user 'john'
|
||||||
# User-based rules. The key is a user name and the value
|
- domain: dev.example.com
|
||||||
# is a list of rules.
|
resources:
|
||||||
users:
|
- '^/users/john/.*$'
|
||||||
john:
|
subject: 'user:john'
|
||||||
- domain: dev.example.com
|
policy: two_factor
|
||||||
policy: two_factor
|
|
||||||
resources:
|
|
||||||
- '^/users/john/.*$'
|
# Rules applied to user 'harry'
|
||||||
harry:
|
- domain: dev.example.com
|
||||||
- domain: dev.example.com
|
resources:
|
||||||
policy: two_factor
|
- '^/users/harry/.*$'
|
||||||
resources:
|
subject: 'user:harry'
|
||||||
- '^/users/harry/.*$'
|
policy: two_factor
|
||||||
bob:
|
|
||||||
- domain: '*.mail.example.com'
|
# Rules applied to user 'bob'
|
||||||
policy: two_factor
|
- domain: '*.mail.example.com'
|
||||||
- domain: 'dev.example.com'
|
subject: 'user:bob'
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
resources:
|
- domain: 'dev.example.com'
|
||||||
- '^/users/bob/.*$'
|
resources:
|
||||||
|
- '^/users/bob/.*$'
|
||||||
|
subject: 'user:bob'
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
|
||||||
# Configuration of session cookies
|
# Configuration of session cookies
|
||||||
#
|
#
|
||||||
# The session cookies identify the user once logged in.
|
# The session cookies identify the user once logged in.
|
||||||
session:
|
session:
|
||||||
# The name of the session cookie. (default: authelia_session).
|
# The name of the session cookie. (default: authelia_session).
|
||||||
|
@ -199,7 +183,7 @@ session:
|
||||||
|
|
||||||
# The secret to encrypt the session cookie.
|
# The secret to encrypt the session cookie.
|
||||||
secret: unsecure_session_secret
|
secret: unsecure_session_secret
|
||||||
|
|
||||||
# The time in ms before the cookie expires and session is reset.
|
# The time in ms before the cookie expires and session is reset.
|
||||||
expiration: 3600000 # 1 hour
|
expiration: 3600000 # 1 hour
|
||||||
|
|
||||||
|
@ -208,9 +192,9 @@ session:
|
||||||
|
|
||||||
# The domain to protect.
|
# The domain to protect.
|
||||||
# Note: the authenticator must also be in that domain. If empty, the cookie
|
# Note: the authenticator must also be in that domain. If empty, the cookie
|
||||||
# is restricted to the subdomain of the issuer.
|
# is restricted to the subdomain of the issuer.
|
||||||
domain: example.com
|
domain: example.com
|
||||||
|
|
||||||
# The redis connection details
|
# The redis connection details
|
||||||
redis:
|
redis:
|
||||||
host: redis
|
host: redis
|
||||||
|
@ -223,12 +207,12 @@ session:
|
||||||
# It bans the user if too many attempts are done in a short period of
|
# It bans the user if too many attempts are done in a short period of
|
||||||
# time.
|
# time.
|
||||||
regulation:
|
regulation:
|
||||||
# The number of failed login attempts before user is banned.
|
# The number of failed login attempts before user is banned.
|
||||||
# Set it to 0 to disable regulation.
|
# Set it to 0 to disable regulation.
|
||||||
max_retries: 3
|
max_retries: 3
|
||||||
|
|
||||||
# The time range during which the user can attempt login before being banned.
|
# The time range during which the user can attempt login before being banned.
|
||||||
# The user is banned if the authenticaction failed `max_retries` times in a `find_time` seconds window.
|
# The user is banned if the authenticaction failed `max_retries` times in a `find_time` seconds window.
|
||||||
find_time: 120
|
find_time: 120
|
||||||
|
|
||||||
# The length of time before a banned user can login again.
|
# The length of time before a banned user can login again.
|
||||||
|
@ -241,7 +225,7 @@ storage:
|
||||||
# The directory where the DB files will be saved
|
# The directory where the DB files will be saved
|
||||||
## local:
|
## local:
|
||||||
## path: /var/lib/authelia/store
|
## path: /var/lib/authelia/store
|
||||||
|
|
||||||
# Settings to connect to mongo server
|
# Settings to connect to mongo server
|
||||||
mongo:
|
mongo:
|
||||||
url: mongodb://mongo
|
url: mongodb://mongo
|
||||||
|
@ -261,13 +245,13 @@ notifier:
|
||||||
## filename: /tmp/authelia/notification.txt
|
## filename: /tmp/authelia/notification.txt
|
||||||
|
|
||||||
# Use your email account to send the notifications. You can use an app password.
|
# Use your email account to send the notifications. You can use an app password.
|
||||||
# List of valid services can be found here: https://nodemailer.com/smtp/well-known/
|
# List of valid services can be found here: https://nodemailer.com/smtp/well-known/
|
||||||
## email:
|
## email:
|
||||||
## username: user@example.com
|
## username: user@example.com
|
||||||
## password: yourpassword
|
## password: yourpassword
|
||||||
## sender: admin@example.com
|
## sender: admin@example.com
|
||||||
## service: gmail
|
## service: gmail
|
||||||
|
|
||||||
# Use a SMTP server for sending notifications
|
# Use a SMTP server for sending notifications
|
||||||
smtp:
|
smtp:
|
||||||
username: test
|
username: test
|
||||||
|
|
|
@ -102,7 +102,7 @@ export function get_start_validation(handler: IdentityValidable,
|
||||||
let identity: Identity.Identity;
|
let identity: Identity.Identity;
|
||||||
|
|
||||||
return handler.preValidationInit(req)
|
return handler.preValidationInit(req)
|
||||||
.then(function (id: Identity.Identity) {
|
.then((id: Identity.Identity) => {
|
||||||
identity = id;
|
identity = id;
|
||||||
const email = identity.email;
|
const email = identity.email;
|
||||||
const userid = identity.userid;
|
const userid = identity.userid;
|
||||||
|
@ -116,7 +116,7 @@ export function get_start_validation(handler: IdentityValidable,
|
||||||
return createAndSaveToken(userid, handler.challenge(),
|
return createAndSaveToken(userid, handler.challenge(),
|
||||||
vars.userDataStore);
|
vars.userDataStore);
|
||||||
})
|
})
|
||||||
.then(function (token: string) {
|
.then((token) => {
|
||||||
const host = req.get("Host");
|
const host = req.get("Host");
|
||||||
const link_url = util.format("https://%s%s?identity_token=%s", host,
|
const link_url = util.format("https://%s%s?identity_token=%s", host,
|
||||||
postValidationEndpoint, token);
|
postValidationEndpoint, token);
|
||||||
|
@ -125,11 +125,11 @@ export function get_start_validation(handler: IdentityValidable,
|
||||||
return vars.notifier.notify(identity.email, handler.mailSubject(),
|
return vars.notifier.notify(identity.email, handler.mailSubject(),
|
||||||
link_url);
|
link_url);
|
||||||
})
|
})
|
||||||
.then(function () {
|
.then(() => {
|
||||||
handler.preValidationResponse(req, res);
|
handler.preValidationResponse(req, res);
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(Exceptions.IdentityError, function (err: Error) {
|
.catch(Exceptions.IdentityError, (err: Error) => {
|
||||||
handler.preValidationResponse(req, res);
|
handler.preValidationResponse(req, res);
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
|
|
|
@ -25,9 +25,7 @@ describe("authorization/Authorizer", function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
configuration = {
|
configuration = {
|
||||||
default_policy: "deny",
|
default_policy: "deny",
|
||||||
any: [],
|
rules: []
|
||||||
users: {},
|
|
||||||
groups: {}
|
|
||||||
};
|
};
|
||||||
authorizer = new Authorizer(configuration, winston);
|
authorizer = new Authorizer(configuration, winston);
|
||||||
});
|
});
|
||||||
|
@ -42,9 +40,10 @@ describe("authorization/Authorizer", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should control access when multiple domain matcher is provided", function () {
|
it("should control access when multiple domain matcher is provided", function () {
|
||||||
configuration.users["user1"] = [{
|
configuration.rules = [{
|
||||||
domain: "*.mail.example.com",
|
domain: "*.mail.example.com",
|
||||||
policy: "two_factor",
|
policy: "two_factor",
|
||||||
|
subject: "user:user1",
|
||||||
resources: [".*"]
|
resources: [".*"]
|
||||||
}];
|
}];
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/", "user1", ["group1"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/", "user1", ["group1"]), Level.DENY);
|
||||||
|
@ -54,9 +53,10 @@ describe("authorization/Authorizer", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow access to all resources when resources is not provided", function () {
|
it("should allow access to all resources when resources is not provided", function () {
|
||||||
configuration.users["user1"] = [{
|
configuration.rules = [{
|
||||||
domain: "*.mail.example.com",
|
domain: "*.mail.example.com",
|
||||||
policy: "two_factor"
|
policy: "two_factor",
|
||||||
|
subject: "user:user1"
|
||||||
}];
|
}];
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/", "user1", ["group1"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/", "user1", ["group1"]), Level.DENY);
|
||||||
Assert.equal(authorizer.authorization("mx1.mail.example.com", "/", "user1", ["group1"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("mx1.mail.example.com", "/", "user1", ["group1"]), Level.TWO_FACTOR);
|
||||||
|
@ -66,10 +66,11 @@ describe("authorization/Authorizer", function () {
|
||||||
|
|
||||||
describe("check user rules", function () {
|
describe("check user rules", function () {
|
||||||
it("should allow access when user has a matching allowing rule", function () {
|
it("should allow access when user has a matching allowing rule", function () {
|
||||||
configuration.users["user1"] = [{
|
configuration.rules = [{
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "two_factor",
|
policy: "two_factor",
|
||||||
resources: [".*"]
|
resources: [".*"],
|
||||||
|
subject: "user:user1"
|
||||||
}];
|
}];
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/", "user1", ["group1"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/", "user1", ["group1"]), Level.TWO_FACTOR);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/another/resource", "user1", ["group1"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/another/resource", "user1", ["group1"]), Level.TWO_FACTOR);
|
||||||
|
@ -77,10 +78,11 @@ describe("authorization/Authorizer", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should deny to other users", function () {
|
it("should deny to other users", function () {
|
||||||
configuration.users["user1"] = [{
|
configuration.rules = [{
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "two_factor",
|
policy: "two_factor",
|
||||||
resources: [".*"]
|
resources: [".*"],
|
||||||
|
subject: "user:user1"
|
||||||
}];
|
}];
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/", "user2", ["group1"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/", "user2", ["group1"]), Level.DENY);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/another/resource", "user2", ["group1"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/another/resource", "user2", ["group1"]), Level.DENY);
|
||||||
|
@ -88,10 +90,11 @@ describe("authorization/Authorizer", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow user access only to specific resources", function () {
|
it("should allow user access only to specific resources", function () {
|
||||||
configuration.users["user1"] = [{
|
configuration.rules = [{
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "two_factor",
|
policy: "two_factor",
|
||||||
resources: ["/private/.*", "^/begin", "/end$"]
|
resources: ["/private/.*", "^/begin", "/end$"],
|
||||||
|
subject: "user:user1"
|
||||||
}];
|
}];
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/", "user1", ["group1"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/", "user1", ["group1"]), Level.DENY);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/private", "user1", ["group1"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/private", "user1", ["group1"]), Level.DENY);
|
||||||
|
@ -106,18 +109,21 @@ describe("authorization/Authorizer", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow access to multiple domains", function () {
|
it("should allow access to multiple domains", function () {
|
||||||
configuration.users["user1"] = [{
|
configuration.rules = [{
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "two_factor",
|
policy: "two_factor",
|
||||||
resources: [".*"]
|
resources: [".*"],
|
||||||
|
subject: "user:user1"
|
||||||
}, {
|
}, {
|
||||||
domain: "home1.example.com",
|
domain: "home1.example.com",
|
||||||
policy: "one_factor",
|
policy: "one_factor",
|
||||||
resources: [".*"]
|
resources: [".*"],
|
||||||
|
subject: "user:user1"
|
||||||
}, {
|
}, {
|
||||||
domain: "home2.example.com",
|
domain: "home2.example.com",
|
||||||
policy: "deny",
|
policy: "deny",
|
||||||
resources: [".*"]
|
resources: [".*"],
|
||||||
|
subject: "user:user1"
|
||||||
}];
|
}];
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/", "user1", ["group1"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/", "user1", ["group1"]), Level.TWO_FACTOR);
|
||||||
Assert.equal(authorizer.authorization("home1.example.com", "/", "user1", ["group1"]), Level.ONE_FACTOR);
|
Assert.equal(authorizer.authorization("home1.example.com", "/", "user1", ["group1"]), Level.ONE_FACTOR);
|
||||||
|
@ -125,19 +131,22 @@ describe("authorization/Authorizer", function () {
|
||||||
Assert.equal(authorizer.authorization("home3.example.com", "/", "user1", ["group1"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home3.example.com", "/", "user1", ["group1"]), Level.DENY);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should always apply latest rule", function () {
|
it("should apply rules in order", function () {
|
||||||
configuration.users["user1"] = [{
|
configuration.rules = [{
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "two_factor",
|
policy: "one_factor",
|
||||||
resources: ["^/my/.*"]
|
resources: ["/my/private/resource"],
|
||||||
|
subject: "user:user1"
|
||||||
}, {
|
}, {
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "deny",
|
policy: "deny",
|
||||||
resources: ["^/my/private/.*"]
|
resources: ["^/my/private/.*"],
|
||||||
|
subject: "user:user1"
|
||||||
}, {
|
}, {
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "one_factor",
|
policy: "two_factor",
|
||||||
resources: ["/my/private/resource"]
|
resources: ["^/my/.*"],
|
||||||
|
subject: "user:user1"
|
||||||
}];
|
}];
|
||||||
|
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/my/poney", "user1", ["group1"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/my/poney", "user1", ["group1"]), Level.TWO_FACTOR);
|
||||||
|
@ -148,19 +157,21 @@ describe("authorization/Authorizer", function () {
|
||||||
|
|
||||||
describe("check group rules", function () {
|
describe("check group rules", function () {
|
||||||
it("should allow access when user is in group having a matching allowing rule", function () {
|
it("should allow access when user is in group having a matching allowing rule", function () {
|
||||||
configuration.groups["group1"] = [{
|
configuration.rules = [{
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "two_factor",
|
policy: "two_factor",
|
||||||
resources: ["^/$"]
|
resources: ["^/$"],
|
||||||
}];
|
subject: "group:group1"
|
||||||
configuration.groups["group2"] = [{
|
}, {
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "one_factor",
|
policy: "one_factor",
|
||||||
resources: ["^/test$"]
|
resources: ["^/test$"],
|
||||||
|
subject: "group:group2"
|
||||||
}, {
|
}, {
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "deny",
|
policy: "deny",
|
||||||
resources: ["^/private$"]
|
resources: ["^/private$"],
|
||||||
|
subject: "group:group2"
|
||||||
}];
|
}];
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/", "user1",
|
Assert.equal(authorizer.authorization("home.example.com", "/", "user1",
|
||||||
["group1", "group2", "group3"]), Level.TWO_FACTOR);
|
["group1", "group2", "group3"]), Level.TWO_FACTOR);
|
||||||
|
@ -176,9 +187,9 @@ describe("authorization/Authorizer", function () {
|
||||||
|
|
||||||
describe("check any rules", function () {
|
describe("check any rules", function () {
|
||||||
it("should control access when any rules are defined", function () {
|
it("should control access when any rules are defined", function () {
|
||||||
configuration.any = [{
|
configuration.rules = [{
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "two_factor",
|
policy: "bypass",
|
||||||
resources: ["^/public$"]
|
resources: ["^/public$"]
|
||||||
}, {
|
}, {
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
|
@ -186,11 +197,11 @@ describe("authorization/Authorizer", function () {
|
||||||
resources: ["^/private$"]
|
resources: ["^/private$"]
|
||||||
}];
|
}];
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/public", "user1",
|
Assert.equal(authorizer.authorization("home.example.com", "/public", "user1",
|
||||||
["group1", "group2", "group3"]), Level.TWO_FACTOR);
|
["group1", "group2", "group3"]), Level.BYPASS);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/private", "user1",
|
Assert.equal(authorizer.authorization("home.example.com", "/private", "user1",
|
||||||
["group1", "group2", "group3"]), Level.DENY);
|
["group1", "group2", "group3"]), Level.DENY);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/public", "user4",
|
Assert.equal(authorizer.authorization("home.example.com", "/public", "user4",
|
||||||
["group5"]), Level.TWO_FACTOR);
|
["group5"]), Level.BYPASS);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/private", "user4",
|
Assert.equal(authorizer.authorization("home.example.com", "/private", "user4",
|
||||||
["group5"]), Level.DENY);
|
["group5"]), Level.DENY);
|
||||||
});
|
});
|
||||||
|
@ -208,10 +219,11 @@ describe("authorization/Authorizer", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should deny access to one resource when defined", function () {
|
it("should deny access to one resource when defined", function () {
|
||||||
configuration.users["user1"] = [{
|
configuration.rules = [{
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "deny",
|
policy: "deny",
|
||||||
resources: ["/test"]
|
resources: ["/test"],
|
||||||
|
subject: "user:user1"
|
||||||
}];
|
}];
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/", "user1", ["group1"]), Level.BYPASS);
|
Assert.equal(authorizer.authorization("home.example.com", "/", "user1", ["group1"]), Level.BYPASS);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/test", "user1", ["group1"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/test", "user1", ["group1"]), Level.DENY);
|
||||||
|
@ -229,39 +241,30 @@ describe("authorization/Authorizer", function () {
|
||||||
// admin is in groups ["admins"]
|
// admin is in groups ["admins"]
|
||||||
// john is in groups ["dev", "admin-private"]
|
// john is in groups ["dev", "admin-private"]
|
||||||
// harry is in groups ["dev"]
|
// harry is in groups ["dev"]
|
||||||
configuration.any = [{
|
configuration.rules = [{
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "two_factor",
|
policy: "two_factor",
|
||||||
resources: ["^/public$", "^/$"]
|
resources: ["^/public$", "^/$"]
|
||||||
}];
|
|
||||||
configuration.groups["dev"] = [{
|
|
||||||
domain: "home.example.com",
|
|
||||||
policy: "two_factor",
|
|
||||||
resources: ["^/dev/?.*$"]
|
|
||||||
}];
|
|
||||||
configuration.groups["admins"] = [{
|
|
||||||
domain: "home.example.com",
|
|
||||||
policy: "two_factor",
|
|
||||||
resources: [".*"]
|
|
||||||
}];
|
|
||||||
configuration.groups["admin-private"] = [{
|
|
||||||
domain: "home.example.com",
|
|
||||||
policy: "two_factor",
|
|
||||||
resources: ["^/private/?.*"]
|
|
||||||
}];
|
|
||||||
configuration.users["john"] = [{
|
|
||||||
domain: "home.example.com",
|
|
||||||
policy: "two_factor",
|
|
||||||
resources: ["^/private/john$"]
|
|
||||||
}];
|
|
||||||
configuration.users["harry"] = [{
|
|
||||||
domain: "home.example.com",
|
|
||||||
policy: "two_factor",
|
|
||||||
resources: ["^/private/harry"]
|
|
||||||
}, {
|
}, {
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "deny",
|
policy: "two_factor",
|
||||||
resources: ["^/dev/b.*$"]
|
resources: [".*"],
|
||||||
|
subject: "group:admins"
|
||||||
|
}, {
|
||||||
|
domain: "home.example.com",
|
||||||
|
policy: "two_factor",
|
||||||
|
resources: ["^/private/?.*"],
|
||||||
|
subject: "group:admin-private"
|
||||||
|
}, {
|
||||||
|
domain: "home.example.com",
|
||||||
|
policy: "two_factor",
|
||||||
|
resources: ["^/private/john$"],
|
||||||
|
subject: "user:john"
|
||||||
|
}, {
|
||||||
|
domain: "home.example.com",
|
||||||
|
policy: "two_factor",
|
||||||
|
resources: ["^/private/harry"],
|
||||||
|
subject: "user:harry"
|
||||||
}];
|
}];
|
||||||
|
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/", "admin", ["admins"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/", "admin", ["admins"]), Level.TWO_FACTOR);
|
||||||
|
@ -275,8 +278,8 @@ describe("authorization/Authorizer", function () {
|
||||||
|
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/", "john", ["dev", "admin-private"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/", "john", ["dev", "admin-private"]), Level.TWO_FACTOR);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/public", "john", ["dev", "admin-private"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/public", "john", ["dev", "admin-private"]), Level.TWO_FACTOR);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/dev", "john", ["dev", "admin-private"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/dev", "john", ["dev", "admin-private"]), Level.DENY);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/dev/bob", "john", ["dev", "admin-private"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/dev/bob", "john", ["dev", "admin-private"]), Level.DENY);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/admin", "john", ["dev", "admin-private"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/admin", "john", ["dev", "admin-private"]), Level.DENY);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/private/josh", "john", ["dev", "admin-private"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/private/josh", "john", ["dev", "admin-private"]), Level.TWO_FACTOR);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/private/john", "john", ["dev", "admin-private"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/private/john", "john", ["dev", "admin-private"]), Level.TWO_FACTOR);
|
||||||
|
@ -284,7 +287,7 @@ describe("authorization/Authorizer", function () {
|
||||||
|
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/", "harry", ["dev"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/", "harry", ["dev"]), Level.TWO_FACTOR);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/public", "harry", ["dev"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/public", "harry", ["dev"]), Level.TWO_FACTOR);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/dev", "harry", ["dev"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/dev", "harry", ["dev"]), Level.DENY);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/dev/bob", "harry", ["dev"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/dev/bob", "harry", ["dev"]), Level.DENY);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/admin", "harry", ["dev"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/admin", "harry", ["dev"]), Level.DENY);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/private/josh", "harry", ["dev"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/private/josh", "harry", ["dev"]), Level.DENY);
|
||||||
|
@ -292,49 +295,50 @@ describe("authorization/Authorizer", function () {
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/private/harry", "harry", ["dev"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/private/harry", "harry", ["dev"]), Level.TWO_FACTOR);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should control access when allowed at group level and denied at user level", function () {
|
it("should allow when allowed at group level and denied at user level", function () {
|
||||||
configuration.groups["dev"] = [{
|
configuration.rules = [{
|
||||||
domain: "home.example.com",
|
|
||||||
policy: "two_factor",
|
|
||||||
resources: ["^/dev/?.*$"]
|
|
||||||
}];
|
|
||||||
configuration.users["john"] = [{
|
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "deny",
|
policy: "deny",
|
||||||
resources: ["^/dev/bob$"]
|
resources: ["^/dev/bob$"],
|
||||||
|
subject: "user:john"
|
||||||
|
}, {
|
||||||
|
domain: "home.example.com",
|
||||||
|
policy: "two_factor",
|
||||||
|
resources: ["^/dev/?.*$"],
|
||||||
|
subject: "group:dev"
|
||||||
}];
|
}];
|
||||||
|
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/dev/john", "john", ["dev"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/dev/john", "john", ["dev"]), Level.TWO_FACTOR);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/dev/bob", "john", ["dev"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/dev/bob", "john", ["dev"]), Level.DENY);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should control access when allowed at 'any' level and denied at user level", function () {
|
it("should allow access when allowed at 'any' level and denied at user level", function () {
|
||||||
configuration.any = [{
|
configuration.rules = [{
|
||||||
|
domain: "home.example.com",
|
||||||
|
policy: "deny",
|
||||||
|
resources: ["^/dev/bob$"],
|
||||||
|
subject: "user:john"
|
||||||
|
}, {
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "two_factor",
|
policy: "two_factor",
|
||||||
resources: ["^/dev/?.*$"]
|
resources: ["^/dev/?.*$"]
|
||||||
}];
|
}];
|
||||||
configuration.users["john"] = [{
|
|
||||||
domain: "home.example.com",
|
|
||||||
policy: "deny",
|
|
||||||
resources: ["^/dev/bob$"]
|
|
||||||
}];
|
|
||||||
|
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/dev/john", "john", ["dev"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/dev/john", "john", ["dev"]), Level.TWO_FACTOR);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/dev/bob", "john", ["dev"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/dev/bob", "john", ["dev"]), Level.DENY);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should control access when allowed at 'any' level and denied at group level", function () {
|
it("should allow access when allowed at 'any' level and denied at group level", function () {
|
||||||
configuration.any = [{
|
configuration.rules = [{
|
||||||
|
domain: "home.example.com",
|
||||||
|
policy: "deny",
|
||||||
|
resources: ["^/dev/bob$"],
|
||||||
|
subject: "group:dev"
|
||||||
|
}, {
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "two_factor",
|
policy: "two_factor",
|
||||||
resources: ["^/dev/?.*$"]
|
resources: ["^/dev/?.*$"]
|
||||||
}];
|
}];
|
||||||
configuration.groups["dev"] = [{
|
|
||||||
domain: "home.example.com",
|
|
||||||
policy: "deny",
|
|
||||||
resources: ["^/dev/bob$"]
|
|
||||||
}];
|
|
||||||
|
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/dev/john", "john", ["dev"]), Level.TWO_FACTOR);
|
Assert.equal(authorizer.authorization("home.example.com", "/dev/john", "john", ["dev"]), Level.TWO_FACTOR);
|
||||||
Assert.equal(authorizer.authorization("home.example.com", "/dev/bob", "john", ["dev"]), Level.DENY);
|
Assert.equal(authorizer.authorization("home.example.com", "/dev/bob", "john", ["dev"]), Level.DENY);
|
||||||
|
@ -344,17 +348,17 @@ describe("authorization/Authorizer", function () {
|
||||||
// the priority from least to most is 'default_policy', 'all', 'group', 'user'
|
// 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.
|
// 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.
|
// You can think of it that way: they override themselves inside each category.
|
||||||
configuration.any = [{
|
configuration.rules = [{
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "two_factor",
|
policy: "two_factor",
|
||||||
resources: ["^/dev/?.*$"]
|
resources: ["^/dev/?.*$"],
|
||||||
}];
|
subject: "user:john"
|
||||||
configuration.groups["dev"] = [{
|
}, {
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "deny",
|
policy: "deny",
|
||||||
resources: ["^/dev/bob$"]
|
resources: ["^/dev/bob$"],
|
||||||
}];
|
subject: "group:dev"
|
||||||
configuration.users["john"] = [{
|
}, {
|
||||||
domain: "home.example.com",
|
domain: "home.example.com",
|
||||||
policy: "two_factor",
|
policy: "two_factor",
|
||||||
resources: ["^/dev/?.*$"]
|
resources: ["^/dev/?.*$"]
|
||||||
|
|
|
@ -24,6 +24,24 @@ function MatchResource(actualResource: string) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function MatchSubject(user: string, groups: string[]) {
|
||||||
|
return (rule: ACLRule) => {
|
||||||
|
// If no subject, matches anybody
|
||||||
|
if (!rule.subject) return true;
|
||||||
|
|
||||||
|
if (rule.subject.startsWith("user:")) {
|
||||||
|
const ruleUser = rule.subject.split(":")[1];
|
||||||
|
if (user == ruleUser) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.subject.startsWith("group:")) {
|
||||||
|
const ruleGroup = rule.subject.split(":")[1];
|
||||||
|
if (groups.indexOf(ruleGroup) > -1) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export class Authorizer implements IAuthorizer {
|
export class Authorizer implements IAuthorizer {
|
||||||
private logger: Winston;
|
private logger: Winston;
|
||||||
private readonly configuration: ACLConfiguration;
|
private readonly configuration: ACLConfiguration;
|
||||||
|
@ -33,39 +51,16 @@ export class Authorizer implements IAuthorizer {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMatchingUserRules(user: string, domain: string, resource: string): ACLRule[] {
|
private getMatchingRules(domain: string, resource: string, user: string, groups: string[]): ACLRule[] {
|
||||||
const userRules = this.configuration.users[user];
|
const rules = this.configuration.rules;
|
||||||
if (!userRules) return [];
|
|
||||||
return userRules.filter(MatchDomain(domain)).filter(MatchResource(resource));
|
|
||||||
}
|
|
||||||
|
|
||||||
private getMatchingGroupRules(groups: string[], domain: string, resource: string): ACLRule[] {
|
|
||||||
const that = this;
|
|
||||||
// There is no ordering between group rules. That is, when a user belongs to 2 groups, there is no
|
|
||||||
// guarantee one set of rules has precedence on the other one.
|
|
||||||
const groupRules = groups.reduce(function (rules: ACLRule[], group: string) {
|
|
||||||
const groupRules = that.configuration.groups[group];
|
|
||||||
if (groupRules) rules = rules.concat(groupRules);
|
|
||||||
return rules;
|
|
||||||
}, []);
|
|
||||||
return groupRules.filter(MatchDomain(domain)).filter(MatchResource(resource));
|
|
||||||
}
|
|
||||||
|
|
||||||
private getMatchingAllRules(domain: string, resource: string): ACLRule[] {
|
|
||||||
const rules = this.configuration.any;
|
|
||||||
if (!rules) return [];
|
if (!rules) return [];
|
||||||
return rules.filter(MatchDomain(domain)).filter(MatchResource(resource));
|
return rules
|
||||||
|
.filter(MatchDomain(domain))
|
||||||
|
.filter(MatchResource(resource))
|
||||||
|
.filter(MatchSubject(user, groups));
|
||||||
}
|
}
|
||||||
|
|
||||||
authorization(domain: string, resource: string, user: string, groups: string[]): Level {
|
private ruleToLevel(policy: string): Level {
|
||||||
if (!this.configuration) return Level.BYPASS;
|
|
||||||
|
|
||||||
const allRules = this.getMatchingAllRules(domain, resource);
|
|
||||||
const groupRules = this.getMatchingGroupRules(groups, domain, resource);
|
|
||||||
const userRules = this.getMatchingUserRules(user, domain, resource);
|
|
||||||
const rules = allRules.concat(groupRules).concat(userRules).reverse();
|
|
||||||
const policy = rules.map(r => r.policy).concat([this.configuration.default_policy])[0];
|
|
||||||
|
|
||||||
if (policy == "bypass") {
|
if (policy == "bypass") {
|
||||||
return Level.BYPASS;
|
return Level.BYPASS;
|
||||||
} else if (policy == "one_factor") {
|
} else if (policy == "one_factor") {
|
||||||
|
@ -75,4 +70,14 @@ export class Authorizer implements IAuthorizer {
|
||||||
}
|
}
|
||||||
return Level.DENY;
|
return Level.DENY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authorization(domain: string, resource: string, user: string, groups: string[]): Level {
|
||||||
|
if (!this.configuration) return Level.BYPASS;
|
||||||
|
|
||||||
|
const rules = this.getMatchingRules(domain, resource, user, groups);
|
||||||
|
|
||||||
|
return (rules.length > 0)
|
||||||
|
? this.ruleToLevel(rules[0].policy) // extract the policy of the first matching rule
|
||||||
|
: this.ruleToLevel(this.configuration.default_policy); // otherwise use the default policy
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -125,32 +125,26 @@ describe("configuration/ConfigurationParser", function () {
|
||||||
const userConfig = buildYamlConfig();
|
const userConfig = buildYamlConfig();
|
||||||
userConfig.access_control = {
|
userConfig.access_control = {
|
||||||
default_policy: "deny",
|
default_policy: "deny",
|
||||||
any: [{
|
rules: [{
|
||||||
|
domain: "www.example.com",
|
||||||
|
policy: "two_factor",
|
||||||
|
subject: "user:user"
|
||||||
|
}, {
|
||||||
domain: "public.example.com",
|
domain: "public.example.com",
|
||||||
policy: "two_factor"
|
policy: "two_factor"
|
||||||
}],
|
}]
|
||||||
users: {
|
|
||||||
"user": [{
|
|
||||||
domain: "www.example.com",
|
|
||||||
policy: "two_factor"
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
groups: {}
|
|
||||||
};
|
};
|
||||||
const config = ConfigurationParser.parse(userConfig);
|
const config = ConfigurationParser.parse(userConfig);
|
||||||
Assert.deepEqual(config.access_control, {
|
Assert.deepEqual(config.access_control, {
|
||||||
default_policy: "deny",
|
default_policy: "deny",
|
||||||
any: [{
|
rules: [{
|
||||||
|
domain: "www.example.com",
|
||||||
|
policy: "two_factor",
|
||||||
|
subject: "user:user"
|
||||||
|
}, {
|
||||||
domain: "public.example.com",
|
domain: "public.example.com",
|
||||||
policy: "two_factor"
|
policy: "two_factor"
|
||||||
}],
|
}]
|
||||||
users: {
|
|
||||||
"user": [{
|
|
||||||
domain: "www.example.com",
|
|
||||||
policy: "two_factor"
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
groups: {}
|
|
||||||
} as ACLConfiguration);
|
} as ACLConfiguration);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -161,9 +155,7 @@ describe("configuration/ConfigurationParser", function () {
|
||||||
const config = ConfigurationParser.parse(userConfig);
|
const config = ConfigurationParser.parse(userConfig);
|
||||||
Assert.deepEqual(config.access_control, {
|
Assert.deepEqual(config.access_control, {
|
||||||
default_policy: "bypass",
|
default_policy: "bypass",
|
||||||
any: [],
|
rules: []
|
||||||
users: {},
|
|
||||||
groups: {}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,9 +11,7 @@ describe("configuration/SessionConfigurationBuilder", function () {
|
||||||
const configuration: Configuration = {
|
const configuration: Configuration = {
|
||||||
access_control: {
|
access_control: {
|
||||||
default_policy: "deny",
|
default_policy: "deny",
|
||||||
any: [],
|
rules: []
|
||||||
users: {},
|
|
||||||
groups: {}
|
|
||||||
},
|
},
|
||||||
totp: {
|
totp: {
|
||||||
issuer: "authelia.com"
|
issuer: "authelia.com"
|
||||||
|
|
|
@ -4,11 +4,31 @@ import Assert = require("assert");
|
||||||
describe("configuration/schema/AclConfiguration", function() {
|
describe("configuration/schema/AclConfiguration", function() {
|
||||||
it("should complete ACLConfiguration", function() {
|
it("should complete ACLConfiguration", function() {
|
||||||
const configuration: ACLConfiguration = {};
|
const configuration: ACLConfiguration = {};
|
||||||
const newConfiguration = complete(configuration);
|
const [newConfiguration, errors] = complete(configuration);
|
||||||
|
|
||||||
Assert.deepEqual(newConfiguration.default_policy, "bypass");
|
Assert.deepEqual(newConfiguration.default_policy, "bypass");
|
||||||
Assert.deepEqual(newConfiguration.any, []);
|
Assert.deepEqual(newConfiguration.rules, []);
|
||||||
Assert.deepEqual(newConfiguration.groups, {});
|
});
|
||||||
Assert.deepEqual(newConfiguration.users, {});
|
|
||||||
|
it("should return errors when subject is not good", function() {
|
||||||
|
const configuration: ACLConfiguration = {
|
||||||
|
default_policy: "deny",
|
||||||
|
rules: [{
|
||||||
|
domain: "dev.example.com",
|
||||||
|
subject: "user:abc",
|
||||||
|
policy: "bypass"
|
||||||
|
}, {
|
||||||
|
domain: "dev.example.com",
|
||||||
|
subject: "user:def",
|
||||||
|
policy: "bypass"
|
||||||
|
}, {
|
||||||
|
domain: "dev.example.com",
|
||||||
|
subject: "badkey:abc",
|
||||||
|
policy: "bypass"
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
const [newConfiguration, errors] = complete(configuration);
|
||||||
|
|
||||||
|
Assert.deepEqual(errors, ["Rule 2 has wrong subject. It should be starting with user: or group:."]);
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -3,22 +3,17 @@ export type ACLPolicy = "deny" | "bypass" | "one_factor" | "two_factor";
|
||||||
|
|
||||||
export type ACLRule = {
|
export type ACLRule = {
|
||||||
domain: string;
|
domain: string;
|
||||||
policy: ACLPolicy;
|
|
||||||
resources?: string[];
|
resources?: string[];
|
||||||
|
subject?: string;
|
||||||
|
policy: ACLPolicy;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ACLDefaultRules = ACLRule[];
|
|
||||||
export type ACLGroupsRules = { [group: string]: ACLRule[]; };
|
|
||||||
export type ACLUsersRules = { [user: string]: ACLRule[]; };
|
|
||||||
|
|
||||||
export interface ACLConfiguration {
|
export interface ACLConfiguration {
|
||||||
default_policy?: ACLPolicy;
|
default_policy?: ACLPolicy;
|
||||||
any?: ACLDefaultRules;
|
rules?: ACLRule[];
|
||||||
groups?: ACLGroupsRules;
|
|
||||||
users?: ACLUsersRules;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function complete(configuration: ACLConfiguration): ACLConfiguration {
|
export function complete(configuration: ACLConfiguration): [ACLConfiguration, string[]] {
|
||||||
const newConfiguration: ACLConfiguration = (configuration)
|
const newConfiguration: ACLConfiguration = (configuration)
|
||||||
? JSON.parse(JSON.stringify(configuration)) : {};
|
? JSON.parse(JSON.stringify(configuration)) : {};
|
||||||
|
|
||||||
|
@ -26,17 +21,21 @@ export function complete(configuration: ACLConfiguration): ACLConfiguration {
|
||||||
newConfiguration.default_policy = "bypass";
|
newConfiguration.default_policy = "bypass";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newConfiguration.any) {
|
if (!newConfiguration.rules) {
|
||||||
newConfiguration.any = [];
|
newConfiguration.rules = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newConfiguration.groups) {
|
if (newConfiguration.rules.length > 0) {
|
||||||
newConfiguration.groups = {};
|
const errors: string[] = [];
|
||||||
|
newConfiguration.rules.forEach((r, idx) => {
|
||||||
|
if (r.subject && !r.subject.match(/^(user|group):[a-zA-Z0-9]+$/)) {
|
||||||
|
errors.push(`Rule ${idx} has wrong subject. It should be starting with user: or group:.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return [newConfiguration, errors];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newConfiguration.users) {
|
return [newConfiguration, []];
|
||||||
newConfiguration.users = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return newConfiguration;
|
|
||||||
}
|
}
|
|
@ -27,9 +27,13 @@ export function complete(
|
||||||
JSON.stringify(configuration));
|
JSON.stringify(configuration));
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
newConfiguration.access_control =
|
const [acls, aclsErrors] = AclConfigurationComplete(
|
||||||
AclConfigurationComplete(
|
newConfiguration.access_control);
|
||||||
newConfiguration.access_control);
|
|
||||||
|
newConfiguration.access_control = acls;
|
||||||
|
if (aclsErrors.length > 0) {
|
||||||
|
errors.concat(aclsErrors);
|
||||||
|
}
|
||||||
|
|
||||||
const [backend, error] =
|
const [backend, error] =
|
||||||
AuthenticationBackendComplete(
|
AuthenticationBackendComplete(
|
||||||
|
|
|
@ -6,12 +6,12 @@ describe("configuration/schema/NotifierConfiguration", function() {
|
||||||
const configuration: NotifierConfiguration = {};
|
const configuration: NotifierConfiguration = {};
|
||||||
const [newConfiguration, error] = complete(configuration);
|
const [newConfiguration, error] = complete(configuration);
|
||||||
|
|
||||||
Assert.deepEqual(newConfiguration.filesystem, {filename: "/tmp/authelia/notification.txt"})
|
Assert.deepEqual(newConfiguration.filesystem, {filename: "/tmp/authelia/notification.txt"});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should ensure correct key is provided", function() {
|
it("should ensure correct key is provided", function() {
|
||||||
const configuration = {
|
const configuration = {
|
||||||
abc: 'badvalue'
|
abc: "badvalue"
|
||||||
};
|
};
|
||||||
const [newConfiguration, error] = complete(configuration as any);
|
const [newConfiguration, error] = complete(configuration as any);
|
||||||
|
|
||||||
|
|
|
@ -246,7 +246,7 @@ describe("routes/verify/get", function () {
|
||||||
it("should fail when endpoint is protected by two factors", function () {
|
it("should fail when endpoint is protected by two factors", function () {
|
||||||
mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
|
mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
|
||||||
mocks.config.access_control.default_policy = "one_factor";
|
mocks.config.access_control.default_policy = "one_factor";
|
||||||
mocks.config.access_control.any = [{
|
mocks.config.access_control.rules = [{
|
||||||
domain: "secret.example.com",
|
domain: "secret.example.com",
|
||||||
policy: "two_factor"
|
policy: "two_factor"
|
||||||
}];
|
}];
|
||||||
|
|
|
@ -10,7 +10,7 @@ describe("storage/mongo/MongoCollection", function () {
|
||||||
let mongoClientStub: MongoClientStub;
|
let mongoClientStub: MongoClientStub;
|
||||||
let findStub: Sinon.SinonStub;
|
let findStub: Sinon.SinonStub;
|
||||||
let findOneStub: Sinon.SinonStub;
|
let findOneStub: Sinon.SinonStub;
|
||||||
let insertStub: Sinon.SinonStub;
|
let insertOneStub: Sinon.SinonStub;
|
||||||
let updateStub: Sinon.SinonStub;
|
let updateStub: Sinon.SinonStub;
|
||||||
let removeStub: Sinon.SinonStub;
|
let removeStub: Sinon.SinonStub;
|
||||||
let countStub: Sinon.SinonStub;
|
let countStub: Sinon.SinonStub;
|
||||||
|
@ -21,7 +21,7 @@ describe("storage/mongo/MongoCollection", function () {
|
||||||
mongoCollectionStub = Sinon.createStubInstance(require("mongodb").Collection as any);
|
mongoCollectionStub = Sinon.createStubInstance(require("mongodb").Collection as any);
|
||||||
findStub = mongoCollectionStub.find as Sinon.SinonStub;
|
findStub = mongoCollectionStub.find as Sinon.SinonStub;
|
||||||
findOneStub = mongoCollectionStub.findOne as Sinon.SinonStub;
|
findOneStub = mongoCollectionStub.findOne as Sinon.SinonStub;
|
||||||
insertStub = mongoCollectionStub.insert as Sinon.SinonStub;
|
insertOneStub = mongoCollectionStub.insertOne as Sinon.SinonStub;
|
||||||
updateStub = mongoCollectionStub.update as Sinon.SinonStub;
|
updateStub = mongoCollectionStub.update as Sinon.SinonStub;
|
||||||
removeStub = mongoCollectionStub.remove as Sinon.SinonStub;
|
removeStub = mongoCollectionStub.remove as Sinon.SinonStub;
|
||||||
countStub = mongoCollectionStub.count as Sinon.SinonStub;
|
countStub = mongoCollectionStub.count as Sinon.SinonStub;
|
||||||
|
@ -63,11 +63,11 @@ describe("storage/mongo/MongoCollection", function () {
|
||||||
describe("insert", function () {
|
describe("insert", function () {
|
||||||
it("should insert a document in the collection", function () {
|
it("should insert a document in the collection", function () {
|
||||||
const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
|
const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
|
||||||
insertStub.returns(BluebirdPromise.resolve({}));
|
insertOneStub.returns(BluebirdPromise.resolve({}));
|
||||||
|
|
||||||
return collection.insert({ key: "KEY" })
|
return collection.insert({ key: "KEY" })
|
||||||
.then(function () {
|
.then(function () {
|
||||||
Assert(insertStub.calledWith({ key: "KEY" }));
|
Assert(insertOneStub.calledWith({ key: "KEY" }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,7 +40,7 @@ export class MongoCollection implements ICollection {
|
||||||
|
|
||||||
insert(document: any): Bluebird<any> {
|
insert(document: any): Bluebird<any> {
|
||||||
return this.collection()
|
return this.collection()
|
||||||
.then((collection) => collection.insert(document));
|
.then((collection) => collection.insertOne(document));
|
||||||
}
|
}
|
||||||
|
|
||||||
count(query: any): Bluebird<any> {
|
count(query: any): Bluebird<any> {
|
||||||
|
|
|
@ -33,7 +33,7 @@ Feature: User has access restricted access to domains
|
||||||
And I have access to "https://dev.example.com:8080/users/bob/secret.html"
|
And I have access to "https://dev.example.com:8080/users/bob/secret.html"
|
||||||
And I have no access to "https://admin.example.com:8080/secret.html"
|
And I have no access to "https://admin.example.com:8080/secret.html"
|
||||||
And I have access to "https://mx1.mail.example.com:8080/secret.html"
|
And I have access to "https://mx1.mail.example.com:8080/secret.html"
|
||||||
And I have no access to "https://single_factor.example.com:8080/secret.html"
|
And I have access to "https://single_factor.example.com:8080/secret.html"
|
||||||
And I have access to "https://mx2.mail.example.com:8080/secret.html"
|
And I have access to "https://mx2.mail.example.com:8080/secret.html"
|
||||||
|
|
||||||
@need-registered-user-harry
|
@need-registered-user-harry
|
||||||
|
@ -51,5 +51,5 @@ Feature: User has access restricted access to domains
|
||||||
And I have no access to "https://dev.example.com:8080/users/bob/secret.html"
|
And I have no access to "https://dev.example.com:8080/users/bob/secret.html"
|
||||||
And I have no access to "https://admin.example.com:8080/secret.html"
|
And I have no access to "https://admin.example.com:8080/secret.html"
|
||||||
And I have no access to "https://mx1.mail.example.com:8080/secret.html"
|
And I have no access to "https://mx1.mail.example.com:8080/secret.html"
|
||||||
And I have no access to "https://single_factor.example.com:8080/secret.html"
|
And I have access to "https://single_factor.example.com:8080/secret.html"
|
||||||
And I have no access to "https://mx2.mail.example.com:8080/secret.html"
|
And I have no access to "https://mx2.mail.example.com:8080/secret.html"
|
||||||
|
|
|
@ -14,24 +14,3 @@ Feature: Non authenticated users have no access to certain pages
|
||||||
| https://login.example.com:8080/api/u2f/sign | 401 | POST |
|
| https://login.example.com:8080/api/u2f/sign | 401 | POST |
|
||||||
| https://login.example.com:8080/api/u2f/register_request | 401 | GET |
|
| https://login.example.com:8080/api/u2f/register_request | 401 | GET |
|
||||||
| https://login.example.com:8080/api/u2f/register | 401 | POST |
|
| https://login.example.com:8080/api/u2f/register | 401 | POST |
|
||||||
|
|
||||||
|
|
||||||
@needs-single_factor-config
|
|
||||||
@need-registered-user-john
|
|
||||||
Scenario: User does not have acces to second factor related endpoints when in single factor mode
|
|
||||||
Given I post "https://login.example.com:8080/api/firstfactor" with body:
|
|
||||||
| key | value |
|
|
||||||
| username | john |
|
|
||||||
| password | password |
|
|
||||||
Then I get the following status code when requesting:
|
|
||||||
| url | code | method |
|
|
||||||
| https://login.example.com:8080/secondfactor | 401 | GET |
|
|
||||||
| https://login.example.com:8080/secondfactor/u2f/identity/start | 401 | GET |
|
|
||||||
| https://login.example.com:8080/secondfactor/u2f/identity/finish | 401 | GET |
|
|
||||||
| https://login.example.com:8080/secondfactor/totp/identity/start | 401 | GET |
|
|
||||||
| https://login.example.com:8080/secondfactor/totp/identity/finish | 401 | GET |
|
|
||||||
| https://login.example.com:8080/api/totp | 401 | POST |
|
|
||||||
| https://login.example.com:8080/api/u2f/sign_request | 401 | GET |
|
|
||||||
| https://login.example.com:8080/api/u2f/sign | 401 | POST |
|
|
||||||
| https://login.example.com:8080/api/u2f/register_request | 401 | GET |
|
|
||||||
| https://login.example.com:8080/api/u2f/register | 401 | POST |
|
|
|
@ -13,3 +13,4 @@ Feature: User can access certain subdomains with single factor
|
||||||
Scenario: User can login using basic authentication
|
Scenario: User can login using basic authentication
|
||||||
When I request "https://single_factor.example.com:8080/secret.html" with username "john" and password "password" using basic authentication
|
When I request "https://single_factor.example.com:8080/secret.html" with username "john" and password "password" using basic authentication
|
||||||
Then I receive the secret page
|
Then I receive the secret page
|
||||||
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
@needs-single_factor-config
|
|
||||||
Feature: Server is configured as a single factor only server
|
|
||||||
|
|
||||||
@need-registered-user-john
|
|
||||||
Scenario: User is redirected to service after first factor if allowed
|
|
||||||
When I visit "https://login.example.com:8080/?rd=https://public.example.com:8080/secret.html"
|
|
||||||
And I login with user "john" and password "password"
|
|
||||||
Then I'm redirected to "https://public.example.com:8080/secret.html"
|
|
||||||
|
|
||||||
@need-registered-user-john
|
|
||||||
Scenario: User is correctly redirected according to default redirection URL
|
|
||||||
When I visit "https://login.example.com:8080"
|
|
||||||
And I login with user "john" and password "password"
|
|
||||||
Then I'm redirected to "https://login.example.com:8080/loggedin"
|
|
||||||
And I sleep for 5 seconds
|
|
||||||
Then I'm redirected to "https://home.example.com:8080/"
|
|
|
@ -33,7 +33,6 @@ function requestAndExpectStatusCode(ctx: any, url: string, method: string,
|
||||||
Assert.equal(statusCode, expectedStatusCode);
|
Assert.equal(statusCode, expectedStatusCode);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.log(url);
|
|
||||||
console.log("%s (actual) != %s (expected)", statusCode,
|
console.log("%s (actual) != %s (expected)", statusCode,
|
||||||
expectedStatusCode);
|
expectedStatusCode);
|
||||||
throw e;
|
throw e;
|
||||||
|
|
Loading…
Reference in New Issue