In the article Tapestry5HowToCreateADispatcher we covered the basics of creating a Dispatcher and getting it into the pipeline. This quick article is a supplement to that one, continuing the example of implementing an access control mechanism. Specifically, we will cover how to access request-specific state objects (ASOs) from a singleton service (our access controller). If you haven't already read that article, you should do so before proceeding.

Before reading this be sure to first read about how Tapestry5 manages application state:

http://tapestry.apache.org/tapestry5/guide/appstate.html

This is a fairly quick and painless read and should get you up to speed on what application state is, when and why you need it, how to use it, and the basics of how it works. The following javadocs are also worth pointing out:

ApplicationState, ApplicationStateManager

A Glance at the Problem

The access controller we've implemented is a singleton service. It doesn't make sense to implement it per-request as the only per-request state it needs are the access permissions of the requesting user. However we can't just annotate a service field with @ApplicationState as we normally would in a page or component. This annotation ties a field to a request, but singleton services have no such association to any request. They are instantiated once and accessible by all concurrent requests, at the same time.

The Solution in Theory

If you understand the threadding problem and have read the documentation on application state, as well as the javadoc for the ApplicationStateManager, then you know what needs to be done. Our service implementation needs very little modification. We simply must obtain a reference to the ApplicationStateManager and explicitly check for the ASO (by class). It has the mechanics to check the session of the requesting user, so all we need to is ask it for the ASO and it will take care of the dirty work.

Of course this means you must have a class dedicated to the purpose of holding access privileges. For our example we'll assume we have such a class named UserPermissions. I can think of 3 ways one might implement the storage of this class:

  1. Store the UserPermissions class directly as an ASO. The obvious pro here is simplicity: just grab this class and check the contained permission against those required.
  2. Create a higher-level !User class that has a field of type UserPermissions. This makes sense because permissions typically are part of a user abstraction.
  3. Create a general "bucket" class to hold all objects that depend on a valid user session, and then store whatever state you need inside it. I see two advantages with this method:
    1. When a user logs out, you can purge all objects by null-ing one state object, instead of however many you create.
    2. I *believe* that T5's ASO implementation only allows you to store one object per type. That means that if you wanted to store two different objects of the same class, well, you couldn't! By using a general bucket you can store what you like.

Whatever strategy you choose, the fundamentals are the same.

A Solution in Code

Here is how we could implement the solution. Assume that we have a class named UserPermissions for storing the permissions, and that it contains a method named canAccess. This method returns a boolean showing if the user can access the resource (page) represented by the Request object.

import java.io.IOException;

import org.apache.tapestry5.services.ApplicationStateManager;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.Response;

public class AccessController implements Dispatcher {
	
	/* Our state manager. */
	private ApplicationStateManager asm;
	
	/**
	 * Receive our state manager as a constructor argument. When we bind this
	 * service, T5 IoC will intelligently provide the state manager - batteries included! 
	 */
	public SingletonAccessControllerImpl(ApplicationStateManager asm) {
		this.asm = asm;
	}

	public boolean dispatch(Request request, Response response) throws IOException {
		boolean canAccess = false;
		
		/*
		 * Per the application state documentation, we check for the existence of an
		 * ASO before attempting access. These prevents any unnecessary overhead
		 * (automatic session creation).
		 */
		if(asm.exists(UserPermissions.class)) {
			UserPermissions perms = asm.get(UserPermissions.class);
			/*
			 * The object referenced by 'perms' is an instance specific to
			 * the current request, which is what we need. Now check the
			 * permissions against the resource - how you do this of course
			 * depends on your resource restriction implementation. However
			 * you will most likely base this on the page (page name or
			 * class).
			 */
			canAccess = perms.canAccess(request);
		}
		
		if(!canAccess) {
			/*
			 * This is an unauthorized request, so throw an exception. We'll need
			 * more grace than this, such as a customized exception page and/or
			 * redirection to a login page...
			 */
			throw new RuntimeException("Access violation!");
		}
		
		return false;
	}
	
}

Some quick notes on the implementation:

  1. We get our ApplicationStateManager once - as a constructor argument. If we auto-bind our service, T5 IoC will magically take care of everything. If we want to build it ourselves in our app module, we simply need to @Inject the ApplicationStateManager as an argument to our builder method.
  2. To keep reduce session overhead, we avoid creating it if the ASO we need doesn't exist.

Conclusion

It has been noted on the mailing list that access control can be implemented using this same technique, but as a RequestFilter instead. I haven't tried this but the same concepts regarding ASOs apply.

Any suggestions, corrections, or other furtherings on this document are most welcome. If you'd like to chat with me about this, you can find me in #tapestry on irc.freenode.net (chrislewis).

  • No labels