Java tutorial
/* * 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."); } } } }