Building the Secure Account Solution


The starting point is AccountFactoryTest, a test class that exercises the various combinations of permissions and methods. The test class contains two test methods: testReadOnlyAccess ensures that all account methods can be called normally by a user with update permission, and testUpdateAccess ensures that exceptions are thrown when a user with read-only access attempts to execute a secure method.

 package sis.studentinfo; import java.math.*; import java.util.*; import java.lang.reflect.*; import junit.framework.*; import sis.security.*; public class AccountFactoryTest extends TestCase {    private List<Method> updateMethods;    private List<Method> readOnlyMethods;    protected void setUp() throws Exception {       updateMethods = new ArrayList<Method>();       addUpdateMethod("setBankAba", String.class);       addUpdateMethod("setBankAccountNumber", String.class);       addUpdateMethod("setBankAccountType",          Account.BankAccountType.class);       addUpdateMethod("transferFromBank", BigDecimal.class);       addUpdateMethod("credit", BigDecimal.class);       readOnlyMethods = new ArrayList<Method>();       addReadOnlyMethod("getBalance");       addReadOnlyMethod("transactionAverage");    }    private void addUpdateMethod(String name, Class parmClass)          throws Exception {       updateMethods.add(          Accountable.class.getDeclaredMethod(name, parmClass));    }    private void addReadOnlyMethod(String name) throws Exception {       Class[] noParms = new Class[] {};       readOnlyMethods.add(          Accountable.class.getDeclaredMethod(name, noParms));    }    public void testUpdateAccess() throws Exception {       Accountable account = AccountFactory.create(Permission.UPDATE);       for (Method method: readOnlyMethods)          verifyNoException(method, account);       for (Method method: updateMethods)          verifyNoException(method, account);    }    public void testReadOnlyAccess() throws Exception {       Accountable account = AccountFactory.create(Permission.READ_ONLY);       for (Method method: updateMethods)          verifyException(PermissionException.class, method, account);       for (Method method: readOnlyMethods)          verifyNoException(method, account);   }    private void verifyException(          Class exceptionType, Method method, Object object)          throws Exception {       try {          method.invoke(object, nullParmsFor(method));          fail("expected exception");       }       catch (InvocationTargetException e) {          assertEquals("expected exception",             exceptionType, e.getCause().getClass());       }    }    private void verifyNoException(Method method, Object object)          throws Exception {       try {          method.invoke(object, nullParmsFor(method));       }       catch (InvocationTargetException e) {          assertFalse(             "unexpected permission exception",             PermissionException.class == e.getCause().getClass());       }    }    private Object[] nullParmsFor(Method method) {       return new Object[method.getParameterTypes().length];    } } 

The test demonstrates some additional features of the Java reflection API.

In the test, you create two collections, one for read-only methods and one for update methods. You populate both read-only and update collections in the setUp method with java.lang.reflect.Method objects. Method objects represent the individual methods defined within a Java class and contain information including the name, the parameters, the return type, and the modifiers (e.g., static and final) of the method. Most important, once you have a Method object and an object of the class on which the method is defined, you can dynamically execute that method.

You obtain Method objects by sending various messages to a Class object. One way is to get an array of all methods by sending getdeclaredMethods to a Class. The class will return all methods directly declared within the class. You can also attempt to retrieve a specific method by sending the Class the message getdeclaredMethod, passing along with it the name and the list of parameter types. In the above code, the line:

 Accountable.class.getDeclaredMethod(name, parmClass) 

demonstrates use of getdeclaredMethod.

The test testReadOnlyAccess first uses the AccountFactory to create an objectthe proxythat implements the Accountable interface. The parameter to the create method is a Permission enum:

 package sis.security; public enum Permission {    UPDATE, READ_ONLY } 

You derive the Accountable interface by extracting all public method signatures from the Account class:

 package sis.studentinfo; import java.math.*; public interface Accountable {    public void credit(BigDecimal amount);    public BigDecimal getBalance();    public BigDecimal transactionAverage();    public void setBankAba(String bankAba);    public void setBankAccountNumber(String bankAccountNumber);    public void setBankAccountType(       Account.BankAccountType bankAccountType);    public void transferFromBank(BigDecimal amount); } 

You'll need to modify the Account class definition to declare that it implements this interface:

 public class Account implements Accountable { 

Which it already doesno further changes to Account are required.

Once testReadOnlyAccess has an Accountable reference, it loops through the lists of update methods and read-only methods.

 for (Method method: updateMethods)    verifyException(PermissionException.class, method, account); for (Method method: readOnlyMethods)    verifyNoException(method, account); 

For each update method, testReadOnlyAccess calls verifyException to ensure that an exception is thrown when method is invoked on account. The test also ensures that each read-only method can be called without generating security exceptions.

The method verifyException is responsible for invoking a method and ensuring that a SecurityException was thrown. The line of code that actually calls the method:

 method.invoke(object, nullParmsFor(method)); 

To execute a method, you send a Method object the invoke message, passing along with it the object on which to invoke the method plus an Object array of parameters. Here, you use the utility method nullParmsFor to construct an array of all null values. It doesn't matter what you pass the methods. Even if a method generates a NullPointerException or some other sort of exception, the test concerns itself only with PermissionException:

 package sis.security; public class PermissionException extends RuntimeException { } 

The method verifyException expects the invoke message send to generate an InvocationTargetException, not a PermissionException. If the underlying method called by invoke tHRows an exception, invoke wraps it in an InvocationTargetException. You must call getCause on the InvocationTargetException to extract the original wrapped exception object.

The use of reflection in the test was not necessary. If this was not a lesson on reflection, I might have had you implement the test in some other nondynamic manner.

The job of the AccountFactory class is to create instances of classes that implement the Accountable interface.

 package sis.studentinfo; import java.lang.reflect.*; import sis.security.*; public class AccountFactory {    public static Accountable create(Permission permission) {       switch (permission) {          case UPDATE:             return new Account();          case READ_ONLY:             return createSecuredAccount();       }       return null;    }    private static Accountable createSecuredAccount() {       SecureProxy secureAccount =          new SecureProxy(new Account(),             "credit",             "setBankAba",             "setBankAccountNumber",             "setBankAccountType",             "transferFromBank");       return (Accountable)Proxy.newProxyInstance(          Accountable.class.getClassLoader(),          new Class[] {Accountable.class },          secureAccount);    } } 

The use of a factory means that the client is isolated from the fact that a security proxy even exists. The client can request an account and, based on the Permission enum passed, AccountFactory creates either a real Account object (Permission.UPDATE) or a dynamic proxy object (Permission.READ_ONLY). The work of creating the dynamic proxy appears in the method createSecuredAccount.

The class SecureProxy is the dynamic proxy object that you will construct. It could act as a secure proxy for any target class. To construct a SecureProxy, you pass it the target object and a list of the methods that must be secured. (The list of methods could easily come from a database lookup. A security administrator could populate the contents of the database through another part of the SIS application.)

The second statement in createSecuredAccount is the way that you set up the SecureProxy object to act as a dynamic proxy. It's an ugly bit of code, so I'll repeat it here with reference numbers:

 return (Accountable)Proxy.newProxyInstance(   // 1    Accountable.class.getClassLoader(),        // 2    new Class[] {Accountable.class },          // 3    secureAccount);                            // 4 

Line 1 invokes the Proxy factory method newProxyInstance. This method is not parameterized, thus it returns an Object. You must cast the return value to an Accountable interface reference.

The first parameter to newProxyInstance (line 2) requires the class loader for the interface class. A class loader reads a stream of bytes representing a Java compilation unit from some source location. Java contains a default class loader, which reads class files from a disc file, but you can create custom class loaders that read class files directly from a database or from a remote source, such as the internet. In most cases you will want to call the method getClassLoader on the Class object itself; this method returns the class loader that originally loaded the class.

The second parameter (line 3) is an array of interface types for which you want to create a dynamic proxy. Behind the scenes, Java will use this list to dynamically construct an object that implements all of the interfaces.

The type of the final parameter (line 4) is InvocationHandler, an interface that contains a single method that your dynamic proxy class must implement in order to intercept incoming messages. You pass your proxy object as this third parameter.



Agile Java. Crafting Code with Test-Driven Development
Agile Javaв„ў: Crafting Code with Test-Driven Development
ISBN: 0131482394
EAN: 2147483647
Year: 2003
Pages: 391
Authors: Jeff Langr

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net