Personal tools
You are here: Home GRIA Documentation Documentation 5.3 Reference Manuals PBAC 2 Manual Developer's Guide

3. Developer's Guide

Up one level
PBAC is often used to protect web services. However, this introduces a lot of extra complexitiy, so we will instead start with a simple single process example.

PBAC 2 : Developer's Guide

PBAC is often used to protect web services. However, this introduces a lot of extra complexitiy, so we will instead start with a simple single process example.

Deploying a policy

We'll start with a simple resource we wish to protect - a standard Java Map. To begin, we need a policy for it (save this as map-policy.xml):

<?xml version="1.0" encoding="UTF-8"?>
<state-model description="My service"
 xmlns="http://www.itinnovation.soton.ac.uk/uk/ac/soton/itinnovation/grid/pbac2/staticpolicy/types">

	<state name="UNINITIALISED-STATE">
		<transition>
			<event name="init"/>
			<to-state name="active"/>
		</transition>
	</state>

	<state name="active">
		<operation name="put">
			<process-role name="writer"/>
		</operation>
		<operation name="get">
			<process-role name="reader"/>
			<process-role name="writer"/>
		</operation>
		<transition>
			<event name="destroy"/>
			<to-state name="DESTROYED-STATE"/>
		</transition>
	</state>

	<state name="DESTROYED-STATE"/>

</state-model>

The policy states that a Map has three states: UNINITIALISED-STATE (before it is ready for use), active (the normal state) and DESTROYED-STATE (no longer available for use). UNINITIALISED-STATE and DESTROYED-STATE are special, and must have these names.

The policy also defines two state transitions: the init signal moves an uninitialised Map into the active state, and the destroy signal moves an active Map to DESTROYED-STATE. The state model is shown on the right.

As well as giving the state model, the policy defines which actions are available to each process role in each state. Here, we define two roles: in the active state, a reader can call the get operation, while a writer can call get or put.

Our program's first step, after initialising the connection to the database and creating the PDP, is to deploy this policy, giving it a globally unique name (http://example.com/map):

import uk.ac.soton.itinnovation.grid.pbac2.pep.DynamicPEP;
import uk.ac.soton.itinnovation.grid.types.TrustedAttribute;
import uk.ac.soton.itinnovation.grid.types.MatchPattern;
import uk.ac.soton.itinnovation.grid.types.PolicyRule;
import uk.ac.soton.itinnovation.grid.types.SubjectDescription;
import uk.ac.soton.itinnovation.grid.pbac2.pdp.PDP;
import uk.ac.soton.itinnovation.grid.pbac2.pdp.PBACUtils;
import uk.ac.soton.ecs.iam.grid.utils.ImplementationFactory;
import java.util.Map;
import java.util.HashMap;

import org.hibernate.cfg.Configuration;
import org.hibernate.SessionFactory;

public class PBACTutorial {
  private final static String MAP_RESOURCE_TYPE = "http://example.com/map";
  private static SessionFactory factory = createFactory();

  private static SessionFactory createFactory() {
    Configuration cfg = new Configuration();
    cfg.configure();
    cfg.setProperty("hibernate.current_session_context_class", "org.hibernate.context.ManagedSessionContext");
    return cfg.buildSessionFactory();
  }

  public static void main(String[] args) throws Exception {
    PDP pdp = new PDPImpl(factory);
    new PBACUtils(pdp).ensureDeployed(MAP_RESOURCE_TYPE, "map-policy.xml");
  }
}

Building and running the examples

To compile and run this code, add the following dependency to your Maven 2 pom.xml:

	<dependencies>
		<dependency>
			<groupId>org.gria</groupId>
			<artifactId>gria-pbac-pdp</artifactId>
			<version>5.3</version>
		</dependency>
	</dependencies>

You will also need the following configuration files (also in CLASSPATH):

If you run the code now, it should create a few pbac-db.* files, recording that the policy has been registered. However, when running the program again the database will be wiped first. In a real system, you would edit hibernate.properties to specify hibernate.hbm2ddl.auto update rather than hibernate.hbm2ddl.auto create.

Adding resources

Now that we've defined an access policy for Maps, we need to register an actual Map object with PBAC. This is done with newProcess:

public class PBACTutorial {
	...

	public static void main(String[] args) throws Exception {
		PDP pdp = new PDPImpl(factory);

		// Tell PBAC about the state model for resources of type MAP_RESOURCE_TYPE
		new PBACUtils(pdp).ensureDeployed(MAP_RESOURCE_TYPE, "map-policy.xml");

		Map<String,String> map1 = new HashMap<String,String>();
		pdp.newProcess(MAP_RESOURCE_TYPE, "map-1");
		try {
			map1.put("name", "pbac-tutorial");
			pdp.signal("map-1", "init");
		} finally {
			pdp.unlock("map-1");
		}

		System.out.println("Resources registered with PBAC:");
		for (String resource : pdp.getResources(MAP_RESOURCE_TYPE, null, null)) {
			System.out.println("Resource: " + resource);
		}

	}
}

There are several points to note in the above code:

  • When the new map-1 resource is registered it is initially locked. In a multi-threaded application, this prevents other threads from using this resource until we unlock it. You must always have a try...finally block immediately following a call to newProcess to ensure that the unlock always happens, even if initialisation fails. Note: locking does not use the database.

  • We send the init signal at the end of the initialisation so that it will be in the active state when unlocked. If the resource is unlocked while still in the UNINITIALISED-STATE it will be automatically removed from PBAC's database. Thus, no special clean-up code is needed to handle errors during initialisation.

Finally, we list all the resources known to PBAC to check that it worked. If you remove the call to signal, the list will be empty.

Checking for access

Once the resource is created and unlocked, we can check whether someone can call the get method using lockAndCheck:

    SubjectDescription nobody = new SubjectDescription();
    pdp.lockAndCheck("map-1", nobody, "get");
    try {
      System.out.println("name = " + map1.get("name"));
    } finally {
      pdp.unlock("map-1");
    }

Like newProcess, a successful call to lockAndCheck leaves the resource locked, and a try...finally block must follow immediately to ensure it is unlocked again afterwards.

Running the code now will give the exception No access to resource 'map-1' for user '<subject (no certificate)>', because we haven't granted anyone access to this resource yet.

Granting access

While initialising the resource, we'll add a rule granting anyone the reader role, which we defined above in our map-policy.xml:

	Map map1 = new HashMap();
	pdp.newProcess(MAP_RESOURCE_TYPE, "map-1");
	try {
		map1.put("name", "pbac-tutorial");

		pdp.addPolicyRule("map-1", 
			new PolicyRule(MatchPattern.createAnyonePattern(), "reader");
		
		pdp.signal("map-1", "init");
	} finally {
		pdp.unlock("map-1");
	}

If run, the program will now display name = pbac-tutorial, because even our nobody user with no credentials is allowed to call get.

If you change the action to put, you'll get User '<subject (no certificate)>' is not authorised to perform action 'put' on resource 'map-1'. This is a slightly different error message; nobody does have some access to this resource, but not enough to call put, since the anyoneIsAReader rules only makes them a reader, not a writer.

Subject credentials

If we have more information about our user, we can add it to the SubjectDescription. This allows more useful match rules. There are three types of information we can add:

An X.509 certificate

This is a certificate which you know belongs to the user (e.g., because the message you received was signed by the private key corresponding to the public key in the certificate).

For example, a servlet can get this from a request sent over https which has client authentication turned on using the javax.servlet.request.X509Certificate attribute. An Axis SOAP service can get this from a security handler such as WSS4J.

Note: you don't need to believe that the details in the certificate are genuine (you don't need to trust the issuer); you just need to know that the user has the private key. The PDP will check whether the certificate allows access, including checking the issuer's signature.

A signed SAML assertion

You may wish to let users pass in additional SAML tokens, which you should attach to the SubjectDescription. The subject of the assertion must be an X.509 certificate. You do not need to trust the token; PBAC will ensure that the subject of the token is the user's X.509 certificate, and that the signature is trusted.

A trusted attribute

Some other system may have already authenticated the user, or provided some other information which you trust. For example, a servlet container may have asked the user for a password and checked that it is correct. In that case, you can just add the user-name to the SubjectDescription. Obviously, you MUST NOT let users specify the trusted attributes!

PBAC's main use is in checking cryptographic tokens (X.509 certificates and SAML tokens), but we'll use trusted attributes for our example as they're simpler. We'll add a match rule stating that bob is a writer (the null in the authority place indicates that the attribute is verified outside of PBAC):

	pdp.newProcess(MAP_RESOURCE_TYPE, "map-1");
	try {
		map1.put("name", "pbac-tutorial");

		pdp.addPolicyRule("map-1", 
			  new PolicyRule(MatchPattern.createAnyonePattern(), "reader"));

		pdp.addPolicyRule("map-1",
		  new PolicyRule(new MatchPattern("example.username", "bob", null), "writer"));

		pdp.signal("map-1", "init");
	} finally {
		  pdp.unlock("map-1");
	}

Now we'll assume we have authenticated the user already somehow, and add their user name before trying to call put:

    SubjectDescription bob = new SubjectDescription();
    bob.setTrustedAttributes(new TrustedAttribute[] {
	new TrustedAttribute("example.username", "bob"),
    });

    pdp.lockAndCheck("map-1", bob, "put");
    try {
      map1.put("verified", "true");
    } finally {
      pdp.unlock("map-1");
    }

The DynamicPEP

It's annoying to have to write the action name twice each time (once as an argument to lockAndCheck and once as the method on map1. It would be easy to make a copy-and-paste error and end up doing a put when you only checked for get access! Also, the extra boiler-plate code is ugly.

Luckily, PBAC comes with a useful DynamicPEP class to solve these problems. We can use it to create a wrapper for map1 that enforces the access policy:

Map protectedMap = DynamicPEP.dynamicPEP(Map.class, map1, pdp, "map-1", bob);

System.out.println("name = " + protectedMap.get("name"));
protectedMap.put("verified", "true");

Here we performed two operations (get and put), locking, checking and unlocking the resource each time automatically. If you replace bob with nobody, the get will succeed but the put will fail.

In fact, the put will fail with an UndeclaredThrowableException, because Java's Map interface doesn't say that put can throw RemoteException. When defining your own services or resources, you would declare each method to throw this exception.

There are pros and cons to the DynamicPEP. It saves a lot of code, and makes the code more readable and safer. However, you do need to design your interface so that each method corresponds directly to an access control decision.

Destroying a resource

To unregister a resource (so that PBAC forgets about it) you must unlock it while it is in DESTROYED-STATE. We can move it to this state using signal just as we moved it to the active state before:

    pdp.lockForAdmin("map-1", "destroy");
    try {
	    pdp.signal("map-1", "destroy");
    } finally {
	    pdp.unlock("map-1");
    }

    System.out.println("name = " + protectedMap.get("name"));

This program will give the error Resource does not exist (for resource map-1) when we try to invoke the get method after destroying it.

This fragment also demonstrates the lockForAdmin method, an alternative to lockAndCheck which doesn't perform the check part (the action given is for informational use only; it may appear in errors and log messages). This is useful when the system itself needs to perform an action. Of course, if a user requested the destruction of the map then we would use lockAndCheck("map-1", subject, "destroy") and pass the user's credentials as normal.

Conclusions

This guide has shown how to use PBAC within a single application. The PBAC database is stored in a file, and re-initialised on each restart to aid testing. We can make calls on the PDP directly, or we can wrap the target objects using a DynamicPEP to make the calls for us.

In a real deployment, you would probably:

  • Configure hibernate to use a real database, not a file.
  • Configure hibernate not to clear the tables on start-up.
  • Use cryptographic tokens such as X.509 certificates rather than trusted attributes, or get the trusted attribute from some other system.
  • Create multiple resources and possibly multiple resource types.
  • Provide an administration interface, allowing the XML policy to be updated by the administrator.
  • Allow multiple client connections in parallel, taking advantage of PBAC's resource locking system.

The SOAP developer's guide shows how to use PBAC to control access to a SOAP service, and the GRIA Service Developer Kit contains a sample service which uses PBAC to control access to its SOAP operations and a tutorial showing how to extend it.