JBoss.orgCommunity Documentation
Table of Contents
The Permissions API is a set of extensible authorization features that provide capabilities for determining access privileges
for application resources. This chapter describes the ACL (Access Control List) features and the management of persistent
resource permissions via the PermissionManager
. It also explains how the PermissionResolver
SPI
can be used to in conjunction with a custom PermissionVoter
implementation, allowing you to plugin your own
custom authorization logic.
The Permission
interface is used in a number of places throughout the Permissions API, and defines the following
methods:
public interface Permission {
Object getResource();
Class<?> getResourceClass();
Serializable getResourceIdentifier();
IdentityType getAssignee();
String getOperation();
}
Each permission instance represents a specific resource permission, and contains three important pieces of state:
The assignee, which is the identity to which the permission is assigned.
The operation, which is a string value that represents the exact action that the assigned identity is allowed to perform.
Either a direct reference to the resource (if known), or a combination of a resource class and resource identifier. This value represents the resource to which the permission applies.
The primary method for accessing the Permissions API is via the Identity
bean, which provides the following two methods
for checking permissions for the currently authenticated user:
boolean hasPermission(Object resource, String operation);
boolean hasPermission(Class<?> resourceClass, Serializable identifier, String operation);
The first overloaded method may be used when you have a reference to the actual resource for which you wish to check privileges:
@Inject Identity identity;
public void deleteAccount(Account account) {
// Check the current user has permission to delete the account
if (identity.hasPermission(account, "DELETE")) {
// Logic to delete Account object goes here
} else {
throw new SecurityException("Insufficient privileges!");
}
}
The second overloaded method may be used when you don't have a reference to the resource object, but you have it's identifier value (for example the primary key value of an entity bean):
@Inject Identity identity;
public void deleteCustomer(Long customerId) {
// Check the current user has permission to delete the customer
if (identity.hasPermission(Customer.class, customerId, "DELETE")) {
// Logic to delete Customer object goes here
} else {
throw new SecurityException("Insufficient privileges!");
}
}
This method is generally used for performance reasons, for example when you don't necessarily wish to load a resource object because of a possibly expensive instantiation cost, or you wish to check whether there are suitable permissions assigned to the user before loading the resource.
An ACL (Access Control List) can be used to control which identities may invoke specific operations on application resources.
Underneath the covers, ACL security checks are handled by the PersistentPermissionResolver
, which reads the
ACL entries for each resource via the PermissionStore
, which is typically a wrapper around some form of persistent
storage such as database table.
ACL permissions are managed via the PermissionManager
. An instance of this bean can be obtained by first injecting the
PartitionManager
, then getting an instance to the PermissionManager
via the createPermissionManager()
method:
@Inject PartitionManager partitionManager;
public void managePermissions() {
PermissionManager permissionManager = partitionManager.createPermissionManager();
}
Once you have a reference to the PermissionManager
, you can use it to grant permissions:
public void allowRead(User user, Customer customer) {
permissionManager.grantPermission(user, customer, "READ");
}
The grantPermission()
method accepts three parameters:
void grantPermission(IdentityType assignee, Object resource, String operation);
The assignee is the identity to which you wish to grant the permission. The resource is the application resource for which the permission applies. The operation is a String value representing the action that the assignee may invoke in relation to the resource.
Resources may conceivably be any type of Object
so long as there exists a unique, serializable value that can be determined
or in some way calculated from the resource object, which uniquely identifies that resource from other resources of the same type. This
unique value is called the identifier, an example of which might be the primary key value of an entity bean. The
PermissionHandler
SPI (see section below) is responsible for generating identifier values for resource objects.
The revokePermission()
method is used to remove permissions. Like grantPermission()
, it also accepts three
parameters:
void revokePermission(IdentityType assignee, Object resource, String operation);
It is also possible to revoke all assigned permissions for a single resource via the clearPermissions()
method. This is useful
for example if you wish to delete the resource and don't wish to leave orphaned permissions:
void clearPermissions(Object resource);
There are also a number of overloaded methods available for querying permissions. These methods take an assortment of parameters depending on exactly which permissions you wish to find:
List<Permission> listPermissions(Object resource);
List<Permission> listPermissions(Class<?> resourceClass, Serializable identifier);
List<Permission> listPermissions(Object resource, String operation);
List<Permission> listPermissions(Class<?> resourceClass, Serializable identifier,
String operation);
Here's some examples:
// List all permissions for a known Product
Product p = lookupProduct("grapes");
List<Permission> permissions = permissionManager.listPermissions(p);
// List all permissions for a Product where we know the resource class
// and the identifier
List<Permission> permissions = permissionManager.listPermissions(
Product.class, "bananas");
// List all "DELETE" permissions that have been granted for a Product
Product p = lookupProduct("apples");
List<Permissions> permissions = permissionManager.listPermissions(p, "DELETE");
// List all "UPDATE" permissions for a Product where we know the
// resource class and the identifier
List<Permissions> permissions = permissionManager.listPermissions(
Product.class, "oranges", "UPDATE");
Every resource class for which you wish to support ACL permissions is required to have a corresponding
PermissionHandler
. This interface is primarily responsible for the generation of resource
identifier values, plus a couple of other utility methods (please refer to the API documentation for more
details):
public interface PermissionHandler {
boolean canHandle(Class<?> resourceClass);
Serializable getIdentifier(Object resource);
Class<?> unwrapResourceClass(Object resource);
Set<String> listClassOperations(Class<?> resourceClass);
Set<String> listInstanceOperations(Class<?> resourceClass);
}
There are two ways that a resource class can be associated with a PermissionHandler
- the first
way is by providing a @PermissionsHandledBy
annotation on the resource class itself:
import org.picketlink.idm.permission.annotations.PermissionsHandledBy;
@PermissionsHandledBy(CustomPermissionHandler.class)
public class MyResourceClass {
}
For the circumstances where it is not possible to annotate the resource class directly, the second way is to register a
custom PermissionHandler
instance for which the canHandle()
method returns true
for
the resource class:
public boolean canHandle(Class<?> resourceClass) {
return MyResourceClass.class.equals(resourceClass);
}
Registering a custom PermissionHandler
is very easy - simply include it in your application deployment as an
@ApplicationScoped
bean, and it will be registered automatically. Here's a complete example of a
PermissionHandler
that allows permissions to be assigned to arbitrary string values (this handler is actually
provided by PicketLink):
@ApplicationScoped
public class StringPermissionHandler implements PermissionHandler {
@Override
public boolean canHandle(Class<?> resourceClass) {
return String.class.equals(resourceClass);
}
@Override
public Serializable getIdentifier(Object resource) {
checkResourceValid(resource);
return (String) resource;
}
@Override
public Class<?> unwrapResourceClass(Object resource) {
checkResourceValid(resource);
return String.class;
}
private void checkResourceValid(Object resource) {
if (!(resource instanceof String)) {
throw new IllegalArgumentException("Resource [" + resource +
"] must be instance of String");
}
}
@Override
public Set<String> listAvailableOperations(Class<?> resourceClass) {
return Collections.emptySet();
}
}
For many resource types it makes sense to restrict the set of resource operations for which permissions might be assigned. For example,
an application might have an entity bean containing lookup values for countries. This Country
bean is likely to only require
the bare minimum in terms of data management and so you might like to restrict the available operations for it to the typical CREATE, READ,
UPDATE, DELETE
operations. To do this we use the @AllowedOperations
annotation - this annotation allows us to provide a
child array of @AllowedOperation
values that specify exactly which operation values that permissions can be assigned for:
import org.picketlink.idm.permission.annotations.AllowedOperation;
import org.picketlink.idm.permission.annotations.AllowedOperations;
@Entity
@AllowedOperations({
@AllowedOperation(value = "CREATE", mask = 1, classOperation = true),
@AllowedOperation(value = "READ", mask = 2),
@AllowedOperation(value = "UPDATE", mask = 4),
@AllowedOperation(value = "DELETE", mask = 8)
})
public class Country implements Serializable {
The optional mask
value can be used to specify a bitmask value to allow for more efficient storage of permission values. If the
mask values are set, the operation values for that object's permissions will be stored as a numerical value with the corresponding bit values
turned on. For example, if a single user was assigned permission for both the READ
and UPDATE
operations
for one of our Country
beans, then this operation value would be stored as 6 (READ
(2) + UPDATE
(4)).
The other optional value, classOperation
can be set to true
if the permission applies to the class itself, and
not an instance of a class. For example, you might wish to check that the current user has permission to actually create a new Country
bean. In this case, the permission check would look something like this:
@Inject Identity identity;
public void createCountry() {
if (!identity.hasPermission(Country.class, "CREATE")) {
throw new SecurityException(
"Current user has insufficient privileges for this operation.");
}
// snip
}
This functionality is provided by the ClassPermissionHandler
permission handler.