Monday, October 31, 2005

ACEGI Tutorial: An Example of Method-based Access Control and JUnit for Testing

NOTE: Updated 2006, May 10 - changed net.sf to org in package names. And updated code to work with ACEGI v1.0 RC2

This ACEGI tutorial shows how to implement the following security requirements for the BookBean class:

  • Only MANAGER users can replace the existing value (ie, call setValue)
  • Only MANAGER users and WORKER users can change the value (ie, call changeValue)
  • Any user (ie, someone with no roles) can view the value (ie, call getValue)

The BookBean class is extremely simple as shown by its interface:

/* BookBean.java */
package com.affy;

public interface BookBean {
    public int getValue();
    public void setValue(int _value);
    public void changeValue(int _value);
}

Likewise, the implementation of this interface is simple:

/* BookBeanImpl.java */
package com.affy;

public class BookBeanImpl implements BookBean {

    private int value = 0;

    public BookBeanImpl() {
        super();
    }

    public int getValue() {
        return this.value;
    }

    // replace the value.
    public void setValue(int _value) {
        this.value = _value;
    }

    // change the value.
    public void changeValue(int _value) {
        this.value += _value;
    }

}

Now that we've seen the interface and the implementation, we can look at how a Spring configuration file is used to provide declarative access control. I'll note at this point that while the user information (ie, names, passwords, etc...) is hard-coded in the configuration file you can also use a database, ldap, or whatever persistent storage mechanisn you prefer.

I find it hard to glean useful information when I read XML configuration files; I suspect it's a learned skill. However, the configuration file for this example is only 56 lines of code - much of which can be considered boilerplate.

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>

<-- spring.xml -->

  <-- This is the bean that needs to be protected. -->
  <bean id='bookBean' class='com.affy.BookBeanImpl'/>

  <-- This bean defines a proxy for the protected bean. Notice that -->
  <-- the id defined above is specified. When an application asks Spring -->
  <-- for a bookBean it will get this proxy instead. -->
  <bean id='autoProxyCreator' class='org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator'>
    <property name='interceptorNames'>
      <list><value>securityInterceptor</value></list>
    </property>
    <property name='beanNames'>
      <list><value>bookBean</value></list>
    </property>
  </bean>

  <-- This bean specifies which roles are authorized to execute which methods. -->
  <bean id='securityInterceptor' class='org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor'>
    <property name='authenticationManager' ref='authenticationManager'/>
    <property name='accessDecisionManager' ref='accessDecisionManager'/>
    <property name='objectDefinitionSource'>
      <value>
        com.affy.BookBean.setValue=ROLE_MANAGER
        com.affy.BookBean.changeValue=ROLE_WORKER,ROLE_MANAGER
      </value>
    </property>
  </bean>

  <-- This bean specifies which roles are assigned to each user. You'll notice  -->
  <-- that I'm using an in-memory database implementation instead of using  -->
  <-- LDAP or a 'real' database. The ACEGI-provided in-memory implementation is great for testing! -->
  <bean id='userDetailsService' class='org.acegisecurity.userdetails.memory.InMemoryDaoImpl'>
    <property name='userMap'>
      <value>
        manager=manager,ROLE_MANAGER
        worker=worker,ROLE_WORKER
        anonymous=anonymous,
        disabled=disabled,disabled,ROLE_WORKER
      </value>
    </property>
  </bean>
 
  <-- This bean specifies that a user can access the protected methods -->
  <-- if they have any one of the roles specified in the objectDefinitionSource above. -->
  <bean id='accessDecisionManager' class='org.acegisecurity.vote.AffirmativeBased'>
    <property name='decisionVoters'>
      <list><ref bean='roleVoter'/></list>
    </property>
  </bean>

  <-- The next three beans are boilerplate. They should be the same for nearly all applications. -->
  <bean id='authenticationManager' class='org.acegisecurity.providers.ProviderManager'>
    <property name='providers'>
      <list><ref bean='authenticationProvider'/></list>
    </property>
  </bean>

  <bean id='authenticationProvider' class='org.acegisecurity.providers.dao.DaoAuthenticationProvider'>
    <property name='userDetailsService' ref='userDetailsService'/>
  </bean>

  <bean id='roleVoter' class='org.acegisecurity.vote.RoleVoter'/>
   
</beans>

So we've seen the interface, the implementation, and the Spring configuration file. The last file shows how to unit test the access control. One important thing to note is that the SecuriyContextHolder is a static object. So while the approach shown below is valid for an single-thread application which can change its user context as needed, there are definitely some synchronization issues that need to be addressed for multi-threaded applications.

// MethodAclTest.java
package com.affy;
 
import junit.framework.TestCase;
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.Authentication;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.DisabledException;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.context.SecurityContextImpl;
import org.acegisecurity.providers.AuthenticationProvider;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MethodAclTest extends TestCase {

    // Read the Spring configuration file. I typically create a directiory called config which
    // gets added to my classpath.
    private static ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");

    private static void createSecureContext(final ApplicationContext ctx, final String username, final String password) {
        AuthenticationProvider provider = (AuthenticationProvider) ctx.getBean("authenticationProvider");
        Authentication auth = provider.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        SecurityContextHolder.getContext().setAuthentication(auth);
    }

    // Clear the security context after each test.
    public void teardown() {
        SecurityContextHolder.setContext(new SecurityContextImpl());
    }

    // There are three methods with three roles Which means that I need
    // nine tests.

    ///////////////////
    // setValue tests.
    ///////////////////

    public void testManagerAccessForSet() {
        createSecureContext(ctx, "manager", "manager");
        ((BookBean) ctx.getBean("bookBean")).setValue(100);
    }

    public void testWorkerAccessForSet() {
        createSecureContext(ctx, "worker", "worker");
        try {
            ((BookBean) ctx.getBean("bookBean")).setValue(100);
            fail("Expected AccessDeniedException.");
        } catch (AccessDeniedException e) {
            // do nothing.
        }
    }

    public void testAnonymousAccessForSet() {
        createSecureContext(ctx, "anonymous", "anonymous");
        try {
            ((BookBean) ctx.getBean("bookBean")).setValue(100);
            fail("Expected AccessDeniedException.");
        } catch (AccessDeniedException e) {
            // do nothing.
        }
    }

    ///////////////////
    // changeValue tests.
    ///////////////////
    public void testManagerAccessForChange() {
        createSecureContext(ctx, "manager", "manager");
        ((BookBean) ctx.getBean("bookBean")).changeValue(100);
    }

    public void testWorkerAccessForChange() {
        createSecureContext(ctx, "worker", "worker");
        ((BookBean) ctx.getBean("bookBean")).changeValue(100);
    }

    public void testAnonymousAccessForChange() {
        createSecureContext(ctx, "anonymous", "anonymous");
        try {
            ((BookBean) ctx.getBean("bookBean")).changeValue(100);
            fail("Expected AccessDeniedException.");
        } catch (AccessDeniedException e) {
            // do nothing.
        }
    }
    
    ///////////////////
    // getValue tests.
    ///////////////////
    public void testManagerAccessForGet() {
        createSecureContext(ctx, "manager", "manager");
        ((BookBean) ctx.getBean("bookBean")).getValue();
    }

    public void testWorkerAccessForGet() {
        createSecureContext(ctx, "worker", "worker");
        ((BookBean) ctx.getBean("bookBean")).getValue();
    }

    public void testAnonymousAccessForGet() {
        createSecureContext(ctx, "anonymous", "anonymous");
        ((BookBean) ctx.getBean("bookBean")).getValue();
    }

    ///////////////////
    // disabled user
    ///////////////////
    public void testDisabledUser() {
        try {
            createSecureContext(ctx, "disabled", "disabled");
            fail("Expected DisabledException.");
        } catch (DisabledException e) {
            // do nothing.
        }
    }

    ///////////////////
    // unknown user
    ///////////////////
    public void testUnknownUser() {
        try {
            createSecureContext(ctx, "unknown", "unknown");
            fail("Expected BadCredentialsException.");
        } catch (BadCredentialsException e) {
            // do nothing.
        }
    }

}

In conclusion, once I created my own example the ideas and classes used by ACEGI became quite clear and easy to use. I will definitely look to ACEGI in future applications.

Tuesday, October 25, 2005

ACEGI: An Example of When to Use AffirmativeBased (instead of UnanimousBased) Voting when Controlling Access to Methods

In this example I have a simple interface, defined below, which needs role-based access control.

public interface IBean {
  Workers and Managers can get the value.
  public String getValue();
  Only Managers can set the value.
  public void setValue(String _value);
}

The access control is specified via a security interceptor like this:

  <bean id='securityInterceptor' class='...MethodSecurityInterceptor'>
    ...
    <property name='objectDefinitionSource'>
      <value>
        com.affy.IBean.getValue=ROLE_WORKER,ROLE_MANAGER
        com.affy.IBean.setValue=ROLE_MANAGER
      </value>
    </property>
</bean>

Since the the getValue method has more than one role associated with it, the type of voter used as the accessDecisionManager bean is important. If you choose UnanimousBased then the user must have both ROLE_WORKER and ROLE_MANAGER roles which is probably not what your security officer wants.

Using the AffirmativeBased voter means that the user only needs one of the roles to be able to execute the getValue method.

Friday, October 21, 2005

How Do I Create a Private Bean Using ACEGI?

Following the directions in the Spring In Action book, I created a small application (only three Java files and two configuration files) so that I could experiment with method-level access control using ACEGI.

However, I continously saw a mysterious message in my log:

Public object - authentication not attempted

So naturally I started looking around the Internet for information about creating private objects. Sadly, there was none. Being stubborn, I downloaded the ACEGI source code to start poking around.

After an hour or so of adding logging messages and tracing the code, I saw the following message in my logging of the MethodDefinitionMap.lookupAttributes method:

...MethodDefinitionMap; this: {...public java.lang.String
com.affy.BeanA.getValue()=[ROLE_FIELD_OPS, ROLE_DIRECTORY,
ROLE_PRESIDENT]}

...MethodDefinitionMap; method: public abstract java.lang.String
com.affy.IBeanA.getValue()

At first I thought the problem lay in the abstract keyword. But then I realized that my Spring configuration file contained:

  <bean id='securityInterceptor' class='...MethodSecurityInterceptor'>
    ...
    <property name='objectDefinitionSource'>
      <value>
        com.affy.BeanA.setValue=ROLE_PRESIDENT
        com.affy.BeanA.getValue=ROLE_FIELD_OPS,ROLE_DIRECTORY,ROLE_PRESIDENT
      </value>
    </property>
 </bean>

The object definition specified the BeanA implementation instead of the Interface. Once I changed to use com.affy.IBean I saw the following message:

...AbstractSecurityInterceptor - Secure object: invocation: method
'setValue', arguments [FOOBAR]; target is of class [com.affy.BeanA];
ConfigAttributes: [ROLE_PRESIDENT]

Now the newly created object was private!

CONCLUSION Use Interfaces in the objectDefinitionSource specification.

Wednesday, October 19, 2005

NTEN One Day Conference: Answering the call: Lessons learned from the Global ICT responses

I spent this past Monday at a one-day conference devoted to how Information Technology responded to the humanitarian responses of the past year (Katrina, the Tsunami, Darfur, and Afghanistan). For me, the best aspect of the conference was listening to the success stories. One memorable story involves using PDAs to collect information which could be easily collected by the main office.

I re-learned that field conditions are vastly inferior to the conditions under which applications are developed. It's extremely difficult to get *any* kind of staff in locations such as Darfur and even when staff is hired, turnover is very high which pushing training costs higher.

As expected, Data Sharing and Data Standards are a big concern. Yahoo is starting to analyze the existing standards with an eye towards how they can be used for First Responders and other aid workers.

There are fledging open-source applications that are being written (such as Sahana at http://sahana.sourceforge.net/). Plone (http://plone.org/) is being used as a content-management system by several organizations.

In conclusion, at $150 for the day the conference was well worth my time. I highly suggest attending such a conference just to get exposed to how another set of people (non-profit, disaster response teams) do their work.