In adding Mobile Toolbox to Bridge, we’re encountering more complex authorization. We have roles like an organization administrator, who can create users who are members of their organization (but who cannot see any other kind of accounts, including study participants in their organization’s studies), and study designers, who can edit some of the things that a developer can edit…but only in the context of the studies they have access to.
Right now these checks are spread out:
Controllers usually check for roles and consent;
AuthUtils methods are called in the services to check the relationship of the caller to entities like studies and organizations.
...
The Solution
We implemented an AuthEvaluator class that can be used to describe static objects that will check our different authorization rules. (Sometimes we still need to do a verification on models returned from the system, which are not covered…e.g. “is this object you loaded associated to the study we just verified you have access to?”)
The AuthEvaluator rules can be called in the controllers, then it’s much easier to find the authorization rule for a given API endpoint when writing SDK documentation. The evaluator currently has an overly-broad rule that needs to be removed for more specific checks:
Jira Legacy | ||||||||
---|---|---|---|---|---|---|---|---|
|
Bridge Roles
ADMIN
- all authorization checks should succeed for ADMINs
. They should be excepted in the AuthEvaluator
, then they can be removed from getSession(…)
checks.
A SUPERADMIN
is an ADMIN
in all respects, but who has APIs to switch between apps without an account in each app (UserManagementController.signInForSuperAdmin
and AuthenticationController.changeApp
). Admin and Superadmin SDK clients can just define the APIs, like the cache or app creation APIs, that are only available to those roles, since all the other clients will work and succeed for these users.
This is not fully or consistently implemented, see:
Jira Legacy | ||||||||
---|---|---|---|---|---|---|---|---|
|
DEVELOPER
, RESEARCHER
- These divide the APIs between calls to configure an app and calls to return information about study participants (include PII). They are app-scoped, and too broad for apps that have multiple studies run by different researchers.
We might choose to allow these roles access to their study-scoped alternates (described below). They would then be supersets of those roles. See:
Jira Legacy | ||||||||
---|---|---|---|---|---|---|---|---|
|
STUDY COORDINATOR
- This is a study-scoped version of a researcher (they can only access studies that they have access to through their organization). This person may be able to do other things, like set up schedules for a study.
STUDY DEVELOPER
- This is a study-scoped version of a developer. It’s not clear yet if it will be needed (coordinators might be able to configure everything about a study and we aren’t going to let outside developers muck about with app configuration).
Jira Legacy | ||||||||
---|---|---|---|---|---|---|---|---|
|
ORGANIZATION ADMINISTRATOR
- This is an administrator of an organization, that can edit an organization, including creating accounts in the organization. They or their users can in turn create studies that are sponsored by, and accessible to, all organization members.
The Problem (original issues that led to this design)
In adding Mobile Toolbox to Bridge, we’re encountering more complex authorization. We have roles like an organization administrator, who can create users who are members of their organization (but who cannot see any other kind of accounts, including study participants in their organization’s studies), and study designers, who can edit some of the things that a developer can edit…but only in the context of the studies they have access to.
Right now these checks are spread out:
Controllers usually check for roles and consent;
AuthUtils methods are called in the services to check the relationship of the caller to entities like studies and organizations.
This is getting messy. The issues I feel I’m encountering
Security is defined in different places, there’s not one place to see what is and is not permissible;
Utility methods are difficult to compose into new requirements and despite my best attempts, the names of these things rename confusing at times;
Consequently it’s hard to say we don’t have lapses in the authorization checks that are occurring.
I think we could use a more robust alternative to implement this. But first, here are the authorization checks we have implemented or want to implement in the MTB timeframe (described in terms of access to objects in the REST API, rather than through the several endpoints that are needed to expose each object in the API itself, and skipping participant-facing APIs). Then as well look at alternatives for implementation, I’ll show what modeling enrollment would look like.
...
Code Block | ||
---|---|---|
| ||
public class EnrollmentService { private static final AuthEvaluator SELF_ADMIN_OR_STUDY_RESEARCHER = AuthUtils.canAccessStudy().inRole(RESEARCHER).or() .inAnyRole(ADMIN, SUPERADMIN).or() isSelf(); public PagedResourceList<EnrollmentDetail> getEnrollments(...) { SELF_ADMIN_OR_STUDY_RESEARCHER.checkAndThrow("studyId", studyId, "userId", userId); } public Enrollment enroll(...) { SELF_ADMIN_OR_STUDY_RESEARCHER.checkAndThrow("studyId", studyId, "userId", userId); } public void updateEnrollment(...) { SELF_ADMIN_OR_STUDY_RESEARCHER.checkAndThrow("studyId", studyId, "userId", userId); } public Enrollment unenroll(...) { SELF_ADMIN_OR_STUDY_RESEARCHER.checkAndThrow("studyId", studyId, "userId", userId); } } |
Pros:
Easier to implement and understand at this point, when compared with overriding Spring Security’s implementation classes
Arguably, easier to understand because it’ll only contain what it necessary for our application (as opposed to Spring which is always more complicated because it can handle anything, including future requirements).
...