Permissions
In order to protect your data, BioDM provides two structures of permissions.
Those are requiring a keycloak service running and the following variables to
be provided in a .env file at the same level as your demo.py script.
KC_HOST=
KC_REALM=
KC_PUBLIC_KEY=
KC_ADMIN=
KC_ADMIN_PASSWORD=
KC_CLIENT_ID=
KC_CLIENT_SECRET=
Server level: REQUIRE_AUTH
Setting REQUIRE_AUTH=True config argument, will make all routes, except the ones explicitely
marked public (such as /login and /[resources/|]schemas) require authentication.
See more at Routing and Auth
Coarse: Static rule on a Controller endpoint
biodm.utils.security module contains three decorators that are meant to be used
on Controller endpoints in order to apply static permissions directly within the codebase.
@token_required()Protects the endpoint demanding incomming requests to by signed with a valid
Keycloak JW Token
@group_required(groups=[gpath_1,... gpath_n])Like token_required, plus assesses that requesting
Useris part of one of thoseGroupsA group path: starts with no leading delimiter and has
/replaced by__
@admin_required()group_required special case, requesting
Usermust be part ofadmingroup.
On our example, this is how you could apply those on DatasetController:
from biodm.utils.security import group_required, admin_required
class DatasetController(bdc.ResourceController):
def __init__(self, app) -> None:
super().__init__(app=app)
self.write = group_required(self.create, ['my_team'])
self.update = group_required(self.update, ['my_team'])
self.release = group_required(self.release, ['my_team__data_owners'])
self.delete = admin_required(self.delete)
Here we restricted the creation and updating of datasets to my_team, publishing a new release
is reserved to data_owners subgroup of and deletion is admin priviledge.
Implicitely reading data is left public.
Nested propagation
For some endpoints, those decorators shall also affect nested behaviour. I.e.
The check is applied when creating a resource whose
createendpoint is protected by those decorators from a composite upper level resource.filtering/reading a nested collocation will also need to pass the check if that given resource has its
readendpoint protected.
Fine: Dynamic user owned permissions
If your data management platform is intended to receive data from users, BioDM provide tools to
let them in control of permissions by providing them directly as the resource input data.
biodm.components.Permission class is designed as an extra SQLAlchemy table argument that let
you flag composition pattern (i.e. One-to-Many relationships) with the following permissions that
will be applied recursively for all children of that particular entity:
ReadWriteDownload
In our example:
from biodm.components import Permission
class Dataset(bd.components.Base):
id : sao.Mapped[int] = sa.Column(sa.Integer, primary_key=True)
...
files : sao.Mapped[List["File"]] = sao.relationship(back_populates="dataset")
__permissions__ = (
Permission(files, write=True, read=False, download=True),
)
The latter enables File permissions at the Dataset level.
In other words it lets you define for a top level resource who is allowed to interact with a nested collection and its elements.
Note
Those permissions will be taken into account when directly accessing /files API routes.
Note
You always need a top level resource. This system is thought to be combined with decorator based permission for such resources.
Nesting and propagation
This tool offers flexible options. Imagine a case with one more level of collections with a
Project table, containing a collection of Dataset such as showcased in example.
class Project(Base):
id = Column(Integer, nullable=False, primary_key=True)
...
datasets: Mapped[List["Dataset"]] = relationship(back_populates="project")
Then you may use a string selector to apply that top level permission directly on a lower level resource, skipping the mid level.
class Project(Base):
...
__permissions__ = (
Permission("datasets.files", download=True),
)
Moreover, you have the option of propagating that top level permission to the lower nested collections. Sharing those permissions between intermediate level and lower level.
class Project(Base):
...
__permissions__ = (
Permission(datasets, read=True, write=True, download=True, propagates_to=["files"]),
)
Self
The term self is also supported in this configuration, it will bind those permissions
on the same resource.
class Project(Base):
...
__permissions__ = (
Permission("self", read=True),
)
Warning
It shall raise an ImplementationError if used in conjunction with write
as it does not makes sense to tie writing rights directly on a resource.
Strict composition
Currently, BioDM assumes a strict composition pattern of resource for those permissions.
Which allow them to be taken into account while directly accessing children resource routes
like mentioned above.
Unfortunately, that also means that distributing permissions from two, or more, parent level resources is currently not tested and shall most likely result in soft-locking those resources.
This may or may not be supported in a future version of the Core, depending on technical feasibility.
If you wish to achieve something in that vein, it is for now advised to create an identical resource with a different name.