4.
Service Developer's Guide
Up one level
PBAC 2 : Service Developer's Guide
PBAC can be used to protect all SOAP invocations on your web services.
Each service will have a PBAC policy for any operations which don't take a context (these are similar to static methods and constructors in Java). This is described in the Services section below.
SOAP requests may also take a resource identifier in the SOAP header to provide context for the operations. Operations which take a context work like normal non-static methods in Java. Each resource has a unique identifier and a unique resource type; there is one PBAC policy for each resource type. Creating and managing resources is described in the Resources section.
The Web administration section shows how to create a web-based adminstrator interface easily using JSP. This lets you deploy and undeploy policies, view the resources protected by each policy, and change the dynamic rules for assigning process roles.
The client
No special features should be required on the client except for WS-Security support. If the context ID is passed in the header instead of the body, the client will need to be able to create these headers (typically by supporting WS-Addressing).
The PEP
The PEP wraps the service and prevents unauthorised users from invoking any operations on resources to which they have not been granted access. The PEP is intended to be very light-weight, as most of the work is done in the PDP. Each web service platform requires its own implementation of the PEP.
The client's SOAP message is received by the service framework, which processes it with a series of inbound handlers before invoking the service itself (see the Deployment section in the overview).
The PEP passes the user's X.509 certificate, the resource ID and the SOAP operation name to the PDP (either a local PDP, or a PDP Invoker).
The PDP locks the resource and then checks that the user is authorised to perform the operation. If not, it unlocks the resource and throws an exception, stopping processing of the message. If the user is authorised, the check returns success with the resource still locked.
On successful completion of the check, the PEP invokes the service operation itself. The service operation may signal events to the PDP during its invocation; these events may change the process state of the resource, with the service policy deciding to which state the resource will transition on each event.
After the service operation has been invoked (whether successful or not), the PEP tells the PDP to unlock the resource.
Axis deployment
The PEP class is in the itinnov-grid-pbac-pep-VERSION.jar JAR file. To use PBAC, the service's server-config.wsdd file must list the PEP as the provider. For example:
<service name="DataService" provider="java:PBAC-PEP" use="literal" style="wrapped"> <requestFlow> <handler type="SecurityInboundHandler"/> </requestFlow> <responseFlow> <handler type="IntegrityEnforcementHandler"/> </responseFlow> <parameter name="className" value="java.package.MyServiceInterface/> <parameter name="allowedMethods" value="*"/> <parameter name="scope" value="Application"/> </service>
Note: WSS4J does not provide a configuration setting to disable calling the vertifyTrust method, which checks that the signing certificate is in the service's keystore. Since PBAC roots of trust are per-resource rather than global, this check must be disabled. This can be done by setting SecurityInboundHandler to a subclass of WSDoAllReceiver which overrides this method to always return true.
Note 2: When using the PBAC-PEP provider, the service's className attribute must be set to a Java interface rather than to the implementing class. This removes the need to set allowedMethods to an actual list of operations (since only operations in the interface are exposed) and permits any web-based configuration interface access to the same singleton implementation object. The implementing class to use for the interface is specified in the implementationfactory.properties file:
java.package.MyServiceInterface = com.example.MyServiceImpl
Services
To write a service that uses PBAC, you should already be familiar with creating web services using your chosen service container (eg, tomcat/axis or .NET).
Creating a service policy
The service policy controls access to operations that don't act on an existing resource. We'll start with a service which has just an echo operation. A suitable PBAC policy for our service might look like this:
<?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="echo"> <process-role name="world"/> </operation> </state> <state name="DESTROYED-STATE"/> </state-model>
All resources start in the UNINITIALISED-STATE state. Once initialisation is complete, the service will send the init event to change it to the active state. The service will stay in this state for the whole time it is deployed.
While in the active state, anyone with the world role may invoke the echo operation.
Registering the service with PBAC
When your service is configured, it should deploy the XML policy, and then create a singleton resource named service:java.package.MyServiceInterface (the same interface that was specified in the wsdd file):
PBACUtils pbacUtils = new PBACUtils(pdp)
pbacUtils.ensureDeployed(MyServiceInterface.MY_SERVICE_RESOURCE_TYPE, "my-service-policy.xml");
pbacUtils.ensureServiceResource(this, new PolicyRule[] {
new PolicyRule(MatchPattern.createAnyonePattern(), "world")
});
The first argument to ensureServiceResource is your service object, which must have a PEPServiceResource annotation on its class. Alternatively, you may pass the PBAC type and Java interface as arguments in place of this.
The second argument to creates an initial dynamic policy which gives anyone at all the world role. This is useful for completely public methods. The service policy above permits users with the world role to invoke the echo operation once the service is in the active state. Alternatively, you might choose to leave the initial policy empty and have the service administrator set it through the web interface.
MY_SERVICE_RESOURCE_TYPE is simply a unique string identifying the new type. To ensure that it is unique, it is usual to pick an unused URL in a domain that you control. Including the year that the URL was coined means you only have to remember which names you've already used this year. For example:
public static final String MY_SERVICE_RESOURCE_TYPE = "http://example.com/2006/MyServicePolicy";
Writing the echo opertion
The implementation of the echo operation doesn't require any interaction with PBAC. Add the prototype for echo to MyServiceInterface:
public String echo(String message);
Add the implementation to MyServiceImpl:
public String echo(String message) {
return "Echo: " + message;
}
Anyone should now be able to invoke the echo operation, since we granted everyone the world role.
Creating a web-based administration interface
Services generally export operations that can be used to manage the dynamic policy (e.g., who gets access to the world role). However, it is useful (especially during development) to have a web interface for this. This can be done with some simple JSP code:
<% PolicyAdmin admin = new PolicyAdmin(pdp, request); admin.addPolicyModelToContext(); %> <jsp:include page="/WEB-INF/PBAC-Policy-table.jsp"> <jsp:param name="pbac.link.http://SAMPLE/2006/SampleResourceType" value="pbac_resource.jsp"/> </jsp:include>
This processes the request and sets the "policyModel" attribute on the request with the results. The pbac_resource.jsp then renders it. If you are using an MVC framework (e.g. Spring) then you will probably want to set the attribute in your controller rather than in the JSP.
When you view this page you should see your new policy and, when you select it, you should see the singleton service object in the active state. Use of the administration interface is described in the PBAC administrator's guide.

To include the dynamic policy component in your web interface in the resource's page (my_service.jsp), use this code:
ACLadmin acladmin = new ACLadmin(pdp, request, resourceID, "acl", response); acladmin.addACLmodelToContext(); %> <jsp:include page="/WEB-INF/PBAC-ACL-table.jsp"/> <
This will produce the table shown below for our sample service (a groups section will also be shown if you have groups defined). Use of the dynamic policy administration interface is described in the PBAC administrator's guide.

Resources created by services
Some services only have operations with no context (like static methods on a Java class). Most services however will create other resources dynamically: an account service will create accounts, and a data service will create data stagers. Each such resource needs to have its own access control dynamic policy (every account will grant a different person the budget-holder process role, for example).
Creating a resource policy
For this tutorial, we'll extend our service to allow the creation of data stagers. Users will be able to create, write to, read from and destroy stagers. A simple initial policy might look like this:
<?xml version="1.0" encoding="UTF-8"?> <state-model description="DataService description" 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="empty"/> </transition> </state> <state name="empty"> <operation name="save"> <process-role name="owner"/> </operation> <operation name="destroy"> <process-role name="owner"/> </operation> <transition> <event name="write"/> <to-state name="full"/> </transition> <transition> <event name="destroy"/> <to-state name="DESTROYED-STATE"/> </transition> </state> <state name="full"> <operation name="read"> <process-role name="reader"/> <process-role name="owner"/> </operation> <operation name="deleteContents"> <process-role name="owner"/> </operation> <transition> <event name="delete"/> <to-state name="empty"/> </transition> </state> <state name="DESTROYED-STATE"/> </state-model>
Apart from the special uninitialised and destroyed states, a stager can be either empty or full. When empty, anyone with the owner role can write to it or destroy it. When full, the owner can delete the contents and both the owner and any reader can read the data.
This model introduces some constraints on how a stager is used. For example, no-one (not even the owner) can destroy a stager which is in the full state. It must be moved to the empty state first.
Add a line to your initialisation code to deploy the resource policy:
pbacUtils.ensureDeployed(MyServiceInterface.MY_SERVICE_RESOURCE_TYPE, "my-service-policy.xml"); pbacUtils.ensureDeployed(MyServiceInterface.MY_RESOURCE_TYPE, "my-policy.xml");
The web interface should now list both resource types, although there won't be any objects listed for the new type because no data stagers have been created yet.
Creating a new resource
New resources are registered with PBAC using newProcess. Typically, there is a web service operation which does not take a resource ID as input (and hence is protected by the PBAC service policy above, rather than by a resource policy), but just creates new resources. Methods that create new resources generally take a MatchPattern argument, allowing for future identification of the creator.
Our newDataStager operation is typical (it creates a new resource and initialises it with a dynamic policy that grants the caller of newDataStager the owner role):
public String newDataStager(MatchPattern owner) {
PBACUtils.validatePattern(owner, getCurrentUser());
// Create the new resource
DataStager resource = new DataStager();
// Register it with PBAC
pdp.newProcess(MY_RESOURCE_TYPE, resource.ID);
try {
// Give the caller the 'owner' role
pdp.addPolicyRule(resourceID, new PolicyRule(owner, "owner"));
// Signal successful initialisation
pdp.signal(resourceID, "init");
return resource.ID;
} finally {
pdp.unlock(resourceID);
}
}
The validatePattern method checks that the caller of newDataStager is matched by this pattern as a sanity check (to prevent people from creating resources that they can't access).
New resources start life in the locked state (preventing the user from doing anything with them until initialisation is complete). Since the PEP didn't lock it, it won't unlock it automatically, so the finally clause is required to do this. If the operation fails, the resource will be unlocked without receiving the "init" signal; the PDP will forget about it automatically in this case.
Before sending the "init" signal, we also need to give someone access to the resource. In this case, we grant the caller of newDataStager the owner process role. Note: If we sent the init signal first, and an error occured after that and before adding the new rule, we would get a new resource that no-one could access (except through the web admin interface). So, don't send the init signal until the point where the resource should continue to exist even if an exception is thrown.
If you try to invoke the newDataStager operation, PBAC will reject the request. To allow people to call it, edit the service's policy (not the data stagers's policy!) to add the new operation:
<state name="active"> <operation name="echo"> <process-role name="world"/> </operation>]]> <operation name="newDataStager"> <process-role name="world"/> </operation> </state>
Undeploy the old version using the web interface and deploy the new one. You should now be able to create new data stager objects. Of course, you may not want everyone to be able to create new data stagers. In that case, specify a different role instead of world. You will have to add a new rule using the web interface to grant someone this role.
Operations using the resource
Whenever a user invokes an operation on a resource, the PEP will check that the user is authorised and lock the resource for you. When your operation returns, the PEP will unlock it. Therefore, no-one else can perform operations on the resource during this time (the PDP will queue requests for around a minute by default before giving up).
A typical operation looks like this:
public void deleteContents() {
DataStager resource = getDataStagerFromContext();
resource.deleteContents();
pdp.signal(resource.ID, "delete");
}
Sending a signal may cause the state of the resource to change, affecting what operations can be called. In this case, the delete signal will cause the PDP to transition the resource to the empty state, from which the read operation is unavailable.
When a SOAP message arrives, the PBAC PEP extracts the resource ID from a WS-Addressing header element. It ensures that there is a signature covering both this header and the SOAP body, and passes the context and operation to the PDP to check that the signer is authorised to invoke the method. If the operation is authorised, the PEP stores the context in the Axis message context for use by the service.
The private getDataStagerFromContext method gets this resource context from the SOAP message context. There should always be a context by this point, because if the user didn't give one then PBAC would have used the service policy rather than the data stager policy to check the user's authorisation and the service policy should never allow deleteContents to be called. A typical implementation looks like this:
private DataStager getDataStagerFromContext() {
ProcessContext primaryContext = ProcessContextHelper.getProcessContext();
String resourceID = primaryContext.getConversation();
if (resourceID == null)
throw new RuntimeException("No resource ID in message context (policy enforcement error)");
return loadFromDatabase(resourceID);
}
Getting a resource's process state
Many services will maintain their own idea of a resource's state. However, sometimes it is useful to know the PBAC process state of a resource, or even make it available to clients. This operation does not send any signals, and thus does not change the state.
public String getState() {
DataStager resource = getDataStagerFromContext();
return pdp.getCurrentState(resource.ID);
}
Destroying a resource
There are two special states: UNINITIALISED-STATE and DESTROYED-STATE. If a resource is unlocked while in either of these two states, it is destroyed (causing PBAC to forget all about it). The destroyed state is used to remove a resource which is no longer needed, while unlocking an uninitialised resource indicates a failure while the service was setting up the resource.
To destroy a resource, send a signal such as destroy. The policy should cause the resource to transition to DESTROYED-STATE. When the PEP unlocks the resource after the operation completes, PBAC will forget about the resource.
Other PBAC operations
For a complete list of available PBAC operations, consult the JavaDoc reference.
Unlocking a resource during an operation
A service's operation is invoked by Axis only once PBAC has checked that the client is authorised to perform the operation. The resource is locked at this point.
Before returning, the service may send any number of events to the PDP, which may choose to update the state of the resource in response.
The fetch operation unlocks the resource, fetches the data from a remote URL, and then sets the state to FULL and returns. The purpose of this example is to show how the context can be unlocked during a long-running operation. Note that the resource itself must not be modified while it is unlocked; instead, we download to a temporary file. Once the data is ready, we lock the context to actually copy it in. "completeFetch" is an internal (fake) operation, used to reacquire the lock during the fetch operations.
Since the resource is unlocked, another operation could change its state (such as making it read-only or finishing it) while we fetch the data. In that case, the lockAndCheck operation would throw an exception (which is safe and reasonable). An alternative approach is to transition into a 'fetching' state at the start, from which only getState can be called. However, this makes the error handling more complex, as you must take care that the resource is not left in the 'fetching' state on error.
public void fetch(URL source) {
DataStager resource = getDataStagerFromContext();
pdp.signal(resource.ID, "start-fetch");
pdp.unlock(resource.ID);
try {
File tempFile = source.fetchToTemp(); // Slow; resource unlocked
} finally {
pdp.lockForAdmin(resource.ID, "fetch-complete");
pdp.signal(resource.ID, "fetch-complete");
}
try {
// Optional: pdp.check(...);
resource.store(tempFile);
} finally (
tempFile.unlink();
}
}
Another option would be to call check before storing the data. This is useful if you want the operation to fail if the resource is now in the wrong state, or if the user may have lost their privileges. Some care should be taken when creating the state model to ensure that the resource cannot be destroyed while the fetch method is running (i.e. start-fetch should move it to a suitable state where only a limited set of operations can be used).
Parameterised checks
Sometimes the access control decision for a SOAP operation also depends on an argument. For example, a generic addPolicyRule operation may allow different users to control access to different roles.
In this case, a special annotation tells the PEP to lock the resource without checking. The operation then invokes the check itself, with the operation and role together as the action. For example:
@AccessControl(disableCheck = true)
public void addPolicyRule(PolicyRule rule) throws RemoteException {
ProcessContext primaryContext = ProcessContextHelper.getProcessContext();
String conversationID = primaryContext.getConversation();
rule.checkValid();
pdp.check(conversationID, getCurrentUser(), "addPolicyRule:" + rule.getRole());
pdp.addPolicyRule(conversationID, rule);
}
Discovering resources
Most services provide an operation to let users ask "Which resources can I access?". This is easily implemented using PBAC:
public String[] getResources() {
return pdp.getResources(MyServiceInterface.MY_RESOURCE_TYPE, getCurrentUser(), null);
}
You may wish to convert the resources to EndpointReferenceTypes, so that they include the service address and any other meta-data (such as the user's label for the resource).
