populationgenomics / metamist

Sample level metadata system
MIT License
1 stars 1 forks source link

Permissions system update (redux) #857

Closed dancoates closed 2 months ago

dancoates commented 2 months ago

Redo of #829 due to forgetting to squash 🤦

Context

Metamist currently has a fairly simple permission system. Each project defines a read group and a write group, these two groups define a list of group members. The members of the read group will have readonly access to the project, and the members of the write group have full unrestricted write access to the project.

Write actions within metamist are primarily performed programmatically via the API, but with some upcoming new features there will soon be the need to allow human users to perform a limited set of write actions. Some examples of these new features are:

We do not want to grant full write access to allow users to perform these actions, so it is necessary to create a more nuanced role based permissions system that allows limited access to certain write operations but restricts access to anything destructive.

Solution

Moving away from storing the majority of permissions in groups to creating a direct relationship between users and projects. There is a lot of unnecessary redundancy from the way permissions are stored currently. By convention a read group will be suffixed with -read and similar for a write group. These are then referenced in the read_group_id and write_group_id columns in the project table. To continue extending this would result in a complex system with lots of columns on the project table and potentially a situation where the group suffixed with -read ends up being the write group.

This update introduces a new table project_member. This table holds the relationship between a member (represented by an email) and the project. In addition it includes a role column which specifies a role that the user has within that project. Each member can have multiple roles within a project by there being multiple rows for them in the project_member table for any given project.

In addition to the table updates, this PR also moves much of the permissions checking logic away from the tables/project.py file which acts as the interface for projects in the database. Instead this logic is now on the Connection class. This class is available pretty much everywhere throughout the project so it makes it much easier to quickly check permissions. This change also mitigates a class of performance problems that was starting to occur in the graphql resolvers where permissions checks were not being cached between resolvers. The Connection object is constructed once at the beginning of every request and all permission checks from then on are synchronous and should be very fast.

A permission check looks like:

self.connection.get_and_check_access_to_projects_for_ids(
    project_ids=[1,2,3],
    allowed_roles=ReadAccessRoles,
)

Where ReadAccessRoles is a set of the project member role enums. If the current user has any one of the specified roles in all of the specified projects the permission check will pass.