org.springframework.richclient.security.support.AbstractSecurityController.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.richclient.security.support.AbstractSecurityController.java

Source

/*
 * Copyright 2002-2005 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.springframework.richclient.security.support;

import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.richclient.core.Authorizable;
import org.springframework.richclient.security.SecurityController;
import org.springframework.security.AccessDecisionManager;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.Authentication;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * Abstract implementation of a security controller. Derived classes are responsible for
 * providing the {@link ConfigAttributeDefinition} and any secured object that will be
 * used by the decision manager to make the decision to authorize the controlled objects.
 * <p>
 * This class uses weak references to track the the controlled objects, so they can be
 * GCed as needed.
 * <p>
 * If a subclass provides a new post-processor action, then it needs to call
 * {@link #registerPostProcessorAction(String)} during construction and it must override
 * {@link #doPostProcessorAction(String, Object, boolean)}. It is <b>critical</b> that
 * the overridden doPostProcessorAction method call
 * <code>super.doPostProcessorAction</code> for any action id it does not directly
 * handle.
 * <p>
 * This base class provides the following post-processor actions:
 * <p>
 * <b>visibleTracksAuthorized</b> - if the controlled object has a
 * <code>setVisible(boolean)</code> method then it is called with the authorized value.
 * Thus, if the object is not authorized, it will have <code>setVisible(false)</code>
 * called on it.
 * 
 * @author Larry Streepy
 * @see #getSecuredObject()
 * @see #getConfigAttributeDefinition(Object)
 * 
 */
public abstract class AbstractSecurityController implements SecurityController, InitializingBean {

    private final Log logger = LogFactory.getLog(getClass());

    /** The list of objects that we are controlling. */
    private List controlledObjects = new ArrayList();

    /** The AccessDecisionManager used to make the "authorize" decision. */
    private AccessDecisionManager accessDecisionManager;

    /** Last known authentication token. */
    private Authentication lastAuthentication = null;

    /** All registered post-processor action ids. */
    private HashSet postProcessorActionIds = new HashSet();

    /** Comma-separated list of post-processor actions to run. */
    private String postProcessorActionsToRun = "";

    public static final String VISIBLE_TRACKS_AUTHORIZED_ACTION = "visibleTracksAuthorized";

    /**
     * Constructor.
     */
    protected AbstractSecurityController() {
        registerPostProcessorAction(VISIBLE_TRACKS_AUTHORIZED_ACTION);
    }

    /**
     * Get the secured object on which we are making the authorization decision. This may
     * be null if no specific object is to be considered in the decision.
     * @return secured object
     */
    protected abstract Object getSecuredObject();

    /**
     * Get the ConfigAttributeDefinition for the secured object. This will provide the
     * authorization information to the access decision manager.
     * @param securedObject Secured object for whom the config attribute definition is to
     *            be rretrieved. This may be null.
     * @return attribute definition for the provided secured object
     */
    protected abstract ConfigAttributeDefinition getConfigAttributeDefinition(Object securedObject);

    /**
     * Set the list of post-processor actions to be run. This must be a comma-separated
     * list of action names.
     * @param actions Comma-separated list of post-processor action names
     */
    public void setPostProcessorActionsToRun(String actions) {
        postProcessorActionsToRun = actions;
    }

    /**
     * Get the list of post-processor actions to run.
     * @return Comma-separated list of post-processor action names
     */
    public String getPostProcessorActionsToRun() {
        return postProcessorActionsToRun;
    }

    /**
     * Register a post-processor action. The action id specified must not conflict with
     * any other action registered. Subclasses that provide additional post-processor
     * actions MUST call this method to register them.
     * @param actionId Id of post-processor action to register
     */
    protected void registerPostProcessorAction(String actionId) {
        if (postProcessorActionIds.contains(actionId)) {
            throw new IllegalArgumentException("Post-processor Action '" + actionId + "' is already registered");
        }
        postProcessorActionIds.add(actionId);
    }

    /**
     * The authentication token for the current user has changed. Update all our
     * controlled objects accordingly.
     * @param authentication now in effect, may be null
     */
    public void setAuthenticationToken(Authentication authentication) {
        setLastAuthentication(authentication); // Keep it for later
        runAuthorization();
    }

    /**
     * Update the authorization of all controlled objects.
     */
    protected void runAuthorization() {
        boolean authorize = shouldAuthorize(getLastAuthentication());

        // Install the decision
        for (Iterator iter = controlledObjects.iterator(); iter.hasNext();) {
            WeakReference ref = (WeakReference) iter.next();

            Authorizable controlledObject = (Authorizable) ref.get();
            if (controlledObject == null) {
                // Has been GCed, remove from our list
                iter.remove();
            } else {
                updateControlledObject(controlledObject, authorize);
            }
        }
    }

    /**
     * Update a controlled object based on the given authorization state.
     * @param controlledObject Object being controlled
     * @param authorized state that has been installed on controlledObject
     */
    protected void updateControlledObject(Authorizable controlledObject, boolean authorized) {
        if (logger.isDebugEnabled()) {
            logger.debug("setAuthorized( " + authorized + ") on: " + controlledObject);
        }
        controlledObject.setAuthorized(authorized);
        runPostProcessorActions(controlledObject, authorized);
    }

    /**
     * Run all the requested post-processor actions.
     * @param controlledObject Object being controlled
     * @param authorized state that has been installed on controlledObject
     */
    protected void runPostProcessorActions(Object controlledObject, boolean authorized) {
        String actions = getPostProcessorActionsToRun();
        if (logger.isDebugEnabled()) {
            logger.debug("Run post-processors actions: " + actions);
        }

        String[] actionIds = StringUtils.commaDelimitedListToStringArray(actions);
        for (int i = 0; i < actionIds.length; i++) {
            doPostProcessorAction(actionIds[i], controlledObject, authorized);
        }
    }

    /**
     * Post-process a controlled object after its authorization state has been updated.
     * Subclasses that override this method MUST ensure that this method is called id they
     * do not process the given action id.
     * 
     * @param actionId Id of the post-processor action to run
     * @param controlledObject Object being controlled
     * @param authorized state that has been installed on controlledObject
     */
    protected void doPostProcessorAction(String actionId, Object controlledObject, boolean authorized) {
        if (VISIBLE_TRACKS_AUTHORIZED_ACTION.equals(actionId)) {
            setVisibilityOnControlledObject(controlledObject, authorized);
        }
    }

    /**
     * Set the visible property on a controlled action according to the provided
     * authorization.
     */
    private void setVisibilityOnControlledObject(Object controlledObject, boolean authorized) {
        try {
            Method method = controlledObject.getClass().getMethod("setVisible", new Class[] { boolean.class });
            method.invoke(controlledObject, new Object[] { new Boolean(authorized) });
        } catch (NoSuchMethodException ignored) {
            System.out.println("NO setVisible method on object: " + controlledObject);
            // No method to call, so nothing to do
        } catch (IllegalAccessException ignored) {
            logger.error("Could not call setVisible", ignored);
        } catch (InvocationTargetException ignored) {
            logger.error("Could not call setVisible", ignored);
        }
    }

    /**
     * Determine if our controlled objects should be authorized based on the provided
     * authentication token.
     * @param authentication token
     * @return true if should authorize
     */
    protected boolean shouldAuthorize(Authentication authentication) {
        Assert.state(getAccessDecisionManager() != null, "The AccessDecisionManager can not be null!");
        boolean authorize = false;
        try {
            if (authentication != null) {
                Object securedObject = getSecuredObject();
                ConfigAttributeDefinition cad = getConfigAttributeDefinition(securedObject);
                getAccessDecisionManager().decide(authentication, getSecuredObject(), cad);
                authorize = true;
            }
        } catch (AccessDeniedException e) {
            // This means the secured objects should not be authorized
        }
        return authorize;
    }

    /**
     * Set the access decision manager to use
     * @param accessDecisionManager
     */
    public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
        this.accessDecisionManager = accessDecisionManager;
    }

    /**
     * Get the access decision manager in use
     */
    public AccessDecisionManager getAccessDecisionManager() {
        return accessDecisionManager;
    }

    /**
     * Set the objects that are to be controlled. Only beans that implement the
     * {@link Authorized} interface are processed.
     * @param secured List of objects to control
     */
    public void setControlledObjects(List secured) {
        controlledObjects = new ArrayList(secured.size());

        // Convert to weak references and validate the object types
        for (Iterator iter = secured.iterator(); iter.hasNext();) {
            Object o = iter.next();

            // Ensure that we got something we can control
            if (!(o instanceof Authorizable)) {
                throw new IllegalArgumentException(
                        "Controlled object must implement Authorizable, got " + o.getClass());
            }
            addAndPrepareControlledObject((Authorizable) o);
        }
    }

    /**
     * Add an object to our controlled set.
     * @param object to control
     */
    public void addControlledObject(Authorizable object) {
        addAndPrepareControlledObject(object);
    }

    /**
     * Add a new object to the list of controlled objects. Install our last known
     * authorization decision so newly created objects will reflect the current security
     * state.
     * @param controlledObject to add
     */
    private void addAndPrepareControlledObject(Authorizable controlledObject) {
        controlledObjects.add(new WeakReference(controlledObject));

        // Properly configure the new object
        boolean authorize = shouldAuthorize(getLastAuthentication());
        updateControlledObject(controlledObject, authorize);
    }

    /**
     * Remove an object from our controlled set.
     * @param object to remove
     * @return object removed or null if not found
     */
    public Object removeControlledObject(Authorizable object) {
        Object removed = null;

        for (Iterator iter = controlledObjects.iterator(); iter.hasNext();) {
            WeakReference ref = (WeakReference) iter.next();

            Authorizable controlledObject = (Authorizable) ref.get();
            if (controlledObject == null) {
                // Has been GCed, remove from our list
                iter.remove();
            } else if (controlledObject.equals(object)) {
                removed = controlledObject;
                iter.remove();
            }
        }
        return removed;
    }

    protected void setLastAuthentication(Authentication authentication) {
        lastAuthentication = authentication;
    }

    protected Authentication getLastAuthentication() {
        return lastAuthentication;
    }

    /**
     * Validate our configuration.
     * @throws Exception
     */
    public void afterPropertiesSet() throws Exception {
        // Ensure that all post-processors requested are registered
        String[] actions = StringUtils.commaDelimitedListToStringArray(getPostProcessorActionsToRun());
        for (int i = 0; i < actions.length; i++) {
            if (!postProcessorActionIds.contains(actions[i])) {
                throw new IllegalArgumentException(
                        "Requested post-processor action '" + actions[i] + "' is not registered.");
            }
        }
    }
}