Merge pull request #95 from clems4ever/acl-by-resources
Refine access control with per resource ACLspull/97/head
commit
7a2b45a66f
|
@ -18,9 +18,8 @@ addons:
|
|||
- auth.test.local
|
||||
- home.test.local
|
||||
- public.test.local
|
||||
- secret.test.local
|
||||
- secret1.test.local
|
||||
- secret2.test.local
|
||||
- admin.test.local
|
||||
- dev.test.local
|
||||
- mx1.mail.test.local
|
||||
- mx2.mail.test.local
|
||||
|
||||
|
|
61
README.md
61
README.md
|
@ -48,7 +48,7 @@ without even configure anything.
|
|||
|
||||
Otherwise here are the available steps to deploy **Authelia** on your machine given
|
||||
your configuration file is **/path/to/your/config.yml**. Note that you can create your
|
||||
own the configuration file from **config.template.yml** at the root of the repo.
|
||||
own the configuration file from [config.template.yml] at the root of the repo.
|
||||
|
||||
### With NPM
|
||||
|
||||
|
@ -91,11 +91,10 @@ Make sure you don't have anything listening on port 8080.
|
|||
|
||||
Add the following lines to your **/etc/hosts** to alias multiple subdomains so that nginx can redirect request to the correct virtual host.
|
||||
|
||||
127.0.0.1 public.test.local
|
||||
127.0.0.1 secret.test.local
|
||||
127.0.0.1 secret1.test.local
|
||||
127.0.0.1 secret2.test.local
|
||||
127.0.0.1 home.test.local
|
||||
127.0.0.1 public.test.local
|
||||
127.0.0.1 dev.test.local
|
||||
127.0.0.1 admin.test.local
|
||||
127.0.0.1 mx1.mail.test.local
|
||||
127.0.0.1 mx2.mail.test.local
|
||||
127.0.0.1 auth.test.local
|
||||
|
@ -119,7 +118,7 @@ After few seconds the services should be running and you should be able to visit
|
|||
|
||||
When accessing the login page, a self-signed certificate exception should appear,
|
||||
it has to be trusted before you can get to the target page. The certificate
|
||||
must be trusted for each subdomain, therefore it is normal to see the exception
|
||||
must also be trusted for each subdomain, therefore it is normal to see the exception
|
||||
several times.
|
||||
|
||||
Below is what the login page looks like:
|
||||
|
@ -128,37 +127,24 @@ Below is what the login page looks like:
|
|||
|
||||
## Features in details
|
||||
|
||||
### First factor with LDAP and ACL
|
||||
An LDAP server has been deployed for you with the following credentials and
|
||||
access control list:
|
||||
|
||||
- **john / password** is in the admin group and has access to the secret from
|
||||
any subdomain.
|
||||
- **bob / password** is in the dev group and has access to the secret from
|
||||
- [secret.test.local](https://secret.test.local:8080/secret.html)
|
||||
- [secret2.test.local](https://secret2.test.local:8080/secret.html)
|
||||
- [home.test.local](https://home.test.local:8080/secret.html)
|
||||
- [\*.mail.test.local](https://mx1.mail.test.local:8080/secret.html)
|
||||
- **harry / password** is not in a group but has rules giving him has access to
|
||||
the secret from
|
||||
- [secret1.test.local](https://secret1.test.local:8080/secret.html)
|
||||
- [home.test.local](https://home.test.local:8080/secret.html)
|
||||
|
||||
You can use them in the login page. If everything is ok, the second factor
|
||||
page should appear as shown below. Otherwise you'll get an error message notifying
|
||||
your credentials are wrong.
|
||||
### First factor using an LDAP server
|
||||
**Authelia** uses an LDAP server as the backend for storing credentials.
|
||||
When authentication is needed, the user is redirected to the login page which
|
||||
corresponds to the first factor. Authelia tries to bind the username and password
|
||||
against the configured LDAP backend.
|
||||
|
||||
You can find an example of the configuration of the LDAP backend in [config.template.yml].
|
||||
|
||||
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/second_factor.png" width="400">
|
||||
|
||||
|
||||
### Second factor with TOTP
|
||||
In **Authelia**, you need to register a per user TOTP (Time-Based One Time Password) secret before
|
||||
In **Authelia**, you can register a per user TOTP (Time-Based One Time Password) secret before
|
||||
authenticating. To do that, you need to click on the register button. It will
|
||||
send a link to the user email address. Since this is an example, no email will
|
||||
be sent, the link is rather delivered in the file
|
||||
send a link to the user email address defined in the LDAP.
|
||||
Since this is an example, no email will be sent, the link is rather delivered in the file
|
||||
**/tmp/notifications/notification.txt**. Paste the link in your browser and you'll get
|
||||
your secret in QRCode and Base32 formats. You can use
|
||||
your secret in QRCode and Base32 format. You can use
|
||||
[Google Authenticator]
|
||||
to store them and get the generated tokens with the app.
|
||||
|
||||
|
@ -169,8 +155,8 @@ to store them and get the generated tokens with the app.
|
|||
USB security keys. U2F is one of the most secure authentication protocol and is
|
||||
already available for Google, Facebook, Github accounts and more.
|
||||
|
||||
Like TOTP, U2F requires you register your security key before authenticating.
|
||||
To do so, click on the register button. This will send a link to the
|
||||
Like TOTP, U2F requires you register a security key before authenticating.
|
||||
To do so, click on the register link. This will send a link to the
|
||||
user email address. Since this is an example, no email will be sent, the
|
||||
link is rather delivered in the file **/tmp/notifications/notification.txt**. Paste
|
||||
the link in your browser and you'll be asking to touch the token of your device
|
||||
|
@ -190,18 +176,18 @@ Paste the link in your browser and you should be able to reset the password.
|
|||
<img src="https://raw.githubusercontent.com/clems4ever/authelia/master/images/reset_password.png" width="400">
|
||||
|
||||
### Access Control
|
||||
With **Authelia**, you can define your own access control rules for restricting
|
||||
the user access to some subdomains. Those rules are defined in the
|
||||
configuration file and can be set either for everyone, per-user or per-group policies.
|
||||
Check out the *config.template.yml* to see how they are defined.
|
||||
With **Authelia**, you can define your own access control rules for finely restricting
|
||||
user access to some resources and subdomains. Those rules are defined and fully documented
|
||||
in the configuration file. They can apply to users, groups or everyone.
|
||||
Check out [config.template.yml] to see how they are defined.
|
||||
|
||||
### Session management with Redis
|
||||
When your users authenticate against Authelia, sessions are stored in a Redis key/value store. You can specify your own Redis instance in the [configuration file](#authelia-configuration).
|
||||
When your users authenticate against Authelia, sessions are stored in a Redis key/value store. You can specify your own Redis instance in [config.template.yml].
|
||||
|
||||
## Documentation
|
||||
### Authelia configuration
|
||||
The configuration of the server is defined in the file
|
||||
**configuration.template.yml**. All the details are documented there.
|
||||
[config.template.yml]. All the details are documented there.
|
||||
You can specify another configuration file by giving it as first argument of
|
||||
**Authelia**.
|
||||
|
||||
|
@ -246,4 +232,5 @@ Follow [contributing](CONTRIBUTORS.md) file.
|
|||
[Yubikey]: https://www.yubico.com/products/yubikey-hardware/yubikey4/
|
||||
[auth_request]: http://nginx.org/en/docs/http/ngx_http_auth_request_module.html
|
||||
[Google Authenticator]: https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en
|
||||
[config.template.yml]: https://github.com/clems4ever/authelia/blob/master/config.template.yml
|
||||
|
||||
|
|
|
@ -49,38 +49,84 @@ ldap:
|
|||
|
||||
# Access Control
|
||||
#
|
||||
# Access control is a set of rules you can use to restrict the user access.
|
||||
# Default (anyone), per-user or per-group rules can be defined.
|
||||
# Access control is a set of rules you can use to restrict user access to certain
|
||||
# resources.
|
||||
# Any (apply to anyone), per-user or per-group rules can be defined.
|
||||
#
|
||||
# If 'access_control' is not defined, ACL rules are disabled and a default policy
|
||||
# is applied, i.e., access is allowed to anyone. Otherwise restrictions follow
|
||||
# the rules defined below.
|
||||
# If no rule is provided, all domains are denied.
|
||||
# If 'access_control' is not defined, ACL rules are disabled and the `allow` default
|
||||
# policy is applied, i.e., access is allowed to anyone. Otherwise restrictions follow
|
||||
# the rules defined.
|
||||
#
|
||||
# Note: One can use the wildcard * to match any subdomain.
|
||||
# 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
|
||||
# to be syntaxically correct.
|
||||
#
|
||||
# Definition: A `rule` is an object with the following keys: `domain`, `policy`
|
||||
# and `resources`.
|
||||
# - `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:
|
||||
# In each category (`any`, `groups`, `users`), the latest rules have the highest
|
||||
# priority. In other words, it means that if a given resource matches two rules in the
|
||||
# same category, the latest one overrides the first one.
|
||||
# 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.
|
||||
#
|
||||
# One can use the wildcard * to match any subdomain.
|
||||
# Note 1: It must stand at the beginning of the pattern. (example: *.mydomain.com)
|
||||
# Note 2: You must put the pattern in simple quotes when using the wildcard.
|
||||
access_control:
|
||||
# The default policy. Applies to any user
|
||||
default:
|
||||
- public.test.local
|
||||
# Default policy can either be `allow` or `deny`.
|
||||
# It is the policy applied to any resource if it has not been overriden
|
||||
# in the `any`, `groups` or `users` category.
|
||||
default_policy: deny
|
||||
|
||||
# The rules that apply to anyone.
|
||||
# The value is a list of rules.
|
||||
any:
|
||||
- domain: public.test.local
|
||||
policy: allow
|
||||
|
||||
# Group based policies. The key is a group name and the value
|
||||
# is the domain to allow access to.
|
||||
# Group-based rules. The key is a group name and the value
|
||||
# is a list of rules.
|
||||
groups:
|
||||
admin:
|
||||
- '*.test.local'
|
||||
# All resources in all domains
|
||||
- domain: '*.test.local'
|
||||
policy: allow
|
||||
# Except mx2.mail.test.local (it restricts the first rule)
|
||||
- domain: 'mx2.mail.test.local'
|
||||
policy: deny
|
||||
dev:
|
||||
- secret.test.local
|
||||
- secret2.test.local
|
||||
- domain: dev.test.local
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/groups/dev/.*$'
|
||||
|
||||
# Group based policies. The key is a group name and the value
|
||||
# is the domain to allow access to.
|
||||
users:
|
||||
# User-based rules. The key is a user name and the value
|
||||
# is a list of rules.
|
||||
users:
|
||||
john:
|
||||
- domain: dev.test.local
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/users/john/.*$'
|
||||
harry:
|
||||
- secret1.test.local
|
||||
- domain: dev.test.local
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/users/harry/.*$'
|
||||
bob:
|
||||
- '*.mail.test.local'
|
||||
- domain: '*.mail.test.local'
|
||||
policy: allow
|
||||
- domain: 'dev.test.local'
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/users/bob/.*$'
|
||||
|
||||
|
||||
# Configuration of session cookies
|
||||
|
|
|
@ -61,27 +61,53 @@ ldap:
|
|||
# Note 1: It must stand at the beginning of the pattern. (example: *.mydomain.com)
|
||||
# Note 2: You must put the pattern in simple quotes when using the wildcard.
|
||||
access_control:
|
||||
# The default policy. Applies to any user
|
||||
default:
|
||||
- public.test.local
|
||||
# Default policy can either be `allow` or `deny`.
|
||||
# It is the policy applied to any resource if it has not been overriden
|
||||
# in the `any`, `groups` or `users` category.
|
||||
default_policy: deny
|
||||
|
||||
# The rules that apply to anyone.
|
||||
# The value is a list of rules.
|
||||
any:
|
||||
- domain: public.test.local
|
||||
policy: allow
|
||||
|
||||
# Group based policies. The key is a group name and the value
|
||||
# is the domain to allow access to.
|
||||
# Group-based rules. The key is a group name and the value
|
||||
# is a list of rules.
|
||||
groups:
|
||||
admin:
|
||||
- '*.test.local'
|
||||
# All resources in all domains
|
||||
- domain: '*.test.local'
|
||||
policy: allow
|
||||
# Except mx2.mail.test.local (it restricts the first rule)
|
||||
- domain: 'mx2.mail.test.local'
|
||||
policy: deny
|
||||
dev:
|
||||
- secret.test.local
|
||||
- secret2.test.local
|
||||
- domain: dev.test.local
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/groups/dev/.*$'
|
||||
|
||||
# Group based policies. The key is a group name and the value
|
||||
# is the domain to allow access to.
|
||||
users:
|
||||
# User-based rules. The key is a user name and the value
|
||||
# is a list of rules.
|
||||
users:
|
||||
john:
|
||||
- domain: dev.test.local
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/users/john/.*$'
|
||||
harry:
|
||||
- secret1.test.local
|
||||
- domain: dev.test.local
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/users/harry/.*$'
|
||||
bob:
|
||||
- '*.mail.test.local'
|
||||
|
||||
- domain: '*.mail.test.local'
|
||||
policy: allow
|
||||
- domain: 'dev.test.local'
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/users/bob/.*$'
|
||||
|
||||
# Configuration of session cookies
|
||||
#
|
||||
|
|
|
@ -14,10 +14,8 @@ services:
|
|||
- example-network
|
||||
# aliases:
|
||||
# - home.test.local
|
||||
# - secret.test.local
|
||||
# - secret1.test.local
|
||||
# - secret2.test.local
|
||||
# - mx1.mail.test.local
|
||||
# - mx2.mail.test.local
|
||||
# - public.test.local
|
||||
# - admin.test.local
|
||||
# - dev.test.local
|
||||
# - auth.test.local
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,132 @@
|
|||
<!DOCTYPE>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Home page</title>
|
||||
<link rel="icon" href="/icon.png" type="image/png" />
|
||||
</head>
|
||||
|
||||
<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/>
|
||||
<ul>
|
||||
<li>
|
||||
public.test.local <a href="https://public.test.local:8080/"> / index.html</a>
|
||||
</li>
|
||||
<li>
|
||||
secret.test.local
|
||||
<ul>
|
||||
<li>Groups
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://dev.test.local:8080/groups/admin/secret.html"> / groups / admins / secret.html</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://dev.test.local:8080/groups/dev/secret.html"> / groups / dev / secret.html</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Users
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://dev.test.local:8080/users/john/secret.html"> / users / john / secret.html</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://dev.test.local:8080/users/harry/secret.html"> / users / harry / secret.html</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://dev.test.local:8080/users/bob/secret.html"> / users / bob / secret.html</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
admin.test.local <a href="https://admin.test.local:8080/secret.html"> / secret.html</a>
|
||||
</li>
|
||||
<li>
|
||||
mx1.main.test.local <a href="https://mx1.mail.test.local:8080/secret.html"> / secret.html</a>
|
||||
</li>
|
||||
<li>
|
||||
mx2.main.test.local <a href="https://mx2.mail.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.
|
||||
|
||||
<ul>
|
||||
<li><strong>john / password</strong>: belongs to <em>admin</em> and <em>dev</em> groups.</li>
|
||||
<li><strong>bob / password</strong>: belongs to <em>dev</em> group only.</li>
|
||||
<li><strong>harry / password</strong>: does not belong to any group.</li>
|
||||
</ul>
|
||||
|
||||
<h1>Access control rules</h1>
|
||||
<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`.
|
||||
# It is the policy applied to any resource if it has not been overriden
|
||||
# in the `any`, `groups` or `users` category.
|
||||
|
||||
default_policy: deny
|
||||
|
||||
# The rules that apply to anyone.
|
||||
# The value is a list of rules.
|
||||
|
||||
any:
|
||||
- domain: public.test.local
|
||||
policy: allow
|
||||
|
||||
# Group-based rules. The key is a group name and the value
|
||||
# is a list of rules.
|
||||
|
||||
groups:
|
||||
admin:
|
||||
# All resources in all domains
|
||||
- domain: '*.test.local'
|
||||
policy: allow
|
||||
# Except mx2.mail.test.local (it restricts the first rule)
|
||||
- domain: 'mx2.mail.test.local'
|
||||
policy: deny
|
||||
dev:
|
||||
- domain: dev.test.local
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/groups/dev/.*$'
|
||||
|
||||
# User-based rules. The key is a user name and the value
|
||||
# is a list of rules.
|
||||
|
||||
users:
|
||||
john:
|
||||
- domain: dev.test.local
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/users/john/.*$'
|
||||
harry:
|
||||
- domain: dev.test.local
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/users/harry/.*$'
|
||||
bob:
|
||||
- domain: '*.mail.test.local'
|
||||
policy: allow
|
||||
- domain: 'dev.test.local'
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/users/bob/.*$'
|
||||
- domain: 'dev.test.local'
|
||||
policy: allow
|
||||
resources:
|
||||
- '^/users/harry/.*$'</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -1,82 +0,0 @@
|
|||
<!DOCTYPE>
|
||||
<html>
|
||||
<head>
|
||||
<title>Home page</title>
|
||||
<link rel="icon" href="/icon.png" type="image/png" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Access the secret</h1>
|
||||
You need to log in to access the secret!<br/><br/>
|
||||
Try to access it via one of the following links.<br/>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://public.test.local:8080/secret.html">public.test.local</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://secret.test.local:8080/secret.html">secret.test.local</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://secret1.test.local:8080/secret.html">secret1.test.local</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://secret2.test.local:8080/secret.html">secret2.test.local</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://mx1.mail.test.local:8080/secret.html">mx1.mail.test.local</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://mx2.mail.test.local:8080/secret.html">mx2.mail.test.local</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.
|
||||
|
||||
<ul>
|
||||
<li><strong>john / password</strong>: belongs to <em>admin</em> and <em>dev</em> groups.</li>
|
||||
<li><strong>bob / password</strong>: belongs to <em>dev</em> group only.</li>
|
||||
<li><strong>harry / password</strong>: does not belong to any group.</li>
|
||||
</ul>
|
||||
|
||||
<h1>Access control rules</h1>
|
||||
|
||||
<ul>
|
||||
<li><strong>Default policy</strong>
|
||||
<ul>
|
||||
<li>public.test.local</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Groups policy</strong>
|
||||
<ul>
|
||||
<li>admin
|
||||
<ul>
|
||||
<li>*.test.local</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>dev
|
||||
<ul>
|
||||
<li>secret.test.local</li>
|
||||
<li>secret2.test.local</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Users policy</strong>
|
||||
<ul>
|
||||
<li>harry
|
||||
<ul>
|
||||
<li>secret1.test.local</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>bob
|
||||
<ul>
|
||||
<li>*.mail.test.local</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -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>
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE>
|
||||
<html>
|
||||
<head>
|
||||
<title>Public resource</title>
|
||||
<link rel="icon" href="/icon.png" type="image/png" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Public resource</h1>
|
||||
<p>This is a public resource.<br/>
|
||||
Go back to <a href="https://home.test.local:8080/">home page</a>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
|
@ -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>
|
|
@ -50,11 +50,128 @@ http {
|
|||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
root /usr/share/nginx/html;
|
||||
root /usr/share/nginx/html/home.test.local;
|
||||
|
||||
server_name secret1.test.local secret2.test.local secret.test.local
|
||||
home.test.local mx1.mail.test.local mx2.mail.test.local
|
||||
public.test.local;
|
||||
server_name home.test.local;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate /etc/ssl/server.crt;
|
||||
ssl_certificate_key /etc/ssl/server.key;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
root /usr/share/nginx/html/public.test.local;
|
||||
|
||||
server_name public.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_pass http://authelia/verify;
|
||||
}
|
||||
|
||||
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;
|
||||
error_page 403 = https://auth.test.local:8080/error/403;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
root /usr/share/nginx/html/admin.test.local;
|
||||
|
||||
server_name admin.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_pass http://authelia/verify;
|
||||
}
|
||||
|
||||
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;
|
||||
error_page 403 = https://auth.test.local:8080/error/403;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
root /usr/share/nginx/html/dev.test.local;
|
||||
|
||||
server_name dev.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_pass http://authelia/verify;
|
||||
}
|
||||
|
||||
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;
|
||||
error_page 403 = https://auth.test.local:8080/error/403;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
root /usr/share/nginx/html/mail.test.local;
|
||||
|
||||
server_name mx1.mail.test.local mx2.mail.test.local;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate /etc/ssl/server.crt;
|
||||
|
@ -70,7 +187,7 @@ http {
|
|||
proxy_pass http://authelia/verify;
|
||||
}
|
||||
|
||||
location = /secret.html {
|
||||
location / {
|
||||
auth_request /auth_verify;
|
||||
|
||||
auth_request_set $redirect $upstream_http_redirect;
|
||||
|
|
|
@ -1,35 +1,109 @@
|
|||
|
||||
import { ACLConfiguration } from "../configuration/Configuration";
|
||||
import PatternBuilder from "./PatternBuilder";
|
||||
import { ACLConfiguration, ACLPolicy, ACLRule } from "../configuration/Configuration";
|
||||
import { IAccessController } from "./IAccessController";
|
||||
import { Winston } from "../../../types/Dependencies";
|
||||
import { DomainMatcher } from "./DomainMatcher";
|
||||
|
||||
export class AccessController {
|
||||
private logger: Winston;
|
||||
private patternBuilder: PatternBuilder;
|
||||
|
||||
constructor(configuration: ACLConfiguration, logger_: Winston) {
|
||||
this.logger = logger_;
|
||||
this.patternBuilder = new PatternBuilder(configuration, logger_);
|
||||
enum AccessReturn {
|
||||
NO_MATCHING_RULES,
|
||||
MATCHING_RULES_AND_ACCESS,
|
||||
MATCHING_RULES_AND_NO_ACCESS
|
||||
}
|
||||
|
||||
function AllowedRule(rule: ACLRule) {
|
||||
return rule.policy == "allow";
|
||||
}
|
||||
|
||||
function MatchDomain(actualDomain: string) {
|
||||
return function (rule: ACLRule): boolean {
|
||||
return DomainMatcher.match(actualDomain, rule.domain);
|
||||
};
|
||||
}
|
||||
|
||||
function MatchResource(actualResource: string) {
|
||||
return function (rule: ACLRule): boolean {
|
||||
// If resources key is not provided, the rule applies to all resources.
|
||||
if (!rule.resources) return true;
|
||||
|
||||
for (let i = 0; i < rule.resources.length; ++i) {
|
||||
const regexp = new RegExp(rule.resources[i]);
|
||||
if (regexp.test(actualResource)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
isDomainAllowedForUser(domain: string, user: string, groups: string[]): boolean {
|
||||
const allowed_domains = this.patternBuilder.getAllowedDomains(user, groups);
|
||||
function SelectPolicy(rule: ACLRule): ("allow" | "deny") {
|
||||
return rule.policy;
|
||||
}
|
||||
|
||||
// Allow all matcher
|
||||
if (allowed_domains.length == 1 && allowed_domains[0] == "*") return true;
|
||||
export class AccessController implements IAccessController {
|
||||
private logger: Winston;
|
||||
private readonly configuration: ACLConfiguration;
|
||||
|
||||
this.logger.debug("ACL: trying to match %s with %s", domain,
|
||||
JSON.stringify(allowed_domains));
|
||||
for (let i = 0; i < allowed_domains.length; ++i) {
|
||||
const allowed_domain = allowed_domains[i];
|
||||
if (allowed_domain.startsWith("*") &&
|
||||
domain.endsWith(allowed_domain.substr(1))) {
|
||||
return true;
|
||||
}
|
||||
else if (domain == allowed_domain) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
constructor(configuration: ACLConfiguration, logger_: Winston) {
|
||||
this.logger = logger_;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
private isAccessAllowedInRules(rules: ACLRule[], domain: string, resource: string): AccessReturn {
|
||||
if (!rules)
|
||||
return AccessReturn.NO_MATCHING_RULES;
|
||||
|
||||
const policies = rules.map(SelectPolicy);
|
||||
|
||||
if (rules.length > 0) {
|
||||
if (policies[0] == "allow") {
|
||||
return AccessReturn.MATCHING_RULES_AND_ACCESS;
|
||||
}
|
||||
else {
|
||||
return AccessReturn.MATCHING_RULES_AND_NO_ACCESS;
|
||||
}
|
||||
}
|
||||
return AccessReturn.NO_MATCHING_RULES;
|
||||
}
|
||||
|
||||
private getMatchingUserRules(user: string, domain: string, resource: string): ACLRule[] {
|
||||
const userRules = this.configuration.users[user];
|
||||
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 [];
|
||||
return rules.filter(MatchDomain(domain)).filter(MatchResource(resource));
|
||||
}
|
||||
|
||||
private isAccessAllowedDefaultPolicy(): boolean {
|
||||
return this.configuration.default_policy == "allow";
|
||||
}
|
||||
|
||||
isAccessAllowed(domain: string, resource: string, user: string, groups: string[]): boolean {
|
||||
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 access = this.isAccessAllowedInRules(rules, domain, resource);
|
||||
if (access == AccessReturn.MATCHING_RULES_AND_ACCESS)
|
||||
return true;
|
||||
else if (access == AccessReturn.MATCHING_RULES_AND_NO_ACCESS)
|
||||
return false;
|
||||
|
||||
return this.isAccessAllowedDefaultPolicy();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
export class DomainMatcher {
|
||||
static match(domain: string, allowedDomain: string): boolean {
|
||||
if (allowedDomain.startsWith("*") &&
|
||||
domain.endsWith(allowedDomain.substr(1))) {
|
||||
return true;
|
||||
}
|
||||
else if (domain == allowedDomain) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
export interface IAccessController {
|
||||
isAccessAllowed(domain: string, resource: string, user: string, groups: string[]): boolean;
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
|
||||
import { Winston } from "../../../types/Dependencies";
|
||||
import { ACLConfiguration, ACLGroupsRules, ACLUsersRules, ACLDefaultRules } from "../configuration/Configuration";
|
||||
import objectPath = require("object-path");
|
||||
|
||||
export default class AccessControlPatternBuilder {
|
||||
logger: Winston;
|
||||
configuration: ACLConfiguration;
|
||||
|
||||
constructor(configuration: ACLConfiguration | undefined, logger_: Winston) {
|
||||
this.configuration = configuration;
|
||||
this.logger = logger_;
|
||||
}
|
||||
|
||||
private buildFromGroups(groups: string[]): string[] {
|
||||
let allowed_domains: string[] = [];
|
||||
const groups_policy = objectPath.get<ACLConfiguration, ACLGroupsRules>(this.configuration, "groups");
|
||||
if (groups_policy) {
|
||||
for (let i = 0; i < groups.length; ++i) {
|
||||
const group = groups[i];
|
||||
if (group in groups_policy) {
|
||||
const group_policy: string[] = groups_policy[group];
|
||||
allowed_domains = allowed_domains.concat(groups_policy[group]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowed_domains;
|
||||
}
|
||||
|
||||
private buildFromUser(user: string): string[] {
|
||||
let allowed_domains: string[] = [];
|
||||
const users_policy = objectPath.get<ACLConfiguration, ACLUsersRules>(this.configuration, "users");
|
||||
if (users_policy) {
|
||||
if (user in users_policy) {
|
||||
allowed_domains = allowed_domains.concat(users_policy[user]);
|
||||
}
|
||||
}
|
||||
return allowed_domains;
|
||||
}
|
||||
|
||||
getAllowedDomains(user: string, groups: string[]): string[] {
|
||||
if (!this.configuration) {
|
||||
this.logger.debug("No access control rules found." +
|
||||
"Default policy to allow all.");
|
||||
return ["*"]; // No configuration means, no restrictions.
|
||||
}
|
||||
|
||||
let allowed_domains: string[] = [];
|
||||
const default_policy = objectPath.get<ACLConfiguration, ACLDefaultRules>(this.configuration, "default");
|
||||
if (default_policy) {
|
||||
allowed_domains = allowed_domains.concat(default_policy);
|
||||
}
|
||||
|
||||
allowed_domains = allowed_domains.concat(this.buildFromGroups(groups));
|
||||
allowed_domains = allowed_domains.concat(this.buildFromUser(user));
|
||||
|
||||
this.logger.debug("ACL: user \'%s\' is allowed access to %s", user,
|
||||
JSON.stringify(allowed_domains));
|
||||
return allowed_domains;
|
||||
}
|
||||
}
|
|
@ -1,32 +1,32 @@
|
|||
export interface UserLdapConfiguration {
|
||||
url: string;
|
||||
base_dn: string;
|
||||
|
||||
|
||||
additional_users_dn?: string;
|
||||
users_filter?: string;
|
||||
|
||||
additional_groups_dn?: string;
|
||||
groups_filter?: string;
|
||||
|
||||
|
||||
group_name_attribute?: string;
|
||||
mail_attribute?: string;
|
||||
|
||||
|
||||
user: string; // admin username
|
||||
password: string; // admin password
|
||||
}
|
||||
|
||||
export interface LdapConfiguration {
|
||||
url: string;
|
||||
|
||||
|
||||
users_dn: string;
|
||||
users_filter: string;
|
||||
|
||||
groups_dn: string;
|
||||
groups_filter: string;
|
||||
|
||||
|
||||
group_name_attribute: string;
|
||||
mail_attribute: string;
|
||||
|
||||
|
||||
user: string; // admin username
|
||||
password: string; // admin password
|
||||
}
|
||||
|
@ -35,12 +35,21 @@ type UserName = string;
|
|||
type GroupName = string;
|
||||
type DomainPattern = string;
|
||||
|
||||
export type ACLDefaultRules = DomainPattern[];
|
||||
export type ACLGroupsRules = { [group: string]: string[]; };
|
||||
export type ACLUsersRules = { [user: string]: string[]; };
|
||||
export type ACLPolicy = 'deny' | 'allow';
|
||||
|
||||
export type ACLRule = {
|
||||
domain: string;
|
||||
policy: ACLPolicy;
|
||||
resources?: string[];
|
||||
}
|
||||
|
||||
export type ACLDefaultRules = ACLRule[];
|
||||
export type ACLGroupsRules = { [group: string]: ACLRule[]; };
|
||||
export type ACLUsersRules = { [user: string]: ACLRule[]; };
|
||||
|
||||
export interface ACLConfiguration {
|
||||
default: ACLDefaultRules;
|
||||
default_policy: ACLPolicy;
|
||||
any: ACLDefaultRules;
|
||||
groups: ACLGroupsRules;
|
||||
users: ACLUsersRules;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import exceptions = require("../../Exceptions");
|
|||
import winston = require("winston");
|
||||
import AuthenticationValidator = require("../../AuthenticationValidator");
|
||||
import ErrorReplies = require("../../ErrorReplies");
|
||||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../AuthenticationSession");
|
||||
|
||||
function verify_filter(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
|
@ -27,10 +27,11 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro
|
|||
const groups = authSession.groups;
|
||||
|
||||
const host = objectPath.get<express.Request, string>(req, "headers.host");
|
||||
const domain = host.split(":")[0];
|
||||
console.log(domain);
|
||||
const path = objectPath.get<express.Request, string>(req, "headers.x-original-uri");
|
||||
|
||||
const isAllowed = accessController.isDomainAllowedForUser(domain, username, groups);
|
||||
const domain = host.split(":")[0];
|
||||
|
||||
const isAllowed = accessController.isAccessAllowed(domain, path, username, groups);
|
||||
if (!isAllowed) return BluebirdPromise.reject(
|
||||
new exceptions.DomainAccessDenied("User '" + username + "' does not have access to " + domain));
|
||||
|
||||
|
|
|
@ -7,13 +7,18 @@ Feature: User has access restricted access to domains
|
|||
And I use "REGISTERED" as TOTP token handle
|
||||
And I click on "TOTP"
|
||||
Then I have access to:
|
||||
| url |
|
||||
| https://public.test.local:8080/secret.html |
|
||||
| https://secret.test.local:8080/secret.html |
|
||||
| https://secret1.test.local:8080/secret.html |
|
||||
| https://secret2.test.local:8080/secret.html |
|
||||
| https://mx1.mail.test.local:8080/secret.html |
|
||||
| https://mx2.mail.test.local:8080/secret.html |
|
||||
| url |
|
||||
| https://public.test.local:8080/secret.html |
|
||||
| https://dev.test.local:8080/groups/admin/secret.html |
|
||||
| https://dev.test.local:8080/groups/dev/secret.html |
|
||||
| https://dev.test.local:8080/users/john/secret.html |
|
||||
| https://dev.test.local:8080/users/harry/secret.html |
|
||||
| https://dev.test.local:8080/users/bob/secret.html |
|
||||
| https://admin.test.local:8080/secret.html |
|
||||
| https://mx1.mail.test.local:8080/secret.html |
|
||||
And I have no access to:
|
||||
| url |
|
||||
| https://mx2.mail.test.local:8080/secret.html |
|
||||
|
||||
@need-registered-user-bob
|
||||
Scenario: User bob has restricted access
|
||||
|
@ -22,15 +27,18 @@ Feature: User has access restricted access to domains
|
|||
And I use "REGISTERED" as TOTP token handle
|
||||
And I click on "TOTP"
|
||||
Then I have access to:
|
||||
| url |
|
||||
| https://public.test.local:8080/secret.html |
|
||||
| https://secret.test.local:8080/secret.html |
|
||||
| https://secret2.test.local:8080/secret.html |
|
||||
| https://mx1.mail.test.local:8080/secret.html |
|
||||
| https://mx2.mail.test.local:8080/secret.html |
|
||||
| url |
|
||||
| https://public.test.local:8080/secret.html |
|
||||
| https://dev.test.local:8080/groups/dev/secret.html |
|
||||
| https://dev.test.local:8080/users/bob/secret.html |
|
||||
| https://mx1.mail.test.local:8080/secret.html |
|
||||
| https://mx2.mail.test.local:8080/secret.html |
|
||||
And I have no access to:
|
||||
| url |
|
||||
| https://secret1.test.local:8080/secret.html |
|
||||
| url |
|
||||
| https://dev.test.local:8080/groups/admin/secret.html |
|
||||
| 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 |
|
||||
|
||||
@need-registered-user-harry
|
||||
Scenario: User harry has restricted access
|
||||
|
@ -39,12 +47,15 @@ Feature: User has access restricted access to domains
|
|||
And I use "REGISTERED" as TOTP token handle
|
||||
And I click on "TOTP"
|
||||
Then I have access to:
|
||||
| url |
|
||||
| https://public.test.local:8080/secret.html |
|
||||
| https://secret1.test.local:8080/secret.html |
|
||||
| url |
|
||||
| https://public.test.local:8080/secret.html |
|
||||
| https://dev.test.local:8080/users/harry/secret.html |
|
||||
And I have no access to:
|
||||
| url |
|
||||
| https://secret.test.local:8080/secret.html |
|
||||
| https://secret2.test.local:8080/secret.html |
|
||||
| https://mx1.mail.test.local:8080/secret.html |
|
||||
| https://mx2.mail.test.local:8080/secret.html |
|
||||
| url |
|
||||
| https://dev.test.local:8080/groups/dev/secret.html |
|
||||
| https://dev.test.local:8080/users/bob/secret.html |
|
||||
| https://dev.test.local:8080/groups/admin/secret.html |
|
||||
| https://admin.test.local:8080/secret.html |
|
||||
| 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 |
|
||||
|
|
|
@ -18,19 +18,19 @@ Feature: User validate first factor
|
|||
Given I visit "https://auth.test.local:8080/"
|
||||
And I login with user "john" and password "password"
|
||||
And I register a TOTP secret called "Sec0"
|
||||
When I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fsecret.test.local%3A8080%2Fsecret.html"
|
||||
When I visit "https://admin.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
|
||||
And I login with user "john" and password "password"
|
||||
And I use "Sec0" as TOTP token handle
|
||||
And I click on "TOTP"
|
||||
Then I'm redirected to "https://secret.test.local:8080/secret.html"
|
||||
Then I'm redirected to "https://admin.test.local:8080/secret.html"
|
||||
|
||||
Scenario: User fails TOTP second factor
|
||||
When I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fsecret.test.local%3A8080%2Fsecret.html"
|
||||
When I visit "https://admin.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
|
||||
And I login with user "john" and password "password"
|
||||
And I use "BADTOKEN" as TOTP token
|
||||
And I click on "TOTP"
|
||||
Then I get a notification of type "error" with message "Problem with TOTP validation."
|
||||
|
||||
Scenario: Logout redirects user to redirect URL given in parameter
|
||||
When I visit "https://auth.test.local:8080/logout?redirect=https://www.google.fr"
|
||||
Then I'm redirected to "https://www.google.fr"
|
||||
When I visit "https://auth.test.local:8080/logout?redirect=https://home.test.local:8080/"
|
||||
Then I'm redirected to "https://home.test.local:8080/"
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
Feature: User is correctly redirected
|
||||
|
||||
Scenario: User is redirected to authelia when he is not authenticated
|
||||
Given I'm on https://home.test.local:8080
|
||||
When I click on the link to secret.test.local
|
||||
When I visit "https://public.test.local:8080"
|
||||
Then I'm redirected to "https://auth.test.local:8080/"
|
||||
|
||||
@need-registered-user-john
|
||||
|
@ -15,9 +14,9 @@ Feature: User is correctly redirected
|
|||
And I click on "TOTP"
|
||||
Then I'm redirected to "https://public.test.local:8080/secret.html"
|
||||
|
||||
Scenario: User Harry does not have access to https://secret.test.local:8080/secret.html and thus he must get an error 403
|
||||
Scenario: User Harry does not have access to admin domain and thus he must get an error 403
|
||||
When I register TOTP and login with user "harry" and password "password"
|
||||
And I visit "https://secret.test.local:8080/secret.html"
|
||||
And I visit "https://admin.test.local:8080/secret.html"
|
||||
Then I get an error 403
|
||||
|
||||
|
||||
|
|
|
@ -5,15 +5,15 @@ Feature: Authelia keeps user sessions despite the application restart
|
|||
When the application restarts
|
||||
Then I have access to:
|
||||
| url |
|
||||
| https://secret.test.local:8080/secret.html |
|
||||
| https://admin.test.local:8080/secret.html |
|
||||
|
||||
@need-registered-user-john
|
||||
Scenario: Secrets are stored even when Authelia restarts
|
||||
When the application restarts
|
||||
And I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fsecret.test.local%3A8080%2Fsecret.html"
|
||||
And I visit "https://admin.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
|
||||
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://secret.test.local:8080/secret.html |
|
||||
| https://admin.test.local:8080/secret.html |
|
|
@ -81,7 +81,7 @@ and I use TOTP token handle {stringInDoubleQuotes}",
|
|||
}
|
||||
|
||||
Then("I have access to:", function (dataTable: Cucumber.TableDefinition) {
|
||||
const promises = [];
|
||||
const promises: any = [];
|
||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
||||
const url = (dataTable.hashes() as any)[i].url;
|
||||
promises.push(hasAccessToSecret(url, this));
|
||||
|
|
|
@ -47,6 +47,9 @@ function CustomWorld() {
|
|||
.findElement(seleniumWebdriver.By.tagName("button"))
|
||||
.findElement(seleniumWebdriver.By.xpath("//button[contains(.,'" + buttonText + "')]"))
|
||||
.click();
|
||||
})
|
||||
.then(function () {
|
||||
return that.driver.sleep(500);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ describe("test session configuration builder", function () {
|
|||
it("should return session options without redis options", function () {
|
||||
const configuration: AppConfiguration = {
|
||||
access_control: {
|
||||
default: [],
|
||||
default_policy: "deny",
|
||||
any: [],
|
||||
users: {},
|
||||
groups: {}
|
||||
},
|
||||
|
@ -81,7 +82,8 @@ describe("test session configuration builder", function () {
|
|||
it("should return session options with redis options", function () {
|
||||
const configuration: AppConfiguration = {
|
||||
access_control: {
|
||||
default: [],
|
||||
default_policy: "deny",
|
||||
any: [],
|
||||
users: {},
|
||||
groups: {}
|
||||
},
|
||||
|
|
|
@ -1,53 +1,353 @@
|
|||
|
||||
import assert = require("assert");
|
||||
import Assert = require("assert");
|
||||
import winston = require("winston");
|
||||
import { AccessController } from "../../../../src/server/lib/access_control/AccessController";
|
||||
import { ACLConfiguration } from "../../../../src/server/lib/configuration/Configuration";
|
||||
import { AccessController } from "../../../../src/server/lib/access_control/AccessController";
|
||||
import { ACLConfiguration, ACLRule } from "../../../../src/server/lib/configuration/Configuration";
|
||||
|
||||
describe("test access control manager", function () {
|
||||
let accessController: AccessController;
|
||||
let configuration: ACLConfiguration;
|
||||
let accessController: AccessController;
|
||||
let configuration: ACLConfiguration;
|
||||
|
||||
beforeEach(function () {
|
||||
configuration = {
|
||||
default_policy: "deny",
|
||||
any: [],
|
||||
users: {},
|
||||
groups: {}
|
||||
};
|
||||
accessController = new AccessController(configuration, winston);
|
||||
});
|
||||
|
||||
describe("check access control with default policy to deny", function () {
|
||||
beforeEach(function () {
|
||||
configuration = {
|
||||
default: [],
|
||||
users: {},
|
||||
groups: {}
|
||||
};
|
||||
accessController = new AccessController(configuration, winston);
|
||||
configuration.default_policy = "deny";
|
||||
});
|
||||
|
||||
describe("check access control matching", function () {
|
||||
beforeEach(function () {
|
||||
configuration.default = ["home.example.com", "*.public.example.com"];
|
||||
configuration.users = {
|
||||
user1: ["user1.example.com", "user1.mail.example.com"]
|
||||
};
|
||||
configuration.groups = {
|
||||
group1: ["secret2.example.com"],
|
||||
group2: ["secret.example.com", "secret1.example.com"]
|
||||
};
|
||||
});
|
||||
|
||||
it("should allow access to secret.example.com", function () {
|
||||
assert(accessController.isDomainAllowedForUser("secret.example.com", "user", ["group1", "group2"]));
|
||||
});
|
||||
|
||||
it("should deny access to secret3.example.com", function () {
|
||||
assert(!accessController.isDomainAllowedForUser("secret3.example.com", "user", ["group1", "group2"]));
|
||||
});
|
||||
|
||||
it("should allow access to home.example.com", function () {
|
||||
assert(accessController.isDomainAllowedForUser("home.example.com", "user", ["group1", "group2"]));
|
||||
});
|
||||
|
||||
it("should allow access to user1.example.com", function () {
|
||||
assert(accessController.isDomainAllowedForUser("user1.example.com", "user1", ["group1", "group2"]));
|
||||
});
|
||||
|
||||
it("should allow access *.public.example.com", function () {
|
||||
assert(accessController.isDomainAllowedForUser("user.public.example.com", "nouser", []));
|
||||
assert(accessController.isDomainAllowedForUser("test.public.example.com", "nouser", []));
|
||||
});
|
||||
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"]));
|
||||
});
|
||||
|
||||
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 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"]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
|
||||
import assert = require("assert");
|
||||
import winston = require("winston");
|
||||
|
||||
import PatternBuilder from "../../../../src/server/lib/access_control/PatternBuilder";
|
||||
import { ACLConfiguration } from "../../../../src/server/lib/configuration/Configuration";
|
||||
|
||||
describe("test access control manager", function () {
|
||||
describe("test access control pattern builder when no configuration is provided", () => {
|
||||
it("should allow access to the user", () => {
|
||||
const patternBuilder = new PatternBuilder(undefined, winston);
|
||||
|
||||
const allowed_domains = patternBuilder.getAllowedDomains("user", ["group1"]);
|
||||
assert.deepEqual(allowed_domains, ["*"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("test access control pattern builder", function () {
|
||||
let patternBuilder: PatternBuilder;
|
||||
let configuration: ACLConfiguration;
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
configuration = {
|
||||
default: [],
|
||||
users: {},
|
||||
groups: {}
|
||||
};
|
||||
patternBuilder = new PatternBuilder(configuration, winston);
|
||||
});
|
||||
|
||||
it("should deny all if nothing is defined in the config", function () {
|
||||
const allowed_domains = patternBuilder.getAllowedDomains("user", ["group1", "group2"]);
|
||||
assert.deepEqual(allowed_domains, []);
|
||||
});
|
||||
|
||||
it("should allow domain test.example.com to all users if defined in" +
|
||||
" default policy", function () {
|
||||
configuration.default = ["test.example.com"];
|
||||
const allowed_domains = patternBuilder.getAllowedDomains("user", ["group1", "group2"]);
|
||||
assert.deepEqual(allowed_domains, ["test.example.com"]);
|
||||
});
|
||||
|
||||
it("should allow domain test.example.com to all users in group mygroup", function () {
|
||||
const allowed_domains0 = patternBuilder.getAllowedDomains("user", ["group1", "group1"]);
|
||||
assert.deepEqual(allowed_domains0, []);
|
||||
|
||||
configuration.groups = {
|
||||
mygroup: ["test.example.com"]
|
||||
};
|
||||
|
||||
const allowed_domains1 = patternBuilder.getAllowedDomains("user", ["group1", "group2"]);
|
||||
assert.deepEqual(allowed_domains1, []);
|
||||
|
||||
const allowed_domains2 = patternBuilder.getAllowedDomains("user", ["group1", "mygroup"]);
|
||||
assert.deepEqual(allowed_domains2, ["test.example.com"]);
|
||||
});
|
||||
|
||||
it("should allow domain test.example.com based on per user config", function () {
|
||||
const allowed_domains0 = patternBuilder.getAllowedDomains("user", ["group1"]);
|
||||
assert.deepEqual(allowed_domains0, []);
|
||||
|
||||
configuration.users = {
|
||||
user1: ["test.example.com"]
|
||||
};
|
||||
|
||||
const allowed_domains1 = patternBuilder.getAllowedDomains("user", ["group1", "mygroup"]);
|
||||
assert.deepEqual(allowed_domains1, []);
|
||||
|
||||
const allowed_domains2 = patternBuilder.getAllowedDomains("user1", ["group1", "mygroup"]);
|
||||
assert.deepEqual(allowed_domains2, ["test.example.com"]);
|
||||
});
|
||||
|
||||
it("should allow domains from user and groups", function () {
|
||||
configuration.groups = {
|
||||
group2: ["secret.example.com", "secret1.example.com"]
|
||||
};
|
||||
configuration.users = {
|
||||
user: ["test.example.com"]
|
||||
};
|
||||
|
||||
const allowed_domains0 = patternBuilder.getAllowedDomains("user", ["group1", "group2"]);
|
||||
assert.deepEqual(allowed_domains0, [
|
||||
"secret.example.com",
|
||||
"secret1.example.com",
|
||||
"test.example.com",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should allow domains from several groups", function () {
|
||||
configuration.groups = {
|
||||
group1: ["secret2.example.com"],
|
||||
group2: ["secret.example.com", "secret1.example.com"]
|
||||
};
|
||||
|
||||
const allowed_domains0 = patternBuilder.getAllowedDomains("user", ["group1", "group2"]);
|
||||
assert.deepEqual(allowed_domains0, [
|
||||
"secret2.example.com",
|
||||
"secret.example.com",
|
||||
"secret1.example.com",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should allow domains from several groups and default policy", function () {
|
||||
configuration.default = ["home.example.com"];
|
||||
configuration.groups = {
|
||||
group1: ["secret2.example.com"],
|
||||
group2: ["secret.example.com", "secret1.example.com"]
|
||||
};
|
||||
|
||||
const allowed_domains0 = patternBuilder.getAllowedDomains("user", ["group1", "group2"]);
|
||||
assert.deepEqual(allowed_domains0, [
|
||||
"home.example.com",
|
||||
"secret2.example.com",
|
||||
"secret.example.com",
|
||||
"secret1.example.com",
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +1,8 @@
|
|||
import * as Assert from "assert";
|
||||
import { UserConfiguration, LdapConfiguration } from "../../../../src/server/lib/configuration/Configuration";
|
||||
import {
|
||||
UserConfiguration,
|
||||
LdapConfiguration, ACLConfiguration
|
||||
} from "../../../../src/server/lib/configuration/Configuration";
|
||||
import { ConfigurationAdapter } from "../../../../src/server/lib/configuration/ConfigurationAdapter";
|
||||
|
||||
describe("test config adapter", function () {
|
||||
|
@ -94,15 +97,17 @@ describe("test config adapter", function () {
|
|||
it("should get the access_control config", function () {
|
||||
const yaml_config = build_yaml_config();
|
||||
yaml_config.access_control = {
|
||||
default: [],
|
||||
default_policy: "deny",
|
||||
any: [],
|
||||
users: {},
|
||||
groups: {}
|
||||
};
|
||||
const config = ConfigurationAdapter.adapt(yaml_config);
|
||||
Assert.deepEqual(config.access_control, {
|
||||
default: [],
|
||||
default_policy: "deny",
|
||||
any: [],
|
||||
users: {},
|
||||
groups: {}
|
||||
});
|
||||
} as ACLConfiguration);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
import sinon = require("sinon");
|
||||
|
||||
export interface AccessControllerMock {
|
||||
isDomainAllowedForUser: sinon.SinonStub;
|
||||
}
|
||||
|
||||
export function AccessControllerMock() {
|
||||
return {
|
||||
isDomainAllowedForUser: sinon.stub()
|
||||
};
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
import Sinon = require("sinon");
|
||||
import { IAccessController } from "../../../../src/server/lib/access_control/IAccessController";
|
||||
|
||||
export class AccessControllerStub implements IAccessController {
|
||||
isAccessAllowedMock: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.isAccessAllowedMock = Sinon.stub();
|
||||
}
|
||||
|
||||
isAccessAllowed(domain: string, resource: string, user: string, groups: string[]): boolean {
|
||||
return this.isAccessAllowedMock(domain, resource, user, groups);
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ import AuthenticationSession = require("../../../../../src/server/lib/Authentica
|
|||
import Endpoints = require("../../../../../src/server/endpoints");
|
||||
|
||||
import AuthenticationRegulatorMock = require("../../mocks/AuthenticationRegulator");
|
||||
import AccessControllerMock = require("../../mocks/AccessController");
|
||||
import { AccessControllerStub } from "../../mocks/AccessControllerStub";
|
||||
import ExpressMock = require("../../mocks/express");
|
||||
import ServerVariablesMock = require("../../mocks/ServerVariablesMock");
|
||||
import { ServerVariables } from "../../../../../src/server/lib/ServerVariablesHandler";
|
||||
|
@ -22,7 +22,7 @@ describe("test the first factor validation route", function () {
|
|||
let groups: string[];
|
||||
let configuration;
|
||||
let regulator: AuthenticationRegulatorMock.AuthenticationRegulatorMock;
|
||||
let accessController: AccessControllerMock.AccessControllerMock;
|
||||
let accessController: AccessControllerStub;
|
||||
let serverVariables: ServerVariables;
|
||||
|
||||
beforeEach(function () {
|
||||
|
@ -36,8 +36,8 @@ describe("test the first factor validation route", function () {
|
|||
emails = ["test_ok@example.com"];
|
||||
groups = ["group1", "group2" ];
|
||||
|
||||
accessController = AccessControllerMock.AccessControllerMock();
|
||||
accessController.isDomainAllowedForUser.returns(true);
|
||||
accessController = new AccessControllerStub();
|
||||
accessController.isAccessAllowedMock.returns(true);
|
||||
|
||||
regulator = AuthenticationRegulatorMock.AuthenticationRegulatorMock();
|
||||
regulator.regulate.returns(BluebirdPromise.resolve());
|
||||
|
|
|
@ -10,17 +10,17 @@ import BluebirdPromise = require("bluebird");
|
|||
import express = require("express");
|
||||
|
||||
import ExpressMock = require("../../mocks/express");
|
||||
import AccessControllerMock = require("../../mocks/AccessController");
|
||||
import { AccessControllerStub } from "../../mocks/AccessControllerStub";
|
||||
import ServerVariablesMock = require("../../mocks/ServerVariablesMock");
|
||||
|
||||
describe("test authentication token verification", function () {
|
||||
let req: ExpressMock.RequestMock;
|
||||
let res: ExpressMock.ResponseMock;
|
||||
let accessController: AccessControllerMock.AccessControllerMock;
|
||||
let accessController: AccessControllerStub;
|
||||
|
||||
beforeEach(function () {
|
||||
accessController = AccessControllerMock.AccessControllerMock();
|
||||
accessController.isDomainAllowedForUser.returns(true);
|
||||
accessController = new AccessControllerStub();
|
||||
accessController.isAccessAllowedMock.returns(true);
|
||||
|
||||
req = ExpressMock.RequestMock();
|
||||
res = ExpressMock.ResponseMock();
|
||||
|
@ -128,8 +128,8 @@ describe("test authentication token verification", function () {
|
|||
|
||||
req.headers.host = "test.example.com";
|
||||
|
||||
accessController.isDomainAllowedForUser.returns(false);
|
||||
accessController.isDomainAllowedForUser.withArgs("test.example.com", "user", ["group1", "group2"]).returns(true);
|
||||
accessController.isAccessAllowedMock.returns(false);
|
||||
accessController.isAccessAllowedMock.withArgs("test.example.com", "user", ["group1", "group2"]).returns(true);
|
||||
|
||||
return test_unauthorized_403({
|
||||
first_factor: true,
|
||||
|
|
Loading…
Reference in New Issue