...
This is getting messy. 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.
Authorization Rules
Object | Assoc(1) | Role | Permissions(2) |
---|---|---|---|
AccountSummary | global (but filtered) | researcher | read |
Study (“participant”) | researcher | read | |
Organization (“member”) | org admin | read | |
App | all | read | |
dev, admin | update | ||
superadmin | create, delete | ||
AppConfig | public | read (filtered) | |
dev | create, read, write, delete | ||
AppConfigElement | dev | create, read, write, delete | |
Assessment | Organization (“owner”) | dev | create, read, write |
admin | delete | ||
AssessmentConfig | public | read | |
Organization (“owner”) | dev | write | |
Enrollment(Detail) | Account (“self”) | any | create, read, delete |
Study | researcher | create, read, delete | |
Study | admin | create, read, delete | |
AssessmentResource | Organization (“owner”) | developer | create, read, delete |
admin | delete | ||
FileMetadata/Revision | developer | create, read, write | |
admin | delete | ||
HealthDataRecord(Ex3) | participant | create | |
worker | write | ||
MasterScheduleConfig | superadmin | create, read, write, delete | |
NotificationMessage | self, admin, researcher | create | |
NotificationRegistration | self, researcher, admin | read | |
NotificationTopic | developer | create, read, write | |
admin | delete | ||
Organization | any | read | |
Account (“membership”) | org_admin | write | |
admin | create, delete | ||
RecordExportStatusRequest | worker | write | |
ReportData | Study | any/public | read |
Study | dev, worker | create, delete | |
ReportIndex | Study | any | read |
Study | dev | create, write | |
RequestInfo | Account (“self”) | ||
RequestInfo | Study | researcher | read |
admin, worker | read | ||
SchedulePlan | developer, researcher, worker | read | |
developer | create, write | ||
admin | delete | ||
SmsTemplate | Account | worker | create |
Study | org_admin | create | |
Organization (“sponsors”) | org_admin | create, read, delete | |
StudyConsent | dev | create, read | |
StudyParticipant | Study (“enrolled”) | researcher | create, read, write |
worker | read | ||
Participant (“self”) | read, write | ||
admin | create | ||
Subpopulation | dev | create, read, write | |
researcher | read | ||
admin | delete | ||
Survey | dev, researcher, worker | read | |
dev | create, write | ||
admin | delete | ||
Tag | public | read | |
superadmin | create, delete | ||
Template/TemplateRevision | dev | create, read, write | |
admin | delete | ||
Upload | dev, admin, worker | read | |
UploadSchema | dev | create, read, write | |
admin | delete |
...
This is the most complex option available, but we could use the method-based security via annotations. Our service methods could declare the security using complex expression rules.
Modeling EnrollmentService, it would look like this (ignoring the work necessary to make it work):
Code Block | ||
---|---|---|
| ||
public class EnrollmentService { @Secure("principal.id == callerUserId or hasRole('ADMIN') or " + "(hasRole('RESEARCHER') and hasOrgSponsoredStudy(studyId))") public PagedResourceList<EnrollmentDetail> getEnrollmentsForStudy(...) { } @Secure("principal.id == callerUserId or hasRole('ADMIN') or " + "(hasRole('RESEARCHER') and hasOrgSponsoredStudy(studyId))") public List<EnrollmentDetail> getEnrollmentsForUser(...) { } @Secure("principal.id == callerUserId or hasRole('ADMIN') or " + "(hasRole('RESEARCHER') and hasOrgSponsoredStudy(studyId))") public Enrollment enroll(...) { } @Secure("principal.id == callerUserId or hasRole('ADMIN') or " + "(hasRole('RESEARCHER') and hasOrgSponsoredStudy(studyId))") public void updateEnrollment(...) { } @Secure("principal.id == callerUserId or hasRole('ADMIN') or " + "(hasRole('RESEARCHER') and hasOrgSponsoredStudy(studyId))") public Enrollment unenroll(...) { } } |
Pros
It’s a standardized thing so other developers should have an easier time working with it;
Ultimately, security would be expressed in annotations, which seems nice;
One step closer to implementing a major change to something like storing access rights in a database (so far we haven’t seen anything in Bridge that requires that level of authorization, but)
...