[seam-dev] Next-gen Security API

Shane Bryzak shane.bryzak at jboss.com
Mon Feb 18 02:30:04 EST 2008


I've been working on a bunch of changes to the Seam Security API in 
preparation for the next round of new features in our next major 
release.  Before I start committing any of this stuff I'd like to run 
through some of the changes first and get some feedback.  I believe that 
once these changes are implemented then Seam will have the most powerful 
set of security features in existence, even beating specialised security 
frameworks like Acegi.  It would probably be easiest to explain 
everything in list form, so here we go with a list of new features in no 
particular order:

1) Strongly typed annotation-based permission checks.

Based on Gavin's ideas back in October, the SecurityInterceptor will now 
perform permission checks for methods annotated like this:

  @Update(Customer.class)
  public void updateCustomer() { ... }
 
We can possibly deprecate the @Restrict annotation in favour of this, 
but the point is that the new annotations are typesafe (as opposed to 
@Restrict which is not typesafe) and easily extensible.  For example, 
say we want to define a new customer permission called "approve" - it 
would be as simple as creating a new annotation:

  @PermissionAction("approve")
  public @interface Approve {
     Class value();
  }
 
Which is then used like this:

  @Approve(Customer.class)
  public void approveCustomer() { ... }  
 
We will provide a standard set of annotations for CRUD permissions 
(although we already have a @Create so perhaps we need to think of some 
other name for it - maybe @Insert?) and developers can easily define 
their own.  
 
2) A more sensible permission API

Currently when we want to perform a permission check for an instance of 
a domain class, we write the permission check like this:

  Identity.instance().checkPermission("customer", "update", customerObject);
 
This doesn't really make a lot of sense.  The real target of our 
permission check is the customer instance (customerObject), not some 
arbitrary string.  While still supporting this API (although it will 
probably be deprecated) the new API deals more directly with the actual 
target of the permission, like so:

  Identity.instance().checkPermission(customerObject, "update");
 
We can probably even use our @Update annotation in place of the String 
param:

  Identity.instance().checkPermission(customerObject, Update.class);
 
The crux of this point is that instead of thinking of permissions as 
being a combination of [name:action], they should instead be thought of 
in terms of [target:action].  I.e. am *I* allowed to perform [action] on 
[target of the action].  The target of the action can still be defined 
in terms of an arbitrary string value, however it's scope has really 
been expanded to include things such as object instances, object 
classes, etc where previously in our rule-based security the permission 
name was _always_ a string.

Similarly, the s:hasPermission() EL function has been updated also.  In 
so many places I see it being used like this:

  s:hasPermission("customer", "insert", null)
 
The third param in most of the cases I see is specified as null.  In the 
actual API it is a varargs but we don't support vararg params in EL, so 
it is implemented as an Object param and therefore it is required.  The 
new EL function will look more like the checkPermission() method:

  s:hasPermission("customer", "insert")
 
  or for an instance-based permission check:
 
  s:hasPermission(selectedCustomer, "update")
 
So that we don't break backwards compatibility, we need to extend our EL 
implementation to support method overloading - this is not supported by 
the spec, although it is quite trivial to implement.  I'm still waiting 
to hear back from Norman about getting this done.

3) Pluggable permission resolvers

While Drools has been a great foundation for resolving logic-based 
security permissions, it is not a silver bullet and cannot easily deal 
with things such as ACL security or role->permission mappings defined at 
runtime.  I also see people overriding Identity.hasPermission() to 
provide their own permission logic (Christian even does this in the Seam 
wiki).  

The new API provides a mechanism for "plugging-in" permission resolvers, 
offering a much more elegant solution than overriding the Identity 
component.  This is how a permission resolver is defined:

  @Name("org.jboss.seam.security.ruleBasedPermissionResolver")
  @Scope(SESSION)
  @BypassInterceptors
  @Install(precedence=FRAMEWORK, 
classDependencies="org.drools.WorkingMemory")
  @Startup
  public class RuleBasedPermissionResolver implements PermissionResolver {

Seam will provide the following permission resolvers out of the box:

  RuleBasedPermissionResolver - replaces RuleBasedIdentity, and provides 
support for the same type of Drools rule-based permissions.
 
  AclPermissionResolver - provides ACL security, for permission checks 
against object instances.  This particular feature is in hot demand.  
The default implementation will use JPA to store object permissions in a 
database table, and will use some kind of nifty caching system to speed 
up permission checks.
 
  DynamicPermissionResolver - provides role->permission mapping 
permission checks.  Like AclPermissionResolver, the default 
implementation will store its permissions in a database table.  We will 
provide screens for the management of permissions and the roles/users 
that they are assigned to, similar to the identity management screens.
 
  GroovyPermissionResolver - this is more of an afterthought than 
anything, but it might be nice to allow people to define their 
permissions in Groovy.
 
Permission resolvers can also be mixed and matched, giving developers 
the flexibility to use one type of permission check for certain 
operations, and another type of permission check for others.  They can 
even be chained together, using a policy similar to a JAAS 
configuration, requiring that either one or all permission resolvers 
must allow a permission for it to be granted.

These resolver chains are named, and domain classes can be configured to 
use certain resolver chains for specific permission checks.  
Configuration can either be annotation-based, like this:

  @Permissions({
    @Permission(action = "update", resolverChain = "default"),
    @Permission(action = "delete", resolverChain = "other")})
  @Entity
  public class Customer implements Serializable {

or defined in components.xml, probably like this:

  <!-- Resolver chains defined here -->
 
  <security:resolver-chain name="default" require="any">
    <security:resolver>ruleBasedPermissionResolver</security:resolver>
  </security:resolver-chain>
 
  <security:resolver-chain name="other" require="any">
    <security:resolver>aclPermissionResolver</security:resolver>
    <security:resolver>customPermissionResolver</security:resolver>
  </security:resolver-chain>

  <!-- Permissions mapped to resolver chains here -->

  <security:permission-mapper default-resolver-chain="default">
    <security:permission target="com.acme.model.Customer" 
action="update" resolver-chain="other"/>
    <security:permission target="com.acme.model.Customer" 
action="delete" resolver-chain="default"/>
  </security:permission-mapper>
 
If no configuration is provided, then a default ResolverChain is created 
containing all PermissionResolvers that have been deployed in the Seam 
application.  Permission checks are then resolved using this default 
ResolverChain, with the permission passing if any of the resolvers 
return true for their hasPermission() check.  This provides a sensible 
default security policy without requiring any configuration.
 
4) ACL Security

Expanding a little further on the last point in regards to ACL security, 
in a similar way it will also be configured via annotations:

  @AclFlags({
    @AclFlag(mask = 1, action = "read"),
    @AclFlag(mask = 2, action = "update"),
    @AclFlag(mask = 4, action = "delete"),
    @AclFlag(mask = 8, action = "approve"),
    @AclFlag(mask = 16, action = "foo")
  })
  @Entity
  public class Customer implements Serializable {
    
The @AclFlag values define a bit-mask, along with a permission action.  
The permissions are stored in a database table with the following structure:

  ACL_PERMISSION
  --------------
  OBJECT_ID
  IDENTIFIER
  ACCOUNT_ID
  FLAGS
 
OBJECT_ID   - used to identify the type of object, e.g. a customer.
IDENTIFIER  - identifies the instance of that object.  In the case of an 
entity, it would be the primary key
ACCOUNT_ID  - the granted permissions are assigned to this account/role.
FLAGS       - the actual permissions granted.  Based on the above 
example, a FLAGS value of 11 would mean the user has read, update and 
approve permissions for this object (read(1) + update(2) + approve(8)).

It is extremely easy to add new permission types, and if the flag is 
based on a 64-bit long, that gives a total of 64 distinct permissions 
allowable for each object type.

5) Authentication - SSO, LDAP

A number of outstanding JIRA issues will be included also - support for 
JBoss SSO, an LdapIdentityStore for connecting to LDAP with the Identity 
Management API, plus (hopefully) better container integration, at least 
with JBoss AS.

That just about covers it - I'd really like some feedback on points 2) 
and 3) if possible, followed by a lesser extent points 1) and 4).

Shane




More information about the seam-dev mailing list