I suppose you can think of authorisation as ‘orthogonal’ code, which like
logging or analytics code, can pollute a business method with extra logic.
Annotations are a good way to take this functionality out of the body of the
method. It also makes code inspections easier - it is easy to spot if a method
is authorised rather than wading through code.
In your example, you only need to compare 2 simple properties, but if the logic
is more complicated this would need to be written and possibly refactored into
a method or class. In this way, the authorisation logic gets spread around the
application. Each separate piece of authorisation code must be developed,
tested, maintained etc.
If you need to know ‘What does User X have permission to do?’ then it’s hard
to determine this.
The benefit of encoding the permissions a persistent String format is that all
the permissions logic is in one place and can be examined using the Shiro
framework. Also non-Java programmers, Support staff etc can easily inspect a
user’s Permissions by looking up a few rows in the database.
E.g. in your example, the permission could be stored as
PROFILE:READ:id=$(user}.id
This is exactly the approach we use in our own code ( except we use '${self}’
to denote the Subject).
Richard
> On 7 Jul 2016, at 09:02, Richard Bradley <[email protected]> wrote:
>
> Could you explain what the benefit of this is over just implementing those
> restrictions in plain old Java code directly?
>
> Each of the use cases you have given could be translated into plain old Java
> code more or less 1-1 and the resulting code would be:
> 1. simpler
> 2. more maintainable
> 3. more accessible to newcomers (no new language needs to be learnt)
> 4. benefit from IDE support and static analysis support
> 5. likely more reliable (there are fewer moving parts; no AOP framework is
> needed etc.; no chance of bugs in the implementation of @RequiresAttributes
> or misunderstandings in its usage)
> 6. easier to test
>
> The only downside I can see is that it might be slightly harder to statically
> verify that a developer has remembered to address security (in the proposed
> version you might be able to write a unit tests that asserts that all
> relevant methods have a non-empty "@RequiresAttributes" annotation).
>
> Here are the examples from below rewritten in POJ:
>
> Assume the following helper code is imported in each case:
>
> public class AuthenticationHelper {
> public static void assertAuth(bool ok) {
> if (!ok) {
> throw new AuthenticationException("Insufficient permissions");
> }
> }
>
> public static User currentUser() {
> // create a domain-specific User class from Shiro's
> SecurityUtils.getSubject()
> }
> }
>
> Example of use case #1:
>
> public class ProfileEditor {
>
> // all users implicitly have permission to edit their own profile,
> // so don't need RequiresPermissions, but we do need to check
> // that the profile belongs to this user
> public void store(Profile userProfile) {
> assertAuth(currentUser().id == userProfile.userId);
>
> // TODO: store the changes
> }
>
> }
>
> Example of use case #2:
>
> public class Project {
> List<String> getMemberIds() {
> // TODO: return list of member ids in this project
> }
>
> // the user has role "project manager" with a "settings:edit" permission
> // BUT... it's only for this project, not for all projects!
> @RequiresPermissions("settings:edit")
> public void editSettings(String key, String newValue) {
> assertAuth(getMemberIds().contains(currentUser().id));
>
> // TODO: change the setting
> }
>
> }
>
> Example of use case #3:
>
> public class ClassifiedDocumentRepository {
>
> @RequiresPermissions("documents:store")
> public void storeDocument(@Map("document") Document doc) {
> assertAuth(currentUser().clearanceLevel >= document.clearanceLevel);
> // TODO: store the document
> }
>
> // applying access control on return value here with special variable
> $return
> @RequiresPermissions("documents:retrieve")
> public Document retrieveDocument(String documentId) {
> // TODO: find the document and its metadata
>
> Document document = ...
>
> assertAuth(currentUser().clearanceLevel >= document.clearanceLevel);
> return document;
> }
>
>
> }
>
> Best,
>
>
> Rich
>
>
>
> -----Original Message-----
> From: jbuhacoff [mailto:[email protected]]
> Sent: 07 July 2016 05:19
> To: [email protected]
> Subject: Attribute-based access control
>
> Hi, I've been using Shiro for a couple of years now in various projects with
> annotations and the wildcard permission syntax. Now I have a new requirement
> for ABAC, and I think it should be possible to add this capability to Shiro.
> I have a proposal below, and wanted to get some comments. If there's
> interest, I would develop it myself and contribute the code & documentation.
>
> Here are some use cases:
>
> 1. a "my profile" or "my settings" feature: user should be able to edit own
> things but not everything in the table.
>
> 2. project or organization based permissions: user should be able to read
> documents in a project while user is associated with the project or team,
> possibly combined with a general "can read documents" permission.
>
> 3. clearance level: user has a "can read documents" permission but this
> needs to be matched with the user's clearance level and the document's
> clearance level.
>
> I see attribute-based access control as being a nice complement and
> orthogonal to the permission-based access control. A given method could be
> annotated with either one or both together to achieve the right level of
> control.
>
> My proposal is to add a new annotation @RequiresAttributes and to let its
> value be a boolean expression to be evaluated. Of course the expression will
> need to be able to refer to the current user, the object in question, and
> maybe even other things like the time of day, the https client certificate,
> or whatever. Getting these bits of info into the context of the expression
> language would be a combination of three things: 1) the realm can set some
> context when the user is authenticated based on information available at that
> time (user attributes, connection attributes, etc.) , 2) there could be
> generic "attribute injectors" like for calendar/time, system properties, or
> other things that are neither user nor object in question;
> 3) a new @Map annotation that can be applied to method parameters in order
> to give them a specific name in the expression.
>
> Also there is a special case when we might want to apply attribute-based
> permissions on the return value of a method. For this we have another
> annotation @RequiresAttributesOnReturn which would be applied with around
> advice, calling proceed() and then checking permissions using the return
> value of the original method bound to a special variable $return.
>
> Both @RequiresAttributes and @RequiresAttributesOnReturn would have
> corresponding methods that could be called directly from anywhere in the code
> in order to perform the same evaluation without annotations.
>
> Special variable $this would be available to expressions annotated on
> instance methods.
>
> Variables and methods of objects would be accessible via dot notation, with
> automatic use of bean-style "getter" methods when they exist.
>
> In all the following examples, $user is a variable bound by the
> authentication realm to a User object with methods String getId() and Integer
> getClearanceLevel().
>
> Example of use case #1:
>
> public class ProfileEditor {
>
> // all users implicitly have permission to edit their own profile,
> // so don't need RequiresPermissions, but we do need to check
> // that the profile belongs to this user
> @RequiresAttributes("$user.id = $profile.userId")
> public void store(@Map("profile") Profile userProfile) {
> // TODO: store the changes
> }
>
> }
>
> Example of use case #2:
>
> public class Project {
> List<String> getMemberIds() {
> // TODO: return list of member ids in this project
> }
>
> // the user has role "project manager" with a "settings:edit" permission
> // BUT... it's only for this project, not for all projects!
> // also special variable $this refers to enclosing class instance
> @RequiresPermissions("settings:edit")
> @RequiresAttributes("$this.memberIds.contains($user.id)")
> public void editSettings(String key, String newValue) {
> // TODO: change the setting
> }
>
> }
>
> Example of use case #3:
>
> public class ClassifiedDocumentRepository {
>
> @RequiresPermissions("documents:store")
> @RequiresAttributes("$user.clearanceLevel >= $document.clearanceLevel")
> public void storeDocument(@Map("document") Document doc) {
> // TODO: store the document
> }
>
> // applying access control on return value here with special variable
> $return
> @RequiresPermissions("documents:retrieve")
> @RequiresAttributesOnReturn("$user.clearanceLevel >=
> $return.clearanceLevel")
> public Document retrieveDocument(String documentId) {
> // TODO: find the document and its metadata
> return document;
> }
>
>
> }
>
>
>
>
>
>
>
> --
> View this message in context:
> http://shiro-user.582556.n2.nabble.com/Attribute-based-access-control-tp7581093.html
> Sent from the Shiro User mailing list archive at Nabble.com.
>
> Richard Bradley
> Tel : 020 7485 7500 ext 3230 | Fax : 020 7485 7575
>
> softwire
> Sunday Times Best Small Companies - UK top 25 six years running
> Web : www.softwire.com<http://www.softwire.com/> | Follow us on Twitter :
> @SoftwireUK<https://twitter.com/SoftwireUK>
> Addr : 110 Highgate Studios, 53-79 Highgate Road, London NW5 1TL
> Softwire Technology Limited. Registered in England no. 3824658. Registered
> Office : Gallery Court, 28 Arcadia Avenue, Finchley, London. N3 2FG
>