Fine-grained Java EE authorization using Enum-based access control lists with EAz: Part 2: Understanding and using the EAz implementation

Java™ developers attempting to exert fine-grained control over access to application resources quickly reach the limits of built-in Java Platform, Enterprise Edition (Java EE) declarative authorization. Part 1 of this three-part series on Enum-based Authorization (EAz) described the basic architecture of a complete Access Control List (ACL) implementation in Java 5. Part 2 describes the usage and implementation of the EAz authorization solution, building on the example from Part 1. This content is part of the IBM WebSphere Developer Technical Journal.

Robert Patt-Corner (rpatt-corner@proqual.net), Chief Technical Officer, Proqual-IT

Robert Patt-Corner architects, designs, and develops enterprise applications for business areas including healthcare, document management, alerting and workflow services, and predictive information gathering. He is Chief Technical Officer at ProQual-IT, focusing on service-oriented architectures. Previously, he was Chief Technical Architect at Noblis (formerly Mitretek Healthcare), leading the team that developed an automated, Web-based healthcare alert service. Robert's certifications include WebSphere Administration, Rational Application Development, and Lotus Administration and Development. You can contact Robert at rpc@dorsetwest.com or rpatt-corner@proqual.net, and you can view his Web site at www.dorsetwest.com or www.proqual.net.



Keys Botzum, Senior Technical Staff Member , IBM India Software Lab Services and Solutions

Keys Botzum is a Senior Technical Staff Member with IBM Software Services for WebSphere. Mr. Botzum has over 10 years of experience in large scale distributed system design and additionally specializes in security. Mr. Botzum has worked with a variety of distributed technologies, including Sun RPC, DCE, CORBA, AFS, and DFS. Recently, he has been focusing on J2EE and related technologies. He holds a Masters degree in Computer Science from Stanford University and a B.S. in Applied Mathematics/Computer Science from Carnegie Mellon University. Mr. Botzum has published numerous papers on WebSphere and WebSphere security. Additional articles and presentations by Keys Botzum can be found at http://www.keysbotzum.com, as well as on IBM developerWorks WebSphere. He is also co-author of IBM WebSphere: Deployment and Advanced Configuration.



12 December 2007

Introduction

Enum-based Authorization (EAz, pronounced "easy") provides a way to implement a complete Access Control List (ACL) implementation in Java 5, including discrete binary permissions, arbitrary named sets of permissions, methods for applying and rapidly testing access control on protected resources, and a means to display the truth table of a particular access control using JavaServer™ Faces (JSF).

Part 1 of this series introduced the case for EAz by proposing:

  • The Java EE specification offers substantial declarative and programmatic authorization capabilities, but falls short in the areas of fine-grained authorization required by many applications.
  • There is no, as of this writing, any vendor framework to adequately extend Java EE in these areas. The Acegi framework comes closest, but completely replaces both Java EE authentication and authorization rather than extending authorization, and currently has some size limitations.
  • Java 5 Enums, originally implemented without particular reference to Java EE authorization, provide a fast bit-mapped implementation of enumerations that can easily be adapted to extend Java EE authorization and retain Java EE authentication.
  • EAz provides an easy-to-use and extensible implementation that extends Java EE authorization based on the Enum model using a classic ACL)approach to authorization.

Reviewing the sample scenario

By way of illustration, we will continue with the virtual campus scenario described in Part 1 to show how a complex authorization model can be implemented through EAz. As a refresher, let's review what has been established in this example so far.

The virtual campus consists of:

  • Multiple campuses, whose gates require authorization for entry.
  • Buildings on each campus with doors requiring entry authorization.
  • Private offices in the buildings.
  • Special secure spaces in the buildings.

EAz is applied to this example in order to:

  • Manage permissions and groups of permissions at a finer grain than Java EE roles, and in a way that can be easily and safely configured, understood, and maintained by an administrator.
  • Introduce a way to authorize resource access in a way that can be directly driven by the application's domain model -- specifically by means of authorization for campuses, buildings, rooms, and so on.
  • Scale to manage thousands of campuses, up to 100,000 named users and hundreds of permissions.
  • Coexist with and extend ordinary Java EE declarative and programmatic security so the existing authorization policies can continue to operate undisturbed, where necessary.

Figure 1 reiterates the virtual campus layout, Table 1 describes the groups and individuals taking part in the example, and Figure 2 shows the ACLs that you will implement in this article using the EAz framework.

Figure 1. Example virtual campus layout
Figure 1. Example virtual campus layout
Table 1. Groups and individuals for virtual campus example
IdentifierTypeGroups
Campus A usersGroup---
Campus B usersGroup---
Campus A Engineers (Engineers)Group---
Campus A Biologists (Biologists)Group---
Campus A Cleaning staffGroup---
Jane LinnaeusIndividualCampus A users, Biologists
Jim FermiIndividualCampus A users, Engineers
Stan the YetiIndividualCampus A users, Biologists
Ken LayIndividualCampus A users, Campus A Cleaning Staff
Danny DafoeIndividualCampus A users
Carla BonheurIndividualCampus B users

As in Part 1, assume the following access rules:

  • Only Campus A users can enter Campus A; only Campus B users can enter Campus B.

And within the Campus A users:

  • Biologists and Engineers can enter their own buildings and public spaces in it; in addition, Biologists can enter the Engineering building.
  • Cleaning staff for Campus A can enter any room except the labs.
  • Jim Fermi is allowed to enter his office in the Engineering Building, Private Office 2.
  • Jim Fermi and Stan the Yeti are the only ones allowed to enter the Engineering Building Secure Lab 3.
  • Jane Linnaeus is allowed to enter her Biology Building Private Office 5,
  • Only Stan the Yeti can enter the Biology Building Secure Lab 6.

The campuses, buildings, and rooms can be secured as shown in Figure 2.

Figure 2. ACL structure for virtual campus example
Figure 2. ACL structure for virtual campus example

(See Part 1 for additional details of the virtual campus example, the EAz concept of operations, and details of the major EAz architectural structures.)

Implementation and illustration

Included with this article is a complete EAz implementation and test of the virtual campus, which you can download in Eclipse project interchange format. This article will walk you through the implementation, starting with a look at EAz in practice, using a test-based approach to see a series of JUnit tests validating an EAz implementation of the virtual campus.

Once you've worked through the test cases, you'll get a look at the actual implementation classes in EAz, beginning with the EAz Permissions class (that defines an application's specific ACL authorization policy) and working up to the ACL and other EAz classes that are used in the JUnit test illustrations.

This article concludes with a brief discussion of the mock and utility classes used in EAz to abstract away the implementation details of specific Java EE implementations, and describes areas where mock classes can be replaced with actual implementations in a specific Java EE environment.


EAz in practice

TestVirtualCampus.java is a JUnit4 test case that illustrates and exercises the virtual campus's EAz implementation. The test case only deals with room entry, but you can extend it or develop parallel test cases to test additional virtual campus actions -- or your own implementation of any other EAz authorization scenario.

The bulk of code in the JUnit is in the JUnit Fixture (see Resources), the portion of the JUnit test that sets up the common structures for testing across all the tests. The fixture is implemented in the TestVirtualCampus.setUp() method, implemented as a JUnit @Before method, executed before all tests.

In this code:

  • Start by looking at the class members for TestVirtualCampus in Listing 1. As you see, there is a mock class for the ACLDao that retrieves specific authorizations; this mock DAO uses properties files so that you can test without needing to install a database. The properties files reside in the org.eaz.resources package, as indicated by the ACL_HOLDER_PROPERTIES_LOC.

  • Groups are not part of the Java EE specification, but are an important part of almost every actual Java EE implementation. This example uses groups, represented as strings, in EAz to represent named collections of users.

  • Your group name strings need to exactly match with the group names in your properties files. The ACL for the Engineering building, for example, is read from the org.eaz.resources.EngrBldg.properties file in Listing 2, and grants the PERM_ENTER permission to the groups for Engineers, Biologists, and the Building Cleaning staff. Simple relational tables will hold this information in production.

  • Your Principal object is also a mock; we've taken the approach of implementing a MockPrincipal that associates groups directly with the principal for convenient testing. In IBM® WebSphere® Application Server, groups are carried in relation to the WSSubject class and the registry (see Resources and Listing 8b).

  • The class members are concluded by defining the ACLs that provide authorization for each of the spaces you will test.

Listing 1. TestVirtualCampus
public class TestVirtualCampus {

	/* Define our data access object that will retrieve our ACL's.
	 * In our test the ACLDao is a mock that uses easily configured
	 * properties files to define ACL's; a production application will
	 * use a database to do the same
	 */
	ACLDao mockAclDao;
	private final static String ACL_HOLDER_PROPERTIES_LOC="org.eaz.resources.";
	
	/*
	 * Define our groups.  Groups are not Java EE constructs and are
	 * typically implemented as part of an environment-dependent
	 * registry, so consider this ours.
	 */
	public final String GROUP_CAMPUS_A_USERS="CAMPUS_A_USERS";
	public final String GROUP_CAMPUS_B_USERS="CAMPUS_B_USERS";
	public final String GROUP_CAMPUS_A_ENGRS="CAMPUS_A_ENGRS";
	public final String GROUP_CAMPUS_A_BIOLS="CAMPUS_A_BIOLS";
	public final String GROUP_CAMPUS_A_CLEANERS="CAMPUS_A_CLEANERS";
	
	/*
	 * Define our test's principal names and MockPrincipal entitites.  Our
	 * MockPrincipal class implements the java.security.Principal interface, and
	 * adds the ability to add and retrieve groups, which you'll typically find
	 * as part of an environment-specific registry.
	 */
	public final String PRINCIPAL_JANE="Jane Linnaeus";
	public final String PRINCIPAL_JIM="Jim Fermi";
	public final String PRINCIPAL_STAN="Stan the Yeti";
	public final String PRINCIPAL_KEN="Ken Lay";
	public final String PRINCIPAL_DANNY="Danny Dafoe";
	public final String PRINCIPAL_CARLA="Carla Bonheur";
	MockPrincipal jane, jim, stan, ken, danny, carla;
	
	/*
	 * Define our test ACL's, one for each space we'll probe for entry
	 */
	ACL campusA_ACL, engrBldgACL, engrBldgRoom2ACL, engrBldgSecureLab3ACL,
		biolBldgACL, biolBldgRoom5ACL, biolBldgSecureLab6ACL;
Listing 2. org.eaz.resources.EngrBldg.properties
# This properties style file represents the ACL for the engineering building ACLHolder
# Format is groupOrPrincipal=permission
CAMPUS_A_ENGRS=PERM_ENTER
CAMPUS_A_BIOLS=PERM_ENTER
CAMPUS_A_CLEANERS=PERM_ENTER

Listing 3 sets up the fixture (see Resources) of your test, using a @Before method that executes once before any of your JUnit tests:

  • You begin by instantiating your mockAclDao and ACLHolders. In EAz, the ACLHolder represents a part of the model that is secured by authorization. The virtual campus ACLHolders are doors to rooms or buildings. You instantiate each holder, linking its name to its property file location; as mentioned earlier, in production your ACLHolders and their ACLs would be stored in basic relational tables.

  • Once you have your room-oriented ACL holders defined, you retrieve each of their ACLs using the mockAclDao you created earlier. Complete the fixture by creating your mockPrincipals and associating them with their group memberships. In a production environment, this is typically the function of a registry implemented in LDAP, a database, or an operating system.

  • All the fixture's work is typically done in an application's initialization code. Your fixture is done, and you're ready to use EAz for authorization.

Listing 3. Setting up the test fixture
@Before
public void setUp() throws Exception {
	/*
	 * Instantiate our Mock DAO and ACLHolders.  You'd implement your
	 * production DAO using a classic or Object-Relational-Mapping-based DAO pattern.  
	 */
	mockAclDao=new ACLDao();
	
	/*
	 * ACLHolders represent parts of our Campus model that require authorization.
	 * Each is mapped to a resource file that defines its ACL.
	 */
	ACLHolder campusA = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"campusA");
	ACLHolder engrBldg = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"engrBldg");
	ACLHolder engrBldgRoom2 = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"engrBldgRoom2");
	ACLHolder engrBldgSecureLab3 = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"engrBldgSecureLab3");
	ACLHolder biolBldg = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"biolBldg");
	ACLHolder biolBldgRoom5 = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"biolBldgRoom5");
	ACLHolder biolBldgSecureLab6 = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"biolBldgSecureLab6");
	
	// Retrieve each ACL from the properties file persisting it
	campusA_ACL=mockAclDao.retrieveACL(campusA);
	engrBldgACL = mockAclDao.retrieveACL(engrBldg);
	engrBldgRoom2ACL=mockAclDao.retrieveACL(engrBldgRoom2);
	engrBldgSecureLab3ACL=mockAclDao.retrieveACL(engrBldgSecureLab3);
	biolBldgACL = mockAclDao.retrieveACL(biolBldg);
	biolBldgRoom5ACL=mockAclDao.retrieveACL(biolBldgRoom5);
	biolBldgSecureLab6ACL=mockAclDao.retrieveACL(biolBldgSecureLab6);
	
		
	/* 
	 * Set up our principals and their groups.  This code emulates a registry	
	*/
	
	// Jane is a Campus A Biologist
	jane = new MockPrincipal(PRINCIPAL_JANE);
	jane.addGroup(GROUP_CAMPUS_A_USERS);
	jane.addGroup(GROUP_CAMPUS_A_BIOLS);

	// Jim is a Campus A Engineer
	jim = new MockPrincipal(PRINCIPAL_JIM);
	jim.addGroup(GROUP_CAMPUS_A_USERS);
	jim.addGroup(GROUP_CAMPUS_A_ENGRS);
		
	// Stan is a Campus A Biologist
	stan = new MockPrincipal(PRINCIPAL_STAN);
	stan.addGroup(GROUP_CAMPUS_A_USERS);
	stan.addGroup(GROUP_CAMPUS_A_BIOLS);
		
	// Ken is a Campus A Building Cleaner
	ken = new MockPrincipal(PRINCIPAL_KEN);
	ken.addGroup(GROUP_CAMPUS_A_USERS);
	ken.addGroup(GROUP_CAMPUS_A_CLEANERS);
		
	// Danny is a generic Campus A user
	danny = new MockPrincipal(PRINCIPAL_DANNY);
	danny.addGroup(GROUP_CAMPUS_A_USERS);
	
	// Carla is a Campus B visiting professor
	carla = new MockPrincipal(PRINCIPAL_CARLA);
	carla.addGroup(GROUP_CAMPUS_B_USERS);
	}

Listing 4 shows the two common test methods you will use in each of your individual tests. The essence of EAz authorization is the invocation of the permit() method on the ACL. The permit() method has a variety of overloaded forms (as we'll discuss when we look at the ACL class). All the forms involve some kind of required permissions set and some type of identity; the form in these tests uses a MockPrincipal object for identity. The essence of any EAz ACL test -- in fact of any ACL test -- is the question: Does identity X have specified permissions Y in relation to entity Z?

Both the assertPermitEntry and the assertDenyEntry methods use the EAz ACL to ask this question:

  • assertPermitEntry takes a list of MockPrincipal identities and asserts that all have the reqdPerms permissions.
  • assertDenyEntry asserts the converse: that all of the identities in the list lack the permissions in reqdPermissions.
Listing 4. Common test methods
private void assertPermitEntry(ACL aclToTest, PermissionSet reqdPerms, 
List<MockPrincipal> testCandidates) {
	try {
		for (MockPrincipal candidate: testCandidates){
			aclToTest.permit(reqdPerms, candidate);
		}
	} catch (Exception e) {
		fail("Should succeed");
	}
}
	
private void assertDenyEntry(ACL aclToTest, PermissionSet reqdPerms, 
List<MockPrincipal> testCandidates) {
	for (MockPrincipal candidate: testCandidates){
		try {
			aclToTest.permit(reqdPerms, candidate);
			fail("Should not succeed");
		} catch (Exception e) {
			// We expect an exception here
		}
	}
}

Let's put the methods to work. Listing 5 applies the permissions to the two campus gates, then to the Engineering building door:

  • Use the Permissions.getPermissionSetForAction() static method to relate the desired action to the PermissionSet that contains the permissions needed to authorize the action.
  • Specify which ACL to test.
  • Create a list of MockPrincipal candidates you expect to be authorized, and a list of MockPrincipal candidates you expect to be denied.
  • Call the assertPermitEntry() and then the assertDenyEntry() methods with your lists.

All the test methods in TestVirtualCampus follow this form. Figure 3 shows the successful test results.

Listing 5. Applying permissions
/**
 * Test Campus A's front gate.  We expect everyone to get in but Carla
 */
@Test public void testFrontGate(){
	PermissionSet reqdPerms = Permissions
		.getPermissionSetForAction(Permissions.AllActions.ACTION_ENTER);
	ACL aclToTest=campusA_ACL;
	MockPrincipal[] expectedPermitCandidates={jane, jim, stan, ken, danny};
	MockPrincipal[] expectedDenyCandidates={carla};
	assertPermitEntry(aclToTest, reqdPerms, Arrays.asList(expectedPermitCandidates));
	assertDenyEntry(aclToTest, reqdPerms, Arrays.asList(expectedDenyCandidates));
}

/**
 * Test the Engineering building door
 * Rules are: Engineers, Biologist and Cleaning Staff can Enter
 * 			  Generic Campus A staff cannot enter
 */
@Test public void testEngrBldgDoor(){
	PermissionSet reqdPerms = Permissions
		.getPermissionSetForAction(Permissions.AllActions.ACTION_ENTER);
	ACL aclToTest=engrBldgACL;
	MockPrincipal[] expectedPermitCandidates={jane, jim, stan, ken};
	MockPrincipal[] expectedDenyCandidates={danny, carla};
	assertPermitEntry(aclToTest, reqdPerms, Arrays.asList(expectedPermitCandidates));
	assertDenyEntry(aclToTest, reqdPerms, Arrays.asList(expectedDenyCandidates));
	}
Figure 3. Testing the virtual campus using EAz
Figure 3. Testing the virtual campus using EAz

Exploring the EAz implementation

Now that you've seen EAz in action, let's take a look at the details of the EAz implementation itself. We will begin with the Permissions class that holds the application-specific actions and permissions authorized by EAz, then move on to the ACL class that implements the critical permit() method, which is the core of EAz's authorization functionality.

Permissions class

EAz implements a Permissions class as a single standard location for an EAz application to declare its permissions and actions. Both permissions and actions are implemented in EAz as EnumSets. Permissions represent atomic binary permissions for an application and actions are the application's commands; that is, the activities that require authorization. In syntactic terms:

  • Actions are the verbs of an application.
  • Permissions are the qualifications needed to perform an action.
  • ACLs authorize or deny actions for the ACLHolders they protect.

Application developers must configure and maintain a single unique EAz permissions class for each application, since it represents the unique permissions of an application. Listing 6 shows the Permissions class for the virtual campus:

  1. Begin by declaring all the permissions in your application in the static AllPermissions enum. Our test exercises the PERM_ENTER permission; additional permissions are included to illustrate how AllPermissions needs to enumerate every permission that EAz requires to grant or deny authorization.

  2. After defining the permissions, go on to define the actions of the virtual campus. Additional actions have been defined over and above the ACTION_ENTER that is exercised in our JUnit to emphasize the fact that AllActions can and must contain every action definition secured by EAz in an application.

  3. Since both enums are static, their members can be referred to directly from anywhere in an application, for example as:

    AllActions.ACTION_ENTER
    or
    AllPermissions.PERM_GRANT_ENTRY

  4. The two static maps are the application structures that relate permissions to actions. They're private to the class and accessed only through public static methods getPermissionSetForAction() (used extensively to retrieve the required PermissionSet for an ACL to authorize against) and getSecuredActions() (a seldom-used but handy utility method returning all actions secured by EAz, used primarily in the GUI configuration displays, to be discussed in Part 3 of this series).

  5. The static initialization block of the class executes exactly once at application initialization, and performs the essential task of relating permissions and actions by configuring the static maps. You can configure the basic authorization structure of the application in this initializer by:

    1. Assembling a list of specific permissions required to perform an application action.
    2. Passing the action and the required permissions for authorization to the configureAuthzAction method.
  6. configureAuthzAction does the actual work of map configuration by creating a new PermissionSet to hold the required permissions, adding the permissions to the set, and configuring the static maps to hold the relationship.

In this way, a single static class declares the entire extended authorization policy of an EAz application.

Listing 6. Permissions class for the virtual campus application
public final class Permissions {

	/**
	 * The AllPermissions Enum enumerates all permissions available in the system.  
	 * Permissions are unary and individible and are not exposed beyond the 
	 * acl package
	 */
	protected static enum AllPermissions {
		PERM_ENTER,
		PERM_GRANT_ENTRY,
		PERM_REVOKE_ENTRY,
		PERM_LOCK_DOOR;
	}
	
	/**
	 * The AllActions Enum enumerates all actions in the system and provides the 
	 * set of keys for the available PermissionSets.  Since an action is the key 
	 * to a set of permissions it can represent one to many permissions.  
	 * Actions are exposed at the service layer
	 * 
	 * @author rpc
	 *
	 */
	public static enum AllActions {
		ACTION_ENTER,
		ACTION_MANAGE_ACCESS,
		ACTION_LOCK_DOOR;
	}
	
	/*
	 * These two maps hold the relationship between actions and
	 * permissions
	 */
	private static Map<AllActions, PermissionSet> NamedPermissionSets = 
		new EnumMap<AllActions, PermissionSet>(AllActions.class); 
	private static Map<PermissionSet, AllActions> PermissionSetToAction = 
		new HashMap<PermissionSet, AllActions>();

	/**
	 * GetPermissionSetForAction retrieves the PermissionSet 
	 * required to perform a specified action
	 * 
	 * @param psKey
	 * @return
	 */
	public static PermissionSet getPermissionSetForAction(AllActions psKey) {
		PermissionSet ps = NamedPermissionSets.get(psKey);
		if (ps ==null){
		  throw new RuntimeException ("No such permission as "+ psKey);
		}
		return ps;
	}
	
	public static Set<AllActions> getSecuredActions(){
		return NamedPermissionSets.keySet();
	}


	/**
	 * This static initializer performs the one-time work of relating permissions
	 * to actions. It delegates the configuration of each authorization action to 
	 * the configureAuthzAction method, passing in an Action enum to configure 
	 * and a list of permissions required to perform the action
	 */
	static {
		try {
			ArrayList<AllPermissions> reqdPerms;
			
			reqdPerms=new ArrayList<AllPermissions>();
			reqdPerms.add(AllPermissions.PERM_ENTER);
			configureAuthzAction(AllActions.ACTION_ENTER, reqdPerms);
			
			reqdPerms=new ArrayList<AllPermissions>();
			reqdPerms.add(AllPermissions.PERM_GRANT_ENTRY);
			reqdPerms.add(AllPermissions.PERM_REVOKE_ENTRY);	
			configureAuthzAction(AllActions.ACTION_MANAGE_ACCESS, reqdPerms);		

			reqdPerms=new ArrayList<AllPermissions>();
			reqdPerms.add(AllPermissions.PERM_LOCK_DOOR);
			configureAuthzAction(AllActions.ACTION_LOCK_DOOR, reqdPerms);
			
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}

	/**
	 * Performs the work of relating an AllActions enum to a PermissionSet
	 * of permissions required to perform it
	 * 
	 * @param action
	 * @param reqdPermissions
	 */
	private static void configureAuthzAction(AllActions action, 
List<AllPermissions> reqdPermissions) {
		PermissionSet ps= new PermissionSetImpl();;
		for (AllPermissions reqdPerm: reqdPermissions){
			ps.addPermission(reqdPerm);
		}
		NamedPermissionSets.put(action, ps);
		PermissionSetToAction.put(ps, action);
	}
	
}

ACL class

If the Permissions class embodies the authorization policy of an application, the ACL class implements the process of authorization. ACL requires some customization, but unlike Permissions, the customization is for a runtime environment rather than for a particular application. The ACL customization relates to the fact that different Java EE implementations carry out or extend Java security in different ways to deal with important artifacts and concepts outside the Java EE specification, such as groups.

Before diving into the ACL itself, let's briefly look at how an ACLDao constructs an ACL from a database or other persistant source. The snippet in Listing 7, from our mock ACLDao, shows the creation of a Group to PermissionSet association in an ACL, which determines the permissions available to a group on the ACLHolder. Here, a series of persisted individual permissions, retrieved as a set of string tokens in this case, is added to a PermissionSet. Once the PermissionSet is complete, the code invokes the ACL's addPermissionSet(groupName, permissionSet)method to add this association of a groupName and set of permissions to the ACL structure.

Listing 7. Detail from ACLDao, creating association of a groupName and PermissionSet in an ACL
/*
 * This loop takes each permission granted to a group,
 * adds it to a PermissionSet, then adds the group and
 * its associated PermissionSet to the ACL under
 * construction
 */	
while (tokenizer.hasMoreTokens()){
	permissionSet.addPermission(Permissions.AllPermissions
	.valueOf(tokenizer.nextToken()));
}
// Add the group and its granted permission set into the ACL
acl.addPermissionSet(groupName, permissionSet);

Our ACL class in Listing 8a features a public constructor accepting the securedResource, the ACLHolder that the ACL protects, and utility methods that operate on the ACL's internal grants structure. The ACL implements its grants structure as a map of PermissionCollections keyed by name. The grants map represents the collection of permissions available to each "name" in relation to the ACL's securedResource ACLHolder. Names must be unique in this implementation and can represent either principal names or the names of groups; the ACL doesn't distinguish between them.

You have already seen the addPermissionSet(groupName, permissionSet) method in action in the ACLDao; it creates the association in the grants structure from the ACL's persisted form in a database, or, in this demonstration implementation, a properties file. The other helper methods are getPermittedNames(),which returns the set of names in grants for use in a GUI display, and getPermissionSet(String name), which is the core access function for the grants structure during the execution of the core permit() method.

The ACL's permit() method is heavily overloaded for convenience; its main form is:

permit(PermissionSet reqdPerms, List<String> nameList) throws PermissionViolationException

and the other overloads extract a name list from their arguments and delegate it to the main form. Operation permit() iterates over the names it has been given, which can represent either principals or groups, and checks the permissions associated with each name in the grants map for the required permissions for the operation, returning silently on success. If options are exhausted, the method throws the PermissionViolationException. Be aware that this exception contains information about the permissions requested and the union of all permissions available for the request, mostly for logging purposes.

The two overloaded forms of permit() that use an explicit or implied subject are left as blank stubs for environment-specific implementations. These methods must be tailored to the a particular Java EE application server, and should extract unique forms of the principal(s) and available groups from the implied or explicit subject, then delegate to the main form of permit(). A WebSphere specific example is shown in the next section.

Listing 8a. EAz ACL class
/**
 * The ACL implements the process of authorization in EAz by means of permit() methods.
 */
public class ACL {
	
//	 An ACL instance secures its ACLHolder
	private ACLHolder securedResource; 
	
	/*
	 * Main internal ACL structure, mapping names to the set of permissions available
	 * to them on the ACLHolder.  Note that a name can represent
	 * either a group or a Principal
	 */
	private Map<String, PermissionSet> grants = new HashMap<String, 
	   PermissionSet>();

	/**
	 * Public constructor creating an ACL and associating it
	 * with the ACLHolder resource it secures
	 * 
	 * @param securedResource
	 */
	public ACL(ACLHolder securedResource){
		this.securedResource=securedResource;
	}
	
	/**
	 * Add assocations of names and their associated PermissionSet
	 * Could be extended to merge permissionSets
	 * 
	 * @param name
	 * @param permissionSet
	 */
	public void addPermissionSet (String name, PermissionSet permissionSet){
		grants.put(name, permissionSet);
	}

	
	
	/**
	 * This variant of permit is for implementation-independent testing of the EAZ 
	 * classes, for example in JUnit tests
	 * 
	 * @param mockPrincipal - Demonstration principal for testing, implements a
	 * 		   				  getGroups() method
	 * 		
	 * @param reqdPerms - PermissionSet of required permissions
	 * @throws PermissionViolationException
	 */
	public void permit(PermissionSet reqdPerms, MockPrincipal mockPrincipal ) throws 
	   PermissionViolationException {
		List<String> groups = new 
		ArrayList<String>(mockPrincipal.getGroups()); 
	// Create groups list from MockPrincipal groups set
		permit(reqdPerms, groups);
	}

	/**
	 * 
	 * @param reqdPerms
	 * @param singleName - convenience implementation for a single group or Principal 
	 * name
	 * @throws PermissionViolationException
	 */
	public void permit (PermissionSet reqdPerms, String singleName) throws 
	   PermissionViolationException {
		List<String> groupList = new ArrayList<String>();
		groupList.add(singleName);
		permit(reqdPerms, groupList);
	}
	
	/**
	 * @param PermissionSet reqdPerms
	 * @param List groupList
	 */
	public void permit(PermissionSet reqdPerms, List<String> nameList) throws 
	   PermissionViolationException {
		PermissionSet availablePerms=new PermissionSetImpl(), namePerms;
		for (String name: nameList){
			namePerms = grants.get(name);
			if (namePerms == null){
				continue;
			}
			availablePerms.addPermissions(namePerms.getPermissions());
			if (namePerms.hasPermissions(reqdPerms)){
				return;
			}
		}
		/* If we got here no name allowed permission, so throw the exception
		 * that indicates denied permission
		 */
		throw new PermissionViolationException (reqdPerms, availablePerms);
	}
	
	/**
	 * @param PermissionSetreqdPerms
	 */
	public void permit(PermissionSet reqdPerms) throws PermissionViolationException {
		// TODO fill in, making use of your implied implementation-dependent 
		// subject
	}

	/**
	 * @param PermissionSetreqdPerms
	 * @param ListgroupList
	 */
	public void permit(PermissionSet reqdPerms, Subject subject) throws 
	   PermissionViolationException {
		// TODO fill in, making use of an explicit implementation-dependent 
		// subject

	}
	
	/**
	 * @return the set of Groups mapped to permission sets in this ACL.  
	 */
	public Set<String> getPermittedNames() {
		return grants.keySet();
	}
	
	/**
	 * 
	 * @param name - the group to return a permission set for
	 * @return PermissionSet for the given group in the ACL or null
	 */
	public PermissionSet getPermissionSet(String name){
		return grants.get(name);
	}
	
	public ACLHolder getSecuredResource() {
		return securedResource;
	}

	public void setSecuredResource(ACLHolder theSecuredResource) {
		securedResource = theSecuredResource;
	}
	
}

Overloading the ACL class

The two ACL.permit() methods that use implicit or explicit subjects to perform authorization are stubbed out because the techniques for obtaining groups from a subject vary by Java EE implementation. Listing 8b illustrates how you can retrieve the needed group information in WebSphere Application Server V6.1 by subclassing the ACL and overriding the subject-oriented permit() methods in a WASACL class. (The WASACL class requires the sas.jar and wssec.jar libraries in the build and runtime paths; these two JAR files are not included in the download but are provided by all WebSphere Application Server V6.1 installations.)

The permit(PermissionSet reqdPerms) method uses the implicit subject of the current caller as a source of group information for authorization. A static call to WebSphere-specific WSSubject.getCallerSubject() obtains the caller subject. Once you have the subject of the caller in hand, the method simply delegates to permit(PermissionSet reqdPerms, Subject subject), which takes an explicit subject. You would use the implicit subject technique for the typical case of requesting authorization for the current user. The explicit subject method enables you to pass in the subject of any user and determine if they are authorized to perform an action.

The permit(PermissionSet reqdPerms, Subject subject) method uses its explicit subject argument to retrieve a WebSphere-specific public credential set. The size check ensures that there is only one entry in the set, as expected. If a particular application is designed to add additional credentials to the credential sets, then you will need to modify the code to select the correct WebSphere credential entry from the set in an application-specific way.

Once the WebSphere-specific credential set is in hand, you can retrieve the groups with groups = creds.getGroupIds(). (The groups will come back as strings in an LDAP-like format. You should write your own application-specific parsing code to transform the group strings into the form used in the application's ACL.grants structure so the available and specified groups will match.)

When you have the groups that the code delegates to, the core permit (reqdPerms, groups); method and authorization proceed as usual.

Listing 8b. WASACL class using WebSphere subject
/**
 * WASACL extends the ACL class for WebSphere-specific example implementations of the 
 * permit() method, using (6.1) WAS-specific implicit and explicit subjects as arguments
 */
package org.eaz.acl;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eaz.domain.ACLHolder;


/*
 * The imports below are only for the WebSphere specific example overloaded
 * permit() methods.  You'll need to place sas.jar and wssec.jar in the classpath
 * to use the imports and example code.
 */
import com.ibm.websphere.security.WSSecurityException;
import com.ibm.websphere.security.auth.CredentialDestroyedException;
import com.ibm.websphere.security.auth.WSSubject;
import com.ibm.websphere.security.cred.WSCredential;
import javax.security.auth.Subject;
import javax.security.auth.login.CredentialExpiredException;

/**
 * @author Robert Patt-Corner, Keys Botzum
 * The WASACL extends ACL with WebSphere-specific techniques for dealing with
 * implicit and explicit subjects in the permit() method
 */
public class WASACL extends ACL {

	public WASACL(ACLHolder securedResource) {
		super(securedResource);
	}

	/**
	 * @param PermissionSet reqdPerms
	 * This is an example implementation for WebSpere V6.1, illustrating how to use
	 * the implicit WebSphere Subject to perform authorization.  Must be run in a
	 * WAS container.
	 * 
	 * Introduces a dependency on WAS 6.1's wssec.jar and sas.jar
	 */
	public void permit(PermissionSet reqdPerms) throws PermissionViolationException {
		try {
			// Get the subject in a WebSphere specific manner, then delegate
			Subject subject = WSSubject.getCallerSubject();
			permit(reqdPerms, subject);
		} catch (WSSecurityException e) {
			throw new PermissionViolationException("Unable to retrieve 
				the WebSphere caller subject", e);
		}
	}

	/**
	 * @param PermissionSet reqdPerms
	 * @param Subject explicit subject
	 * This is an example implementation for WebSphere 6.1 ilustrating how to 
	 * approach using an explicit subject to perform authorization.  
	 */
	public void permit(PermissionSet reqdPerms, Subject subject) throws 
PermissionViolationException {
		subject.getPrincipals();
		
		String username=null;
		List<String> groups=null;
		
		// Get the public credentials (should be only one) from the subject of 
		// interest 
		Set credSet = subject.getPublicCredentials(WSCredential.class);
		   if (credSet.size() != 1) {
		    throw new PermissionViolationException("Found "
		      + credSet.size() + "WSCredentals, which is not legal");
		   }

		   Iterator iter = credSet.iterator();
		   WSCredential creds = (WSCredential) iter.next();
		   
		   // Get the unique security name corresponding to the Principal
			try {
				username=creds.getUniqueSecurityName();
				groups = creds.getGroupIds();

				/* At this point you'll want to insert application-
				 * specific code to transform both the username and then 
				 * each of the groups identifiers to the format your 
				 * application uses for group names in the grants structure
				 */ 
	        
				groups.add(username); // add the Principal name to the groups
			} catch (CredentialExpiredException e) {
				throw new PermissionViolationException("Found expired WS 
					credentials in ACL", e);
			} catch (CredentialDestroyedException e) {
				throw new PermissionViolationException("Found destroyed WS 
					credentials in ACL", e);
			}
        permit (reqdPerms, groups);
	}

}

Remaining classes of interest

  • PermissionSetImpl (Listing 9) is an implementation of the PermissionSet interface that enables the construction of a set of binary permissions from the AllPermissions enum. The main point of interest is the unusual mode of construction of the EnumSet, using the EnumSet.noneOf()class method; this is the Java 5 technique for obtaining an empty instance of an EnumSet containing a particular enum.

    Listing 9. PermissionSet implementation
    public class PermissionSetImpl implements PermissionSet{
    
    	private EnumSet<AllPermissions> permissions = 
    		EnumSet.noneOf(AllPermissions.class);
    
    	public void addPermission(AllPermissions permission) {
    		permissions.add(permission);
    	}
    
    	public void addPermissions(EnumSet<AllPermissions> permissions){
    		for (AllPermissions permission: permissions){
    			addPermission(permission);
    		}
    	}
    	
    	public boolean hasPermission(AllPermissions permission) {
    		return permissions.contains(permission);
    	}
    	
    	public PermissionSetImpl() {
    		
    	}
    	
    	public PermissionSetImpl(AllPermissions seed) {
    		this.addPermission(seed);
    	}
    
    	// Exposes the central containsAll operation
    	public boolean hasPermissions(PermissionSet ps) {
    		return permissions.containsAll(((PermissionSetImpl)ps).permissions);
    	}
    	
    	public EnumSet<AllPermissions> getPermissions() {
    		return permissions;
    	}
    	
    	
    	public String toString() {
    		return permissions.toString();
    	}
    	
    }
  • MockPrincipal (Listing 10) illustrates the simple technique of implementing the java.security.Principal interface with a method to associate groups with a principal. As discussed earlier, it's a stand-in for the implementation-dependent construction of a subject and principals backed by a registry for our testing purposes.

    Listing 10. MockPrincipal testing class
    public class MockPrincipal implements Principal {
    	private String name;
    	private Set<String> groups;
    	public final static String PRINCIPAL_PREFIX="PRINCIPAL/";
    	public MockPrincipal(String name){
    		this.name=name;
    		this.groups=new HashSet<String>();
    		this.groups.add(PRINCIPAL_PREFIX+name);
    	}
    
    	public String getName() {
    		return name;
    	}
    	
    	public void addGroup(String groupName){
    		this.groups.add(groupName);
    	}
    	
    	public Set<String> getGroups(){
    		return this.groups;
    	}
    
    }
  • The AuthorizationManager interface and implementation is, as noted in Part 1, a thin wrapper for your ACL permit() method, specialized for two purposes:

    • The authorize() methods are designed to protect the methods in a service layer from unauthorized access.
    • The canAuthorize() methods are designed to facilitate a GUI display of grants in order to display or manage EAz authorization grants from a graphical interface. The canAuthorize and related display methods will be the focus of the next article in this series.

    Listing 11 shows the key method of interest in the AuthorizationManagerImpl, from the point of view of service layer protection. AuthorizationManagerImpl uses an ACLManager service to retrieve the testACL instead of directly fetching the ACL from an aclDAO. Adding a service layer for ACL retrieval opens up EAz to implementing ACLManager for the possibility of using caches for frequently used ACLs. In closing, Listing 12 shows a stubbed implementation of the ACLManager, indicating locations where a cache would be useful.

    Listing 11. Service layer protection in the AuthorizationManagerImpl
    public void authorize(PermissionSet ps, List<String> nameList,
    		ACLHolder aclHolder) throws PermissionViolationException {
    	// Main implementation
    	ACL testACL = aclManager.getACL(aclHolder);
    	try {
    	testACL.permit(ps, nameList);
    	} catch (PermissionViolationException pve){
    		logger.log(Level.WARNING, "Permission request failed on 
    			nameList "+nameList,pve);
    		throw pve;
    	}
    }
    Listing 12. Stubbed implementation of ACLManager
    public class ACLManagerImpl implements ACLManager {
    	private ACLDao aclDao;
    	
    	public ACL getACL(ACLHolder aclHolder) {
    		ACL theACL = retrieveFromCache(aclHolder);
    		if (theACL==null){
    			theACL=retrieveFromDatabase(aclHolder);
    			addToCache(theACL);
    		}
    		return theACL;
    	}
    
    	private ACL retrieveFromDatabase(ACLHolder aclHolder) {
    		ACLDao aclDao = new ACLDao();
    		ACL theACL = aclDao.retrieveACL(aclHolder);
    		return theACL;
    	}
    
    	private void addToCache(ACL acl) {
    		// TODO cache-dependent ADD method
    		
    	}
    
    	private ACL retrieveFromCache(ACLHolder aclHolder) {
    		// TODO cache-dependent RETRIEVE method
    		return null;
    	}
    }

Conclusion

You have now walked through the testing usage and implementation of EAz as applied to our virtual campus model, visiting and discussing both ways of using and testing EAz and the implementation of the main EAz classes. The places where you need to add Java EE-implementation dependent code to work in a given Java EE container (such as the registry and subject implementation) were indicated and illustrated:

  • Using org.eaz.acl.Permissions to configure an application's permissions and actions/commands.
  • The techniques involved in implementing an EAz data access object to persist and retrieve permissions.
  • The operation of the core ACL class in permitting actions based on PermissionSets associated with groups and principals.
  • The surrounding utility classes and service layer wrapper.

In Part 3, the conclusion to this series, you will explore how to visualize application actions, groups, and principals, and their relationship to EAz permissions, in a graphical interface. This will show users and managers an application's EAz permission grants, and enable you to construct a management console interface.


More in this series


Download

DescriptionNameSize
Code sampleEAuthZ_1.zip18 KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=276210
ArticleTitle=Fine-grained Java EE authorization using Enum-based access control lists with EAz: Part 2: Understanding and using the EAz implementation
publish-date=12122007