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
.NET filters
The .NET filters are written in C# and make use of the WSE 2 libraries. Since the PDP requires a JVM it cannot be run in-process with .NET services, so the .NET filters communicate with the PDP using SOAP calls.
WSE 2 automatically includes the required WS-Security handlers, so this does not need to be listed. The service's Web.config file therefore only needs to list the PEP filter for both chains, eg:
<microsoft.web.services2>
<filters>
<input>
<add type="PBAC.PEPInputFilter, PBAC" /> </input> <output> <add type="PBAC.PEPOutputFilter, PBAC" /> </output> </filters> ... </microsoft.web.services2>
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.ensureDeployed(MyServiceInterface.MY_SERVICE_RESOURCE_TYPE, "my-service-policy.xml");
PBACUtils.ensureServiceResource(this, new MatchRule[] {MatchRule.createAnyoneRule("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:
<... resources of that type.</p><%
PolicyAdmin admin = new PolicyAdmin(request);
String message = admin.processPOST();
if (message != null)
out.write("<p>" + message + "</p>");
admin.showAdmin(out);
The processPOST operation checks whether the user is submitting a form, and performs the requested action if so. It may return a message, which you should display to the user. The showAdmin operation displays the policy tables.
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.
Clicking on a resource takes you to the resource's page. You can configure which JSP page to link to for each resource type using registerResourceTypeLink. By default, the page pbac_resource.jsp is assumed, but customising this allows you to show extra resource-specific information:
admin.registerResourceTypeLink(MyServiceInterface.MY_SERVICE_RESOURCE_TYPE, "my_service.jsp");
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(request);
String message = acladmin.processPOST();
if (message != null)
out.write("<p>" + message + "</p>");
acladmin.showRules(out);
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"); PBACUtils.ensureServiceResource(this, new MatchRule[] {MatchRule.createAnyoneRule("world"));
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 MatchRule 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(MatchRule owner) {
PBACUtils.validateOwner(owner, getCurrentUser(), "owner");
// 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.addAccessControlRule(resourceID, owner);
// Signal successful initialisation
pdp.signal(resourceID, "init");
return resource.ID;
} finally {
pdp.unlock(resourceID);
}
}
The validateOwner method checks that the caller of newDataStager is matched by this policy as a sanity check (to prevent people from creating resources that they can't access). It also ensures that the rule grants the correct role (this is especially important if there are roles with greater access than owner, such as service-admin).
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.unlock(resource.ID);
File tempFile = source.fetchToTemp(); // Slow; resource unlocked
try {
pdp.lockAndCheck(resource.ID, "completeFetch");
resource.store(tempFile);
pdp.update(resource.ID, "fetch-complete");
} finally (
tempFile.unlink();
}
}
Another alternative would be use lockForAdmin instead of lockAndCheck. This locks the resource without checking that it is in a suitable state. In this case you don't need to pass the user's identity, and the operation name is only used for display purposes (e.g., a notice in the admin page saying why the resource is locked). Use lockAndCheck if you want the operation to fail if the resource is now in the wrong state, or the user may have lost their privileges.
Delegation
Delegation operations (granting additional users access to an existing context) are invoked by the service. The client therefore calls a normal service operation, subject to the normal PBAC access control rules, to perform these operations.
For example, a data service might provide an addReaderRule operation which controls access to the reader role. The PBAC policy will determine who can call the addReaderRule operation on the service.
public void addReaderRule(MatchRule rule) {
DataStager resource = getDataStagerFromContext();
delegate.checkRole("reader");
pdp.addAccessControlRule(resource.ID, rule);
}
You should also provide service operations that call PDP.removeAccessControlRule and PDP.getAccessControlRules to provide a full access control interface.
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).
