Separate participant and administrative account management
Here are the current dependencies between our accounts, authorization, and consent classes:
This makes it difficult to talk about changes to the management of accounts, even for something like authorization. Here is one aspirational model where we separate Synapse-managed accounts and participant accounts, so that all the business logic around participants is separated from our code to manage admin accounts:
There would be backwards-incompatible consequences to this refactor. The /v3/participants APIs could no longer be used to create and manage administrative accounts. I don’t know who uses these for that purpose, at least the Bridge Study Manage would need to revise the UI it has to list all admins/users in one giant list (currently under the legacy panel). That API is still useful, but it would only return true participant accounts.
Notes on refactoring…
General
Explicitly mark administrative accounts before we migrate roles, because right now that's the way we determine these kind of accounts.
AccountDao & AccountSecretDao
Basically fine as is. AccountDao.getAppIdForUser is weird. AccountDao.getPagedExternalIds is weird but probably legacy.
ParticipantVersionService
Should monitor changes to the Accounts table that are done through ParticipantService, not AccountService
ConsentService, EnrollmentService
AccountService -> AccountDao
AccountService & ParticipantService
Could use an abstract parent. e.g. do you call "getAccountId" on account service or participant service? Maybe both is OK. Deleting an account: lots of common code between deleteAccount() and deleteParticipant.
AccountService
move authenticate, changePassword, reauthenticate, deleteReauthToken, verifyChannel to the AuthenticationService.
ActivityEventService: should be used in ParticipantService
StudyActivityEventService: should be used in ParticipantService
ParticipantVersionService: should be used in ParticipantVersionService
It does seem like simple methods to get the ID of an account or the like could continue to use AccountService.
ParticipantService
AccountService -> AccountDao
Any code related to roles and/or organizations can be removed to AccountService;
any attempt to manipulate an admin account through this service should fail fast;
updateIdentifiers: authenticate in the controller as part of the security check so you can take these auth methods out of AccountService and ParticipantService
Auth: AuthenticationService, AccountWorkflowService, OAuthProviderService
These are all related and could be combined, except that it would make an unwieldy service. Maybe we could have a front controller that delegates to a set of implementation classes so external classes only deal with an AuthenticationService, but we're not maintaining a monolith.
AuthenticationService should drop reference to AccountService and ParticipantService and work only with AccountDao and AccountSecretDao
AccountWorkflowService should drop reference to AccountService and use AccountDao.
UserAdminService
Fine as is (a crazy service to make integration testing palatable)
AccountController
add paginated API to retrieve admin accounts in the app (across all organizations or no organiations);
access to these methods would require app:admin or system:superadmin powers;
all methods should only operate on admin accounts;
UserManagementController
it may be simpler and easier to create enrollment records directly in the account for testing purposes. This would remove the consent service and the enrollment service;
the account service user delete should delete from all those other services, so we don't have to reference userAdminService to properly delete an account.
Expanded permissions model
We’re seeking a permissions model that will cover our current security capabilities while tackling new use cases, such as the ability to manage access to studies.
Use Cases
Use Case | |
---|---|
| (if cached they need to be separate from the session) |
New admin account created with a sandbox in which studies can be created/edited that are not visible to others | |
“Sandbox” can be converted to real study, with additional users in specific roles for that study | |
Study is extended by creating a new study | |
Add someone to a study’s administration team | |
Remove someone from a study’s administration team | |
Create similar authorization model for assessments | We should be able to expand it to other things than studies, because it seems likely we’ll encounter something else that needs finer-grained authorization. |
Secured objects/scopes
Organizations
“Teams” in Synapse impart an identical set of permissions to a project for a set of users. “Organizations” in Bridge are a scope for manipulating users, since our app is multi-tenanted. The roles related to organizations:
Role | Scope | |
---|---|---|
Administrator | Organization |
|
Member | Organization |
|
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.
Studies
Individuals can be given specific roles vis-a-vis a study.
Roles | Scope | |
---|---|---|
Developer | Study | Can edit the configuration of the study and its schedule. |
Researcher | Study | Can list, view, edit, delete, enroll and unenroll accounts in the study. |
PI_Agent | Study | Can move the study into a production stage, and can view the data being exported to a project in Synapse. User must be a validated Synapse user. |
Admin | Study | Can do anything related to a study or its participants, and they can change users associated to the study or their roles in that association. |
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
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.
class Permission { String guid; // natural key makes create/add/update ambiguous String userId; String role; String objectType; String objectId; // Object ID may need to be compound void setStudyObjectId(String appId, String studyId) { this.objectId = appId + ":" + studyId; } }
Using Spring security for authorization (not authentication, at least initially) we would do the following:
In a filter, create a caller's
Authentication
object and put it in Spring Security'sSecurityContext
(exactly like what we've been doing with our ownRequestContext
, but we'd probably store the caller's permissions);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).Remove our own static method call checks in
AuthUtils
. Eventually consider if we can removeRequestContext
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} | Remove a permission for an object (caller must be an admin for the object). |
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:
Old role | New role | Description |
---|---|---|
DEVELOPER | DEVELOPER, APP SCOPE | |
RESEARCHER | RESEARCHER, APP SCOPE | |
ADMIN | ADMIN, APP SCOPE | |
STUDY_DESIGNER | DEVELOPER, STUDY SCOPE | |
STUDY_COORDINATOR | RESEARCHER, STUDY SCOPE | |
PI_AGENT, STUDY SCOPE | Can move study into recruitment phase, can be made the administrator of data repository for study, must be verified Synapse user. | |
ORG_ADMIN | ADMIN, ORGANIZATION SCOPE | |
SUPERADMIN | ADMIN, SYSTEM SCOPE | |
WORKER | WORKER, SYSTEM SCOPE |
We’d need to update both representations of roles in both places (as part of accounts and part of permissions), move over to authorizing requests using the permissions table, and then remove the bridge code and finally, delete the AccountRoles table.
Multiple organization membership
Once we have a permissions table, we can implement accounts being in multiple organizations. The utility of this construct will be lessened (because people can be permitted to act directly against a study) but it may still be important for the future.