...
This is the most complex option available. Because we didn’t start with it, it would require a lot of implementation work to override Spring’s defaults for authentication, but then authorization could be done with annotations on our service methods, but we could use the method-based security via annotations. Our service methods could declare the security using complex expression rules. However, the time involved makes me shy away from this option.
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)
Cons
Our existing authentication and authorization have all been written from scratch, which means we’d need to overwrite a number of Spring Security classes before any of this would work;
The result would take time and involve a lot of code (and a solid understanding of Spring Security);
Security exceptions would appear in places where we are now able to return InvalidEntityExceptions and the like. Possibly confusing to existing users.
Implement a DSL
Nothing Spring does is that hard to duplicate with a DSL that uses our existing RequestContext. It could look something like this:
...
If we do this early in the method, like Spring, empty/null/imaginary values are going to throw security exceptions, which might be confusing (but also requires less work on our part). For example if the user submits a made up orgId and the call requires the user be in that organization, they’ll get a security error not a validation error. (I’m fine with this).
More custom code for new developers to learn (assuming they know Spring security down to how it is implemented)
Still only in code and does not move permissions to a database.
Recommit to implementing authorization in the controllers
Arguably we can continue to use authorization in the controllers if we just create more endpoints that are tailored to our specific needs. In exactly the same way our worker APIs all start by declaring a study (/v3/studies/{studyId}/*), we could create endpoints for organization administrators (logically under the /v1/organizations/* namespace), etc. Then role-based and entity-based checks could be done in the controller methods.
Take for example the AuthUtils.checkSelfStudyResearcherOrAdmin check for enrollments:
Code Block |
---|
// self
POST /v5/studies/enrollments/self
// study researcher or admin
POST /v3/studies/enrollments |
Pros:
Consistent with what we’ve done and thus the least code churn;
Possibly easier for external users to understand;
Possibly easier to secure down the road with Spring Security (using URL-based security rather than method-based security);
Cons
I was going to say “more code” but after looking at the other options, it might still be the best way to proceed;
Some security checks might get tedious. The delete user is a good example:
Code Block language java boolean orgAdminDelete = context.isInRole(ORG_ADMIN) && !account.getRoles().isEmpty() && context.getCallerOrgMembership().equals(account.getOrgMembership()); boolean testDelete = AuthUtils.isSelfResearcherOrAdmin(context.getCallerUserId()) && account.getDataGroups().contains(TEST_USER_GROUP); boolean adminDelete = context.isInRole(ADMIN); if (!orgAdminDelete && !testDelete && !adminDelete) { throw new UnauthorizedException(); }