Anil Saldhana is the Lead Identity Management Architect at JBoss. He blogs at http://anil-identity.blogspot.com Anil has posted 16 posts at DZone. You can read more from them at their website. View Full User Profile

Security Features of JBoss AS 5.1.0 - Part 5 - Instance Based Authorization

07.10.2009
| 14613 views |
  • submit to reddit

Access control lists, or simply ACLs, have been used for a long time for instance­ based authorization. As opposed to the Java EE authorization model, in which roles are granted access to all instances of a bean, instance­ based authorization offers a more fine­ grained access control model, in which specific resource instances can have their own access control rules specified in terms of permissions.


In a nutshell, an ACL is a collection of <identityOrRole, permissions> entries that is associated to a specific resource instance. Each entry describes the permissions that have been assigned to the corresponding identity or role.

Something like:

resource­id: org.jboss.test.resource.TestResource122
guest: READ
registered_user: READ,UPDATE
manager: READ,UPDATE,DELETE

 

Applications that need to enforce instance­ based authorization first locate the ACL associated with the protected resource instance and then proceed to check whether the caller has the required permissions or not.

 

JBoss ACL
JBoss ACL is a framework that has been designed to provide instance­ based authorization
based on ACLs. It defines an API and a offers a simple, fast implementation that relies on
enum types to represent the permissions.

 

The API encompasses the following interfaces:

  • ACL – represents an access control list. Defines methods to manipulate the entries and to check whether an identity has a set of permissions or not.
  • ACLEntry – represents an entry in the ACL.
  • ACLPermission – represents a permission.
  • ACLPersistenceStrategy – defines methods to persist/retrieve ACLs.
  • ACLProvider – basically a facade to the entire ACL subsystem.
  • RoleBasedACLProvider – a provider that uses the roles associated with the identity when looking for permissions.


The standard implementation that ships with JBoss ACL has the following features:

  • ACL and ACLEntry have been implemented as JPA entities. A standard JPAPersistenceStrategy takes care of the persistence/retrieval of ACLs.
  • The basic CRUD permissions have been implemented as a Enum type. A composite permission type allows for the combination of the basic permissions.

 

Usage example
This section illustrates how to use JBoss ACL to manage the ACLs and to enforce instance­
based authorization managing ACLs.


To create a new ACL, one needs to specify the resource it is going to protect and the initial list
of entries. As most (if not all) ACLs need to be persisted, one can use the
ACLPersistenceStrategy to create and persist the new ACL in a single step:

// this represents the resource instance with id 123.
Resource resource = new TestResource(123);
// 'john' has READ and UPDATE permissions.
BitMaskPermission permission = new
CompositeACLPermission(BasicACLPermission.READ, BasicACLPermission.UPDATE);
ACLEntry entry1 = new ACLEntryImpl(permission, new SimpleIdentity("john"));
// 'anna' has only the READ permission.
permission = BasicACLPermission.READ;
ACLEntry entry2 = new ACLEntryImpl(permission, new SimpleIdentity("anna"));
List<ACLEntry> entries = new ArrayList<ACLEntry>();
entries.add(entry1);
entries.add(entry2);
// create and persist the ACL.
ACLPersistenceStrategy strategy = new JPAPersistenceStrategy();
strategy.createACL(resource, entries);

It is worth noting that the resource must implement the org.jboss.security.authorization.Resource  interface and it must either have a getId() method with a non void return value or one field annotated with
javax.persistence.Id annotation. The id must uniquely identify each resource instance.
The ACL interface defines methods for addition and removal of entries. The changes,
however, won't be persisted until the modified ACL is passed to the updateACL method of
ACLPersistenceStrategy:

// retrieve an ACL from the database.
ACL acl = strategy.getACL(resource);
// add a new entry to the ACL.
permission = BasicACLPermission.READ;
ACLEntry entry3 = new ACLEntryImpl(permission, new SimpleIdentity("victor"));
acl.addEntry(entry3);
// persist the modified ACL.
strategy.updateACL(acl);


      
The JPAPersistenceStrategy assumes that there is a JPA persistence unit named “ACL”.


This is an example of the persistence.xml file used in the test cases:

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="ACL" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>org.jboss.security.acl.ACLImpl</class>
<class>org.jboss.security.acl.ACLEntryImpl</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="hibernate.connection.url"
value="jdbc:hsqldb:mem:unit-testing-jpa"/>
<property name="hibernate.connection.driver_class"
value="org.hsqldb.jdbcDriver"/>
<property name="hibernate.dialect"
value="org.hibernate.dialect.HSQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.connection.username" value="sa"/>
<property name="hibernate.connection.password" value=""/>
</properties>
</persistence-unit>
</persistence>

 

Enforcing Instance­ Based security

In runtime, applications can verify if an identity has the required permissions to execute an
operation on a protected resource instance through the ACLProvider:

// create an instance of ACLProvider and initialize it with the persistence
strategy.
ACLProvider provider = new ACLProviderImpl();
provider.setPersistenceStrategy(strategy);
// verify if 'john' has the READ permission.
Identity identity = new SimpleIdentity("john");
boolean authorized = provider.isAccessGranted(resource, identity,
BasicACLPermission.READ);
// if john has read permission, return the resource.
if(authorized == true)
return resource;
else
return null;


       
The isAccessGranted method checks if the specified identity has the required permissions
to access the specified resource instance. It returns true if the identity has the permissions,
false otherwise.

JBoss Application Server 5.x

It is possible to use JBoss ACL to enforce instance ­based authorization within the JBoss
Application Server 5. The first step is to include an ACL configuration in a security domain, as
follows, in a xxx-jboss-beans.xml file (let us call it, aclpolicy-jboss-beans.xml)

<?xml version="1.0" encoding="UTF-8"?>

<deployment xmlns="urn:jboss:bean-deployer:2.0">

<!-- acl application-policy definition -->
<application-policy xmlns="urn:jboss:security-beans:1.0" name="acl-domain">
<authentication>
<login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="optional"/>
</authentication>
<acl>
<acl-module code="org.jboss.security.acl.ACLProviderImpl">
<module-option name="persistenceStrategy">org.jboss.security.plugins.acl.PolicyRegistrationStrategy</module-option>
</acl-module>
</acl>
</application-policy>

</deployment>

 
In the code, one first needs to get a reference to the AuthorizationManager. This
manager delegates instance­based access control decisions to the ACLProvider that has
been configured in the security domain. Here is how to do it:

 

// first retrieve the authorization manager for the acl-domain.
InitialContext ctx = new InitialContext();
AuthorizationManager manager = (AuthorizationManager)
ctx.lookup("java:jaas/acl-domain/authorizationMgr");

// now invoke the instance-based authorize method.
boolean allowed = manager.authorize(resource, new SimpleIdentity("john"),
BasicACLPermission.READ);

 

The AuthorizationManager interface defines several overloaded authorize methods.
The one shown above uses the configured ACLProvider to find out whether the specified
identity has the required permissions or not in order to access the resource instance.
Besides the authorize method, AuthorizationManager also defines a getEntitlements
method. This method takes a resource and an identity as parameters, and returns the
permissions that have been assigned to the identity regarding the specified resource and all
its sub­resources.

The default implementation returns a set of EntitlementEntry instances
wrapped in an instance of EntitlementHolder. Each entitlement entry contains a pair
<resource, permissions>, representing the permissions that have been assigned to the
identity for that resource. Here is an example:

EntitlementHolder<EntitlementEntry> holder =
manager.getEntitlements(EntitlementEntry.class, resource, johnIdentity);

// display the contents of each entry.
for (EntitlementEntry entry : holder.getEntitled())
{
System.out.println("Resource: " + entry.getResource() + ", Permissions:
" + entry.getPermission());
}


          
For the code above to work, one needs to specify the resource tree, that is, the sub­resources
of the specified resource. Each sub­resource may have its own sub­resources and so on. This
is done by adding a collection of resources to the resource options map under the key
ResourceKeys.CHILD_RESOURCES, as follows:

// create a resource that has resource2 as a child.
TestResource resource2 = new TestResource(2);
Collection<Resource> childResources = new ArrayList<Resource>();
childResources.add(resource2);
resource.getMap().put(ResourceKeys.CHILD_RESOURCES, childResources);

 

Role­based ACLs

All the previous examples associated permissions with the identity name. What if we want to
associate permissions with roles instead?
managing role­based ACLs
Role­based ACLs associate permissions with the application roles. This means that each
entry needs now to be created using the name of the role instead of the identity:

 

// create the ACLs for the resources.
ACLEntry entry1 = new ACLEntryImpl(BasicACLPermission.READ, "guest");
ACLEntry entry2 = new ACLEntryImpl(
new CompositeACLPermission(BasicACLPermission.READ,
BasicACLPermission.UPDATE), "registered_user");
List<ACLEntry> entries = new ArrayList<ACLEntry>();
entries.add(entry1);
entries.add(entry2);
this.strategy.createACL(resource, entries);

 

As we can see in the example, the ACL entries now represent a <role,permissions> pair.


Enforcement of Role­based ACL Permissions

When role­based ACLs are used to protect resources, the RoleBasedACLProvider must
be used to verify the permissions assigned to the roles. This provider's implementation of the
isAccessGranted method does the following:

  1. Gets the roles associated with the identity.
  2. If there are no roles, then use the default identity name (delegate to ACLProvider)
  3. If there are roles, then checks the ACL using each role. If any of the roles have the required permissions, then access to the resource is granted.

Some code to illustrate the role­based ACL enforcement:

// Identity 'john' has two roles.
Role role1 = RoleFactory.createRole("registered_user");
Role role2 = RoleFactory.createRole("system_admin");
RoleGroup roleGroup = RoleFactory.createRoleGroup("RoleGroup");
roleGroup.addRole(role1);
roleGroup.addRole(role2);
this.identity = IdentityFactory.createIdentityWithRole("john", roleGroup);
// create the RoleBasedACLProvider instance.
ACLProvider provider = new RoleBasedACLProviderImpl();
provider.setPersistenceStrategy(this.strategy);
// check if any of the roles has the UPDATE permission.
boolean granted = provider.isAccessGranted(resource, this.identity,
BasicACLPermission.UPDATE));
if(granted == true)
// update the resource.

When authorizing an identity, RoleBasedACLProvider uses the identity's roles to check if
any of the roles has the required permissions. If the identity doesn't have any role, then the
default identity name is used to look for permissions.

 

Instance Based Authorization usage in EJB/WEB Applications

As an example, we can create META-INF/jboss-acl-policy.xml or WEB-INF/jboss-acl-policy.xml

<jboss-acl xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:jboss:jboss-acl-config:1.0"
xmlns="urn:jboss:jboss-acl-config:1.0"
xmlns:jbxb="urn:jboss:jboss-acl-config:1.0">

<acl-definition resource="org.jboss.test.security.resources.TestResource:10">
<entry>
<identity-name>Administrator</identity-name>
<permissions>CREATE,READ,UPDATE</permissions>
</entry>
<entry>
<identity-name>Guest</identity-name>
<permissions>CREATE,READ,UPDATE</permissions>
</entry>
</acl-definition>

<!-- An extends attribute allows an ACL to inherit all the entries
from its parent ACL -->
<acl-definition resource="org.jboss.test.security.resources.TestResource:11" extends="org.jboss.test.security.resources.TestResource:10">
<entry>
<identity-name>Regular_User</identity-name>
<permissions>READ,UPDATE</permissions>
</entry>
<!-- This overrides the definition from the parent ACL -->
<entry>
<identity-name>Guest</identity-name>
<permissions>READ</permissions>
</entry>
</acl-definition>

</jboss-acl>

 

The ACLSessionImpl EJB code looks as follows:

package org.jboss.test.security.ejb;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.naming.InitialContext;

import org.jboss.security.AuthorizationManager;
import org.jboss.security.acl.EntitlementEntry;
import org.jboss.security.authorization.EntitlementHolder;
import org.jboss.security.authorization.Resource;
import org.jboss.security.authorization.ResourceKeys;
import org.jboss.security.identity.plugins.IdentityFactory;
import org.jboss.test.security.interfaces.ACLSession;
import org.jboss.test.security.resources.TestResource;

/**
* <p>
* Implementation of the {@code ACLSession} interface used in the ACL integration tests.
* </p>
*/
@Stateless
@Remote(ACLSession.class)
public class ACLSessionImpl implements ACLSession
{

/*
* (non-Javadoc)
*
* @see org.jboss.test.security.interfaces.ACLSession#getEntitlementsForIdentity(java.lang.String)
*/
public Map<Integer, String> getEntitlementsForIdentity(String identity)
{
Map<Integer, String> entitlementsMap = new HashMap<Integer, String>();

try
{
// first retrieve the authorization manager for the acl-domain.
InitialContext ctx = new InitialContext();
AuthorizationManager manager = (AuthorizationManager) ctx.lookup("java:jaas/acl-domain/authorizationMgr");

// create a resource 10 that has resource 11 as a child.
TestResource resource10 = new TestResource(10);
TestResource resource11 = new TestResource(11);
Collection<Resource> childResources = new ArrayList<Resource>();
childResources.add(resource11);
resource10.getMap().put(ResourceKeys.CHILD_RESOURCES, childResources);
resource11.getMap().put(ResourceKeys.PARENT_RESOURCE, resource10);

// now call the getEntitlements method using created resource and identity objects.
EntitlementHolder<EntitlementEntry> holder = manager.getEntitlements(EntitlementEntry.class, resource10,
IdentityFactory.createIdentity(identity));

// for each entitlement entry, put the resource id and associated permission in the map to be returned.
for (EntitlementEntry entry : holder.getEntitled())
{
TestResource resource = (TestResource) entry.getResource();
entitlementsMap.put(resource.getId(), entry.getPermission().toString());
}
}
catch (Exception e)
{
throw new RuntimeException("Failed to obtain entitlements from authorization manager", e);
}
return entitlementsMap;
}
}

 

The test code can look as follows:

 
package org.jboss.test.security.test.authorization;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.rmi.PortableRemoteObject;

import junit.extensions.TestSetup;
import junit.framework.Test;
import junit.framework.TestSuite;

import org.apache.commons.httpclient.HttpMethodBase;
import org.jboss.test.JBossTestCase;
import org.jboss.test.JBossTestSetup;
import org.jboss.test.security.interfaces.ACLSession;
import org.jboss.test.util.web.HttpUtils;

/**
* <p>
* This {@code TestCase} tests the integration of the ACL layer with the application server. Modules define their ACL
* constraints in the {@code jboss-acl-policy.xml} configuration file and then call the {@code AuthorizationManager} at
* runtime to enforce their ACL policies.
* </p>
*/
public class ACLIntegrationUnitTestCase extends JBossTestCase
{

/**
* <p>
* Creates an instance of {@code ACLIntegrationUnitTestCase} with the specified name.
* </p>
*
* @param name a {@code String} representing the name of the {@code TestCase}.
*/
public ACLIntegrationUnitTestCase(String name)
{
super(name);
}

/**
* <p>
* Tests the results of calling {@code AuthorizationManager#getEntitlements}
from within a web component (a servlet).
* </p>
*
* @throws Exception if an error occurs while running the test.
*/
public void testGetEntitlementsFromServlet() throws Exception
{
// call the ACLServlet using the identity "Administrator" as a parameter.
URL url = new URL(HttpUtils.getBaseURL() + "acl-integration/acl?identity=Administrator");
HttpMethodBase response = HttpUtils.accessURL(url, "JBoss ACL Test", HttpURLConnection.HTTP_OK);
// each line of the response has the following format: resource_id:permissions
List<String> entitlements = this.readEntitlementsFromResponse(response);
assertEquals("ACLServlet retrieved an invalid number of entitlement entries", 2, entitlements.size());
// Administrator should have CREATE,READ,UPDATE and DELETE permissions on both resources (id=1 and id=2).
assertTrue("Invalid entitlement entry found", entitlements.contains("1:CREATE,READ,UPDATE,DELETE"));
assertTrue("Invalid entitlement entry found", entitlements.contains("2:CREATE,READ,UPDATE,DELETE"));

// now repeat the process, this time using the identity "Guest".
url = new URL(HttpUtils.getBaseURL() + "acl-integration/acl?identity=Guest");
response = HttpUtils.accessURL(url, "JBoss ACL Test", HttpURLConnection.HTTP_OK);
entitlements = this.readEntitlementsFromResponse(response);
assertEquals("ACLServlet retrieved an invalid number of entitlement entries", 2, entitlements.size());
// Guest should have READ permission on resource 1 and READ,UPDATE permissions on resource 2.
assertTrue("Invalid entitlement entry found", entitlements.contains("1:READ"));
assertTrue("Invalid entitlement entry found", entitlements.contains("2:READ,UPDATE"));
}

/**
* <p>
* Tests the results of calling {@code AuthorizationManager#getEntitlements} from within an EJB3 component.
* </p>
*
* @throws Exception
*/
public void testGetEntitlementsFromEJB() throws Exception
{
// lookup the test session.
Object obj = getInitialContext().lookup("ACLSessionImpl/remote");
ACLSession session = (ACLSession) PortableRemoteObject.narrow(obj, ACLSession.class);

// get the entitlements for the Administrator identity.
Map<Integer, String> entitlementsMap = session.getEntitlementsForIdentity("Administrator");
assertEquals("ACLSession retrieved an invalid number of entitlement entries", 2, entitlementsMap.size());
// Administrator should have CREATE,READ and UPDATE permissions on both resources (id=10 and id=11).
assertEquals("Invalid entitlement entry found", "CREATE,READ,UPDATE", entitlementsMap.get(10));
assertEquals("Invalid entitlement entry found", "CREATE,READ,UPDATE", entitlementsMap.get(11));

// now repeat the process, this time using the identity "Guest".
entitlementsMap = session.getEntitlementsForIdentity("Guest");
assertEquals("ACLSession retrieved an invalid number of entitlement entries", 2, entitlementsMap.size());
// Guest should have CREATE, READ and UPDATE permissions on resource 10 and READ permission on resource 11.
assertEquals("Invalid entitlement entry found", "CREATE,READ,UPDATE", entitlementsMap.get(10));
assertEquals("Invalid entitlement entry found", "READ", entitlementsMap.get(11));
}

/**
* <p>
* Reads the response contents and create a {@code List<String>} where each
component corresponds to one line of the
* response body.
* </p>
*
* @param response the {@code HttpServletResponse} that contains the
response from the {@code ACLServlet}.
* @return a {@code List<String>}, where each element corresponds to
one line of the response body.
* @throws Exception
*/
private List<String> readEntitlementsFromResponse(HttpMethodBase response) throws Exception
{
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getResponseBodyAsStream()));
List<String> entitlements = new ArrayList<String>();
String line = reader.readLine();
while (line != null)
{
entitlements.add(line);
line = reader.readLine();
}
return entitlements;
}

public static Test suite() throws Exception
{
TestSuite suite = new TestSuite();
suite.addTest(new TestSuite(ACLIntegrationUnitTestCase.class));

TestSetup wrapper = new JBossTestSetup(suite)
{
/*
* (non-Javadoc)
*
* @see org.jboss.test.JBossTestSetup#setUp()
*/
@Override
protected void setUp() throws Exception
{
super.setUp();
// deploy the application policy that specifies an ACL module.
String url = getResourceURL("security/authorization/aclpolicy-jboss-beans.xml");
deploy(url);
// deploy the web application that calls the ACL module.
deploy("acl-integration.war");
// deploy the ejb application that calls the ACL module.
deploy("acl-integration.jar");
}

/*
* (non-Javadoc)
*
* @see org.jboss.test.JBossTestSetup#tearDown()
*/
@Override
protected void tearDown() throws Exception
{
// undeploy the test ejb application.
undeploy("acl-integration.jar");
// undeploy the test web application.
undeploy("acl-integration.war");
// undeploy the application policy.
String url = getResourceURL("security/authorization/aclpolicy-jboss-beans.xml");
undeploy(url);
super.tearDown();
}
};
return wrapper;
}
}

 

About the Authors:

Anil Saldhana is the lead security architect at JBoss. He blogs at http://anil-identity.blogspot.com

Stefan Guilhen is a member of the JBoss Security team.

Legacy
Published at DZone with permission of its author, Anil Saldhana.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)