Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Note that membership in an organization is also directly modeled in the database right now via the Account.orgMembership field, and will be moved to an associative table. We may not need a “member” role though it may be more convenient.

App/System

These are legacy roles but there are still many APIs that require one of these roles, particularly around configuring an app:

...

Roles

...

Scope

...

Developer

...

App

...

Can access many APIs for the configuration of an app and related resources like app configs.

...

Researcher

...

App

...

Can see all accounts in the system regardless of organization or study boundaries.

...

Admin

...

App

...

Can call any API in the scope of the account’s app.

...

Worker

...

Global

...

Can access APIs that specifically allow the worker to call across app boundaries without switching applications first.

...

Superadmin

...

Global

...

Can do anything in any app, study, or organization.

Implementation

...

We’re reimplementing a lot of the functionality of Spring Security’s authorization support. It might be desirable to switch over rather than further implementing a custom solution. We need a table of permissions that can be used to answer the framework’s authorization questions.We will introduce a flat table of Permission records that can be easily retrieved by user or by target model object:

Code Block
languagejava
class Permission {
  String guid; // naturalsynthetic key makes create/add/update ambiguousAPIs easier
  String userIdappId;
  String roleuserId;
  String objectType;role; // "admin", "developer"
  String objectIdobjectType; // "study" or "organization"
  String objectId; // Object ID may need to be compound
  void setStudyObjectId(String appId, String studyId) {
    this.objectId = appId + ":" + studyId;
  }
}"appId:studyId" or "appId:groupId"
}

The service (which we’ll probably access through Spring Security, see below):

Code Block
interface PermissionsService {
  Set<Permission> getPermissionsForUser(String userId, boolean includeTransitive);
  Permission addPermission(Permission permission);
  void updatePermission(Permission permission);
  void removePermission(Permission permissions);
  Set<Permission> getPermissionsForObject(ObjectType type, String id);
}

There will be top-level APIs to change permissions:

Method

URL

Description

GET

/v1/permissions/{userId}

Get all permissions for a user.

GET

/v1/permissions/{objectType}/{objectId}

Get all permissions for an object like organization, study, or app.

POST

/v1/permissions

Create a permission for a specific object and user. Caller must be an admin for the object. Returns the object with a GUID.

POST

/v1/permissions/{guid}

Update a permission (caller must be an admin for the object).

DELETE

/v1/permissions/{guid}

Remove a permission for an object (caller must be an admin for the object).

Using Spring Security

We’re reimplementing a lot of the functionality of Spring Security’s authorization support. It might be desirable to switch over rather than further implementing a custom solution. We need a table of permissions that can be used to answer the framework’s authorization questions.

Using Spring security for authorization (not authentication, at least initially) we would do the following:

  1. In a filter, create a caller's Authentication object and put it in Spring Security's SecurityContext (exactly like what we've been doing with our own RequestContext, but we'd probably store the caller's permissions);

  2. Add authorization annotations to all of our controller methods.
    Spring has many choices, including annotations that will take expression languages and security check methods that we can write ourselves. I like that option a lot. So we can basically do our security checks in these annotations, e.g. @PreAuthorize("permit('developer', #studyId)") - permit a developer for the study ID (taken from the method parameters) to access the controller method. But because we can implement that "permit" method, we could also allow app developers, admins, and superadmins to pass this test, as we do now.
    Another approach is to create custom annotations that bundle complicated expression rules (e.g. @IsDeveloper could do all of the above).

  3. Remove our own static method call checks in AuthUtils. Eventually consider if we can remove RequestContext since it is 99% of the time being used to do authorization checks.

There will be top-level APIs to change permissions:

...

Method

...

URL

...

Description

...

GET

...

/v1/permissions/{userId}

...

Get all permissions for a user.

...

GET

...

/v1/permissions/{objectType}/{objectId}

...

Get all permissions for an object like organization, study, or app.

...

POST

...

/v1/permissions

...

Create a permission for a specific object and user. Caller must be an admin for the object. Returns the object with a GUID.

...

POST

...

/v1/permissions/{guid}

...

Update a permission (caller must be an admin for the object).

...

DELETE

...

/v1/permissions/{guid}

...

  1. .

Migration

Existing roles can be expressed in the new permissions table in order to make the same kind of authorization checks. This can be done independently of allowing users to be in multiple organizations. For every administrative account in the system, we’d want to create entries based on their current roles:

...