[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