...
Code Block | ||
---|---|---|
| ||
public class EnrollmentService { @Secure("principal.id == callerUserId or hasRole('ADMIN') or " + "(hasRole('RESEARCHER') and hasOrgSponsoredStudy(studyId))") public PagedResourceList<EnrollmentDetail> getEnrollmentsForStudygetEnrollments(...) { } @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(...) { } } |
...
Nothing Spring does is that hard to duplicate with a DSL that uses our existing RequestContext. I think it makes sense to call these in the services, where multiple endpoints might create multiple conditions under which a call is acceptable. It could look something like this:
Code Block | ||
---|---|---|
| ||
public class EnrollmentService { private static final AuthEvaluator IS_ORGSELF_ADMIN_OR_STUDY_RESEARCHER = AuthUtils.orgMembercanAccessStudy().inRole(ORG_ADMIN);RESEARCHER).or() .inAnyRole(ADMIN, SUPERADMIN).or() // If we pass arguments in when we check, these are threadsafe and composable private static final AuthEvaluator IS_ORGisSelf(); public PagedResourceList<EnrollmentDetail> getEnrollments(...) { IS_SELF_ADMIN_OR_ADMIN = IS_ORG_ADMIN.or().inAnyRole(ADMIN, SUPERADMINSTUDY_RESEARCHER.checkAndThrow("studyId", studyId); } public voidEnrollment mustBeAnOrgAdmin(String appId, String orgId) {enroll(...) { IS_ORGSELF_ADMIN_OR_STUDY_RESEARCHER.checkAndThrow("orgIdstudyId", orgIdstudyId); } // do something } public void updateEnrollment(...) { IS_SELF_ADMIN_OR_STUDY_RESEARCHER.checkAndThrow("studyId", studyId); } public voidEnrollment mustBeSomeKindOfAdmin(String appId, String orgId) {unenroll(...) { IS_ORGSELF_ADMIN_OR_STUDY_ADMINRESEARCHER.checkAndThrow("orgIdstudyId", orgIdstudyId); } // do something } |
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).
...
Take the example of AuthUtils.checkSelfStudyResearcherOrAdmin check for enrollments:
Code Block |
---|
public class EnrollmentService { @GetMapping("/v5/studies/{studyId}/enrollments") public PagedResourceList<EnrollmentDetail> getEnrollments(...) { } @GetMapping("/v3/participants/self POST /enrollments") public PagedResourceList<EnrollmentDetail> getSelfEnrollments(...) { } @PostMapping("/v5/studies/{studyId}/enrollments") public Enrollment enroll(...) { } @PostMapping("/v3/participants/self/enrollments") public Enrollment enrollSelf(...) { } @PostMapping("/v5/studies/{studyId}/enrollments/ study researcher or admin POST /v3/studies/enrollments{userId}") public void updateEnrollment(...) { } @DeleteMapping("/v5/studies/{studyId}/enrollments/{userId}") public Enrollment unenroll(...) { } @DeleteMapping("/v3/participants/self/enrollments/{userId}") public Enrollment unenrollSelf(...) { } } |
Another example are org administrators who can list, read, create, and delete administrative accounts in their organizations:
...