Brian's Waste of Time

Mon, 22 Dec 2003

Method-Invocation Level ACL Style Security via Interception

Checked in (and pushed a snapshot) of the interception library which Chris Nokleberg and I are working on. I started to put together an iptables style method invocation filter based upon actor-rule filtering. Using just interception, as compared to full AOP via AspectJ or whatnot doesn't allow access to the calling context, so we have to cheat a little bit and require registering the actor with the rule broker -- but it is done as a ThreadLocal so as to be easily used in a servlet or other J2EE environment.

Right now there are two levels of filtering through the security filter and broker. The first is a very course grained filtering, which is also much more efficient, based solely on static invocation information -- this is what you have the SecurityInterceptor match on:


InterceptionBroker broker = new InterceptionBroker();
SecurityInterceptor intercept = new SecurityInterceptor();
broker.addInterceptor(intercept, new NamedSignature("execute"));

The code above tells the interception broker that we we want to intercept and run through the security tests on any method named "execute" -- sudh as would be used on a typical Command style web application. If the method signature doesn't match the test (method name is "execute") than it isn't intrercepted and is allowed to continue.

The next level of filtering is done at invocation time, so is slower. To use this we need to register a SecurityBroker with the SecurityInterceptor


SecurityBroker sec = new SecurityBroker();
intercept.setSecurityBroker(sec);

sec.appendRule(new Rule()
{
    public boolean allow(InvocationContext ctx)
    {
        User user = (User)ctx.getActor();
        if (!user.isAuthenticated()) return false;
        else return true;    
    }
});

This sets a rule that makes sure the actor is authenticated.

All of this is fun, but until we see it used it isn't all that useful, so here is an example of using it. This is a quick-and-dirty Struts Action which delegates to a more flexible Command


package org.skife.kim.struts.tools;

import org.skife.intercept.interceptors.security.Actor;
import org.skife.intercept.interceptors.security.SecurityBroker;
import org.skife.intercept.interceptors.security.AccessError;
import org.skife.intercept.interceptors.security.Rules;
import org.skife.intercept.interceptors.security.Rule;
import org.skife.intercept.interceptors.security.InvocationContext;
import org.skife.kim.model.User;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMessage;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DelegatingAction extends Action
{
    private static final CommandFactory factory;
    private static final SecurityBroker broker;
    static
    {
        broker = new SecurityBroker();
        broker.appendRule(new Rule()
        {
            public boolean allow(InvocationContext ctx)
            {
                User user = (User) ctx.getActor();
                if (user.getFirstName().equalsIgnoreCase("George"))
                {
                    return false;
                }
                return true;
            }
        });
        broker.appendRule(Rules.ALLOW);
        factory = new CommandFactory(broker);
    }

    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws Exception
    {
        String cname = mapping.getParameter();
        Actor actor = (Actor) request.getSession().getAttribute("current-actor");
        if (actor == null) return mapping.findForward("login");
        broker.setActor(actor);
        Command comm = factory.build(cname);
        BeanUtils.copyProperties(comm, form);
        try
        {
            return mapping.findForward(comm.execute());
        }
        catch (AccessError e)
        {
            ActionErrors errors = new ActionErrors();
            errors.add("general", new ActionMessage(e.getMessage()));
            this.saveErrors(request, errors);
            return mapping.findForward("SecurityError");
        }
    }
}

The CommandFactory does the fun work:


package org.skife.kim.struts.tools;

import org.skife.intercept.InterceptionBroker;
import org.skife.intercept.signatures.NamedSignature;
import org.skife.intercept.interceptors.security.SecurityInterceptor;
import org.skife.intercept.interceptors.security.SecurityBroker;

import java.io.IOException;
import java.util.Properties;

public class CommandFactory
{
    private Properties registry;
    private InterceptionBroker broker;

    public CommandFactory(SecurityBroker sec)
    {
        broker = new InterceptionBroker();
        broker.addInterceptor(new SecurityInterceptor(sec), 
                              new NamedSignature("execute"));
        registry = new Properties();
        try
        {
            registry.load(CommandFactory.class.getClassLoader()
                    .getResourceAsStream("commands.properties"));
        }
        catch (IOException e)
        {
            throw new RuntimeException("Unable to load commands.properties", e);
        }
    }

    public Command build(String name)
    {
        String cname = registry.getProperty(name);
        try
        {
            return (Command)broker.wrap(Class.forName(cname).newInstance());
        }
        catch (Exception e)
        {
            throw new RuntimeException("Error building Command: " + name, e);
        }
    }
}

The security broker is configured in a static initialization block in this action. In a real application it would be done in a seperate policy configurator, but this is just top demonstrate. FYI, the rules established in this broker block "George" from doing anything, but allow anyone else to execute any command they want to. Not realistic, but then this is just a sample application ;-)

Right now there isn't much in the way of sophisticated security rule handling. There is a single chain that is executed for everyone. It will be easy to chaneg this to support iptables style chains and jumping, or real rules systems, or delegating back to JAAS.

Right now I am wanting to rethink some of the filtering. I would like to make the static rule elements filter via the InterceptionBroker as that is much faster than executing the whole chain (until failure anyway) every time. I also need to add short-circuting for allowing progression as well as denying. The boolean return probably won't last as it really needs a PAM style "sufficient" to short circuit for authenticated admins etc.

0 writebacks [/src/java/commons-intercept] permanent link