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