Skip to content

Commit

Permalink
feat: add Casbin-based access control (#109)
Browse files Browse the repository at this point in the history
Co-authored-by: Kushagra Nagori <kushagra@Kushagras-MacBook-Pro.local>
Co-authored-by: Alex Kanitz <alexander.kanitz@unibas.ch>
Co-authored-by: Alex Kanitz <alexander.kanitz@alumni.ethz.ch>
  • Loading branch information
4 people committed Sep 2, 2022
1 parent 8676b93 commit 05be4df
Show file tree
Hide file tree
Showing 52 changed files with 3,093 additions and 82 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
examples/
7 changes: 7 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
["3.9", "3.9-slim-buster"],
["3.10", "3.10-slim-buster"]
]
mongodb-version: ["4.2", "4.4", "5.0"]
mongodb-port: [12345]

steps:
- uses: actions/checkout@v2
Expand All @@ -37,6 +39,11 @@ jobs:
pip install -r requirements_dev.txt
- name: Lint with flake8
run: flake8
- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.7.0
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-port: ${{ matrix.mongodb-port }}
- name: Calculate unit test coverage
run: |
coverage run --source foca -m pytest -W ignore::DeprecationWarning
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -327,4 +327,6 @@ pip-selfcheck.json
.vscode
examples/petstore/petstore.modified.yaml
examples/petstore/data/
examples/petstore-access-control/petstore-access-control.modified.yaml
examples/petstore-access-control/data/
*.modified.yaml
63 changes: 47 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,13 @@ Check the [API docs][badge-url-docs] for further details.
* [Configuring logging](#configuring-logging)
* [Configuring security](#configuring-security)
* [Configuring the server](#configuring-the-server)
* [Custom configuration](#cusomt-configuration)
* [Custom configuration](#custom-configuration)
* [Accessing configuration parameters](#accessing-configuration-parameters)
* [Utilities](#utilities)
* [Database utilities](#database-utilities)
* [Logging utitlies](#logging-utilities)
* [Miscellaneous utilities](#miscellaneous-utilities)
* [Access Control utilities](#access-control-utilities)
* [Contributing](#contributing)
* [Versioning](#versioning)
* [License](#license)
Expand All @@ -65,7 +66,7 @@ Check the [API docs][badge-url-docs] for further details.
pip install foca
```

(2) Create a [configuration file](#configuration-file).
(2) Create a [configuration](#configuration) file.

(3) Import the FOCA class and pass your config file:

Expand Down Expand Up @@ -302,9 +303,10 @@ log:

FOCA offers some convenience functionalities for securing your app.
Specifically, it allows you to configure the validation of [JSON Web
Token (JWT)][res-jwt]-based authorization and the use of [cross-origin resource
sharing (CORS)][res-cors].To make use of them, include the `security` top-level
section in your app configuration, as well as the desired sublevel section(s):
Token (JWT)][res-jwt]-based authorization, a [Casbin][res-casbin]-based access
control model, and the use of [cross-origin resource sharing (CORS)][res-cors].
To make use of them, include the `security` top-level section in your app
configuration, as well as the desired sublevel section(s):

```yaml
security:
Expand All @@ -316,6 +318,13 @@ security:
- userinfo
- public_key
validation_checks: any
access_control:
api_specs: 'path/to/your/access/control/specs'
api_controllers: 'path/to/your/access/control/spec/controllers'
api_route: '/route/to/access_control_api'
db_name: access_control_db_name
collection_name: access_control_collection_name
model: access_control_model_definition
cors:
enabled: True
```
Expand All @@ -324,10 +333,19 @@ security:
> `RS256` algorithm, would not allow expired tokens and would grant access to
> a protected endpoint if `any` of the two listed validation methods (via the
> identity provider's `/userinfo` endpoint or its JSON Web Key (JWK) public
> key. Furthermore, CORS would be enabled for this application.
> key. Furthermore, the application created with this config would provide
> an access control model `model`. Corresponding permissions could be accessed
> and altered by a user with admin permissions via the dedicated endpoints
> defined in the `api_specs`, operationalized by the controllers in
> `api_controllers` and hosted at `api_route`. Permissions will be stored in
> collection `collection_name` of a dedicated MongoDB database `db_name`.
> Finally, CORS would be enabled for this application.
>
> Cf. the [API model][docs-models-security] for further options and details.
**Note:** A detailed explaination of the access control implementation can be
found [here][docs-access-control].

### Configuring the server

FOCA allows you to pass certain basic configuration options to your Flask
Expand Down Expand Up @@ -401,11 +419,11 @@ configuration (like with the FOCA-specific parameters).
Apart from the reserved keyword sections listed above, you are free to include
any other sections and parameters in your app configuration file. FOCA will
simply attach these to your application instance as described
[above](#configuration-file) and shown
[below](#accessing-configuration-parameters). Note, however, that any
such parameters need to be _manually_ validated. The same is true if you
include a `custom` section but do _not_ provide a validation model class via
the `custom_config_model` parameter when instantiating `Foca`.
[above](#configuration) and shown [below](#accessing-configuration-parameters).
Note, however, that any such parameters need to be _manually_ validated. The
same is true if you include a `custom` section but do _not_ provide a
validation model class via the `custom_config_model` parameter when
instantiating `Foca`.

_Example:_

Expand Down Expand Up @@ -480,7 +498,7 @@ latest_object_id = find_id_latest("your_db_collection_instance")

### Logging utilities

FOCA provides a decorator that ca be used on any route to automatically log
FOCA provides a decorator that can be used on any route to automatically log
any requests and/or responses passing through that route:

```python
Expand Down Expand Up @@ -509,6 +527,21 @@ obj_id = generate_id(charset=string.digits, length=6)
> The above function processes and returns a random `obj_id` of length `6`
> consisting of only digits (`string.digits`).
### Access Control utilities

FOCA provides a decorator that can be used on any route to automatically
validate request on the basis of permission rules.

```python
from foca.security.access_control.register_access_control import (
check_permissions
)

@check_permissions
def your_controller():
pass
```

## Contributing

This project is a community effort and lives off your contributions, be it in
Expand Down Expand Up @@ -551,6 +584,7 @@ question etc.
[badge-url-pypi]: <https://pypi.python.org/pypi/foca>
[config-template]: templates/config.yaml
[config-petstore]: examples/petstore/config.yaml
[docs-access-control]: docs/access_control/README.md
[docs-models]: <https://foca.readthedocs.io/en/latest/modules/foca.models.html>
[docs-models-api]: <https://foca.readthedocs.io/en/latest/modules/foca.models.html#foca.models.config.APIConfig>
[docs-models-db]: <https://foca.readthedocs.io/en/latest/modules/foca.models.html#foca.models.config.DBConfig>
Expand All @@ -565,19 +599,16 @@ question etc.
[license]: LICENSE
[license-apache]: <https://www.apache.org/licenses/LICENSE-2.0>
[org-elixir-cloud]: <https://github.com/elixir-cloud-aai/elixir-cloud-aai>
[res-casbin]: <https://casbin.org/>
[res-celery]: <http://docs.celeryproject.org/>
[res-connexion]: <https://github.com/zalando/connexion>
[res-datamodel-code-generator]: <https://github.com/koxudaxi/datamodel-code-generator/>
[res-cors]: <https://flask-cors.readthedocs.io/en/latest/>
[res-elixir-cloud-coc]: <https://github.com/elixir-cloud-aai/elixir-cloud-aai/blob/dev/CODE_OF_CONDUCT.md>
[res-elixir-cloud-contributing]: <https://github.com/elixir-cloud-aai/elixir-cloud-aai/blob/dev/CONTRIBUTING.md>
[res-flask]: <http://flask.pocoo.org/>
[res-flask-app-context]: <https://flask.palletsprojects.com/en/1.1.x/appcontext/>
[res-foca]: <https://pypi.org/project/foca/>
[res-jwt]: <https://jwt.io>
[res-mongo-db]: <https://www.mongodb.com/>
[res-python-logging]: <https://docs.python.org/3/library/logging.html>
[res-python-logging-how-to]: <https://docs.python.org/3/howto/logging.html?highlight=yaml#configuring-logging>
[res-openapi]: <https://www.openapis.org/>
[res-pydantic]: <https://pydantic-docs.helpmanual.io/>
[res-rabbitmq]: <https://www.rabbitmq.com/>
Expand Down
209 changes: 209 additions & 0 deletions docs/access_control/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
# Access Control in FOCA

## Description

This document summarizes how access control works in [FOCA][res-foca], and how
it can be configured for your needs.

## Table of Contents

* [Description](#description)
* [Why do we need it?](#why-do-we-need-it)
* [How does it work?](#how-does-it-work)
* [PERM Model](#perm-model)
* [Policy](#policy)
* [Effect](#effect)
* [Request](#request)
* [Matchers](#matchers)
* [Configuration](#configuration)
* [Configuring the database](#configuring-the-database)
* [Configuring the API](#configuring-the-api)
* [Configuring header properties](#configuring-header-properties)
* [Configuring the access control
model](#configuring-the-access-control-model)
* [Exploring the API](#exploring-the-api)

## Why do we need it?

This feature is designed to allow you to create more secure applications. You
may need to restrict the API usage on the basis of specific users or perhaps
create user groups with access to specific functionalities. For example, user A
should be allowed to read a resources, while user B should _also_ be able to
modify/write to that resource. User C, on the other hand, should not be able to
access that resource at all!

To address this and similar use cases, [FOCA][res-foca] provides configurable
access control based on the popular [Casbin][casbin-docs] project.

## How does it work?

The access control model is abstracted into a configuration file based on the
**PERM (Policy, Effect, Request, Matchers) metamodel**. You can provide the
required model definition as per your particular use case. Policies can then be
defined on the basis of that model, stored in a MongoDB database and enforced
on incoming requests based on the matchers and effects defined in the policies.

The flow described above is highlighted in the schema below (taken from
[Casbin][casbin-docs]):

![Casbin Flow][img-casbin-flow]

### PERM Model

The PERM model is composed of four foundations - Policy, Effect, Request,
Matchers - describing the relationship between resources and users.

#### Policy

A _policy_ defines _who can do what_ or _who has what permissions_.

The basic syntax for a policy is

```console
p = sub, obj, act, eft
```

which can be read as: Who(`sub`) can/cannot(`eft`) do what(`act`) on some
resource(`obj`)?

#### Effect

An _effect_ can be understood as a model in which a logical combination
judgment is performed on the result of a matcher. For example:

```console
e = some(where(p.eft == allow))
```

This statement expressed that if the matching strategy result `p.eft` has the
result of (some) `allow`, then the final result is `True` and access is
granted.

Here `eft` can be either `allow` or `deny`. If it is not included (which is
frequently the case), the default value is `allow`.

In the [above diagram](#how-does-it-work), as per the defined policy:

1. John has permission to read `RECORD1`
2. John has no permission to modify/write to `RECORD1`
3. Harry has permission to read `RECORD1`
4. Harry has permission to modify/write to `RECORD1`

#### Request

A _request_ defines the names and order of parameters to be passed to the
matchers. A basic request is a tuple object, requiring at least a subject
(accessed entity), object (accessed resource) and action (access method).

For instance, a request definition may look like this:

```console
r = sub , obj, act
```

In the [above diagram](#how-does-it-work), as per the incoming request:

1. John wants to read `RECORD1`
2. John wants to modify/write to `RECORD1`

#### Matchers

A _matcher_ integrates the request (`r`) and policy (`p`). For example:

```console
m = r.sub == p.sub && r.act == p.act && r.obj == p.obj
```

This simple and common matching rule means that if the requested parameters
(entities, resources, and methods) are equal, that is, if they can be found in
the policy, then the policy result (`p.eft`) is returned.

## Configuration

You can tweak the default access control behavior by setting a number of
configuration parameters:

```yaml
security:
access_control:
model: access_control_model_definition
api_specs: 'path/to/your/access/control/specs'
api_controllers: 'path/to/your/access/control/spec/controllers'
api_route: '/path/to/access_control_api'
db_name: access_control_db_name
collection_name: access_control_collection_name
owner_headers: admin_identification_properties
user_headers: user_identification_properties
```

> The application created with this config would provide an access control
> model `model`. Corresponding permissions could be accessed and altered
> by a user with admin permissions via the dedicated endpoints defined in the
> `api_specs`, operationalized by the controllers in `api_controllers` and
> hosted at `api_route`. Permissions will be stored in collection
> `collection_name` of a dedicated MongoDB database `db_name`. The headers
> `owner_headers` and `user_headers` would be set for admins and regular users,
> respectively.
>
> Cf. the [API model][docs-models-access-control] for further options and details.
### Configuring the database

FOCA sets up a MongoDB database and collection to manage permission resources.
Default names are set to `access_control_db` and `permission_rules`,
respectively. To manually set the names, provide values for the `db_name` and
`collection_name` parameters, respectively.

### Configuring the API

FOCA comes with a default API for configuring permission resources. However,
you can provide a custom [Swagger 2.x or OpenAPI 3.x](res-openapi) definition
for `/permissions` endpoints via `api_specs`, provide a module containing
custom controllers for the defined endpoints via the `api_controllers`
parameter and tell FOCA where to host the endpoints via `api_route`.

The specs and controllers used by default can be accessed via the links below:

* [Default Specifications][default-specs]
* [Default Controller Definitions][default-controllers]

### Configuring the access control model

Access control works on the basis of model definitions. If a custom model
definition is not provided via parameter `model`, a [role-based access control
(RBAC) model definition][default-model] will be used by default. To learn more
on writing your own custom models, refer to the [Casbin
documentation][res-casbin-models].

### Configuring header properties

For access control to work, we must provide specific properties for every
request. These are used to validate the incoming request and to differentiate
users and owners/admins. You can set up `owner_headers` and `user_headers` to
specify custom owner and user properties, respectively.

## Exploring the API

Once your app is up and running, you can explore the access control API
endpoints via a [Swagger UI][res-swagger] in your browser, e.g.:

```bash
firefox http://<host>/admin/access-control/ui/ # or use your browser of choice
```

> The exact route at which endpoints are served depends on the value of
> `AccessControlConfig.api_route`.
![Logo_banner][img-logo-banner]

[casbin-docs]: <https://casbin.org/docs/en/how-it-works>
[docs-models-access-control]: <https://foca.readthedocs.io/en/latest/modules/foca.models.html#foca.models.config.AccessControlConfig>
[default-specs]: api/access-control-specs.yaml
[default-controllers]: ./access_control_server.py
[default-model]: api/default_model.conf
[img-casbin-flow]: ../../images/casbin_model.jpeg
[img-logo-banner]: ../../images/logo-banner.svg
[res-casbin-models]: <https://casbin.org/docs/en/supported-models>
[res-foca]: <https://pypi.org/project/foca/>
[res-openapi]: <https://www.openapis.org/>
[res-swagger]: <https://swagger.io/tools/swagger-ui/>
1 change: 1 addition & 0 deletions examples/petstore-access-control/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/data

0 comments on commit 05be4df

Please sign in to comment.