001 /* 002 * Copyright 2002-2005 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package org.springframework.richclient.security.support; 017 018 import java.lang.ref.WeakReference; 019 import java.lang.reflect.InvocationTargetException; 020 import java.lang.reflect.Method; 021 import java.util.ArrayList; 022 import java.util.HashSet; 023 import java.util.Iterator; 024 import java.util.List; 025 026 import org.apache.commons.logging.Log; 027 import org.apache.commons.logging.LogFactory; 028 import org.springframework.beans.factory.InitializingBean; 029 import org.springframework.richclient.core.Authorizable; 030 import org.springframework.richclient.security.SecurityController; 031 import org.springframework.security.AccessDecisionManager; 032 import org.springframework.security.AccessDeniedException; 033 import org.springframework.security.Authentication; 034 import org.springframework.security.ConfigAttributeDefinition; 035 import org.springframework.util.Assert; 036 import org.springframework.util.StringUtils; 037 038 /** 039 * Abstract implementation of a security controller. Derived classes are responsible for 040 * providing the {@link ConfigAttributeDefinition} and any secured object that will be 041 * used by the decision manager to make the decision to authorize the controlled objects. 042 * <p> 043 * This class uses weak references to track the the controlled objects, so they can be 044 * GCed as needed. 045 * <p> 046 * If a subclass provides a new post-processor action, then it needs to call 047 * {@link #registerPostProcessorAction(String)} during construction and it must override 048 * {@link #doPostProcessorAction(String, Object, boolean)}. It is <b>critical</b> that 049 * the overridden doPostProcessorAction method call 050 * <code>super.doPostProcessorAction</code> for any action id it does not directly 051 * handle. 052 * <p> 053 * This base class provides the following post-processor actions: 054 * <p> 055 * <b>visibleTracksAuthorized</b> - if the controlled object has a 056 * <code>setVisible(boolean)</code> method then it is called with the authorized value. 057 * Thus, if the object is not authorized, it will have <code>setVisible(false)</code> 058 * called on it. 059 * 060 * @author Larry Streepy 061 * @see #getSecuredObject() 062 * @see #getConfigAttributeDefinition(Object) 063 * 064 */ 065 public abstract class AbstractSecurityController implements SecurityController, InitializingBean { 066 067 private final Log logger = LogFactory.getLog( getClass() ); 068 069 /** The list of objects that we are controlling. */ 070 private List controlledObjects = new ArrayList(); 071 072 /** The AccessDecisionManager used to make the "authorize" decision. */ 073 private AccessDecisionManager accessDecisionManager; 074 075 /** Last known authentication token. */ 076 private Authentication lastAuthentication = null; 077 078 /** All registered post-processor action ids. */ 079 private HashSet postProcessorActionIds = new HashSet(); 080 081 /** Comma-separated list of post-processor actions to run. */ 082 private String postProcessorActionsToRun = ""; 083 084 public static final String VISIBLE_TRACKS_AUTHORIZED_ACTION = "visibleTracksAuthorized"; 085 086 /** 087 * Constructor. 088 */ 089 protected AbstractSecurityController() { 090 registerPostProcessorAction( VISIBLE_TRACKS_AUTHORIZED_ACTION ); 091 } 092 093 /** 094 * Get the secured object on which we are making the authorization decision. This may 095 * be null if no specific object is to be considered in the decision. 096 * @return secured object 097 */ 098 protected abstract Object getSecuredObject(); 099 100 /** 101 * Get the ConfigAttributeDefinition for the secured object. This will provide the 102 * authorization information to the access decision manager. 103 * @param securedObject Secured object for whom the config attribute definition is to 104 * be rretrieved. This may be null. 105 * @return attribute definition for the provided secured object 106 */ 107 protected abstract ConfigAttributeDefinition getConfigAttributeDefinition(Object securedObject); 108 109 /** 110 * Set the list of post-processor actions to be run. This must be a comma-separated 111 * list of action names. 112 * @param actions Comma-separated list of post-processor action names 113 */ 114 public void setPostProcessorActionsToRun(String actions) { 115 postProcessorActionsToRun = actions; 116 } 117 118 /** 119 * Get the list of post-processor actions to run. 120 * @return Comma-separated list of post-processor action names 121 */ 122 public String getPostProcessorActionsToRun() { 123 return postProcessorActionsToRun; 124 } 125 126 /** 127 * Register a post-processor action. The action id specified must not conflict with 128 * any other action registered. Subclasses that provide additional post-processor 129 * actions MUST call this method to register them. 130 * @param actionId Id of post-processor action to register 131 */ 132 protected void registerPostProcessorAction(String actionId) { 133 if( postProcessorActionIds.contains( actionId ) ) { 134 throw new IllegalArgumentException( "Post-processor Action '" + actionId + "' is already registered" ); 135 } 136 postProcessorActionIds.add( actionId ); 137 } 138 139 /** 140 * The authentication token for the current user has changed. Update all our 141 * controlled objects accordingly. 142 * @param authentication now in effect, may be null 143 */ 144 public void setAuthenticationToken(Authentication authentication) { 145 setLastAuthentication( authentication ); // Keep it for later 146 runAuthorization(); 147 } 148 149 /** 150 * Update the authorization of all controlled objects. 151 */ 152 protected void runAuthorization() { 153 boolean authorize = shouldAuthorize( getLastAuthentication() ); 154 155 // Install the decision 156 for( Iterator iter = controlledObjects.iterator(); iter.hasNext(); ) { 157 WeakReference ref = (WeakReference) iter.next(); 158 159 Authorizable controlledObject = (Authorizable) ref.get(); 160 if( controlledObject == null ) { 161 // Has been GCed, remove from our list 162 iter.remove(); 163 } else { 164 updateControlledObject( controlledObject, authorize ); 165 } 166 } 167 } 168 169 /** 170 * Update a controlled object based on the given authorization state. 171 * @param controlledObject Object being controlled 172 * @param authorized state that has been installed on controlledObject 173 */ 174 protected void updateControlledObject(Authorizable controlledObject, boolean authorized) { 175 if( logger.isDebugEnabled() ) { 176 logger.debug( "setAuthorized( " + authorized + ") on: " + controlledObject ); 177 } 178 controlledObject.setAuthorized( authorized ); 179 runPostProcessorActions( controlledObject, authorized ); 180 } 181 182 /** 183 * Run all the requested post-processor actions. 184 * @param controlledObject Object being controlled 185 * @param authorized state that has been installed on controlledObject 186 */ 187 protected void runPostProcessorActions(Object controlledObject, boolean authorized) { 188 String actions = getPostProcessorActionsToRun(); 189 if( logger.isDebugEnabled() ) { 190 logger.debug( "Run post-processors actions: " + actions ); 191 } 192 193 String[] actionIds = StringUtils.commaDelimitedListToStringArray( actions ); 194 for( int i = 0; i < actionIds.length; i++ ) { 195 doPostProcessorAction( actionIds[i], controlledObject, authorized ); 196 } 197 } 198 199 /** 200 * Post-process a controlled object after its authorization state has been updated. 201 * Subclasses that override this method MUST ensure that this method is called id they 202 * do not process the given action id. 203 * 204 * @param actionId Id of the post-processor action to run 205 * @param controlledObject Object being controlled 206 * @param authorized state that has been installed on controlledObject 207 */ 208 protected void doPostProcessorAction(String actionId, Object controlledObject, boolean authorized) { 209 if( VISIBLE_TRACKS_AUTHORIZED_ACTION.equals( actionId ) ) { 210 setVisibilityOnControlledObject( controlledObject, authorized ); 211 } 212 } 213 214 /** 215 * Set the visible property on a controlled action according to the provided 216 * authorization. 217 */ 218 private void setVisibilityOnControlledObject(Object controlledObject, boolean authorized) { 219 try { 220 Method method = controlledObject.getClass().getMethod( "setVisible", new Class[] { boolean.class } ); 221 method.invoke( controlledObject, new Object[] { new Boolean( authorized ) } ); 222 } catch( NoSuchMethodException ignored ) { 223 System.out.println( "NO setVisible method on object: " + controlledObject ); 224 // No method to call, so nothing to do 225 } catch( IllegalAccessException ignored ) { 226 logger.error( "Could not call setVisible", ignored ); 227 } catch( InvocationTargetException ignored ) { 228 logger.error( "Could not call setVisible", ignored ); 229 } 230 } 231 232 /** 233 * Determine if our controlled objects should be authorized based on the provided 234 * authentication token. 235 * @param authentication token 236 * @return true if should authorize 237 */ 238 protected boolean shouldAuthorize(Authentication authentication) { 239 Assert.state( getAccessDecisionManager() != null, "The AccessDecisionManager can not be null!" ); 240 boolean authorize = false; 241 try { 242 if( authentication != null ) { 243 Object securedObject = getSecuredObject(); 244 ConfigAttributeDefinition cad = getConfigAttributeDefinition( securedObject ); 245 getAccessDecisionManager().decide( authentication, getSecuredObject(), cad ); 246 authorize = true; 247 } 248 } catch( AccessDeniedException e ) { 249 // This means the secured objects should not be authorized 250 } 251 return authorize; 252 } 253 254 /** 255 * Set the access decision manager to use 256 * @param accessDecisionManager 257 */ 258 public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) { 259 this.accessDecisionManager = accessDecisionManager; 260 } 261 262 /** 263 * Get the access decision manager in use 264 */ 265 public AccessDecisionManager getAccessDecisionManager() { 266 return accessDecisionManager; 267 } 268 269 /** 270 * Set the objects that are to be controlled. Only beans that implement the 271 * {@link Authorized} interface are processed. 272 * @param secured List of objects to control 273 */ 274 public void setControlledObjects(List secured) { 275 controlledObjects = new ArrayList( secured.size() ); 276 277 // Convert to weak references and validate the object types 278 for( Iterator iter = secured.iterator(); iter.hasNext(); ) { 279 Object o = iter.next(); 280 281 // Ensure that we got something we can control 282 if( !(o instanceof Authorizable) ) { 283 throw new IllegalArgumentException( "Controlled object must implement Authorizable, got " 284 + o.getClass() ); 285 } 286 addAndPrepareControlledObject( (Authorizable) o ); 287 } 288 } 289 290 /** 291 * Add an object to our controlled set. 292 * @param object to control 293 */ 294 public void addControlledObject(Authorizable object) { 295 addAndPrepareControlledObject( object ); 296 } 297 298 /** 299 * Add a new object to the list of controlled objects. Install our last known 300 * authorization decision so newly created objects will reflect the current security 301 * state. 302 * @param controlledObject to add 303 */ 304 private void addAndPrepareControlledObject(Authorizable controlledObject) { 305 controlledObjects.add( new WeakReference( controlledObject ) ); 306 307 // Properly configure the new object 308 boolean authorize = shouldAuthorize( getLastAuthentication() ); 309 updateControlledObject( controlledObject, authorize ); 310 } 311 312 /** 313 * Remove an object from our controlled set. 314 * @param object to remove 315 * @return object removed or null if not found 316 */ 317 public Object removeControlledObject(Authorizable object) { 318 Object removed = null; 319 320 for( Iterator iter = controlledObjects.iterator(); iter.hasNext(); ) { 321 WeakReference ref = (WeakReference) iter.next(); 322 323 Authorizable controlledObject = (Authorizable) ref.get(); 324 if( controlledObject == null ) { 325 // Has been GCed, remove from our list 326 iter.remove(); 327 } else if( controlledObject.equals( object ) ) { 328 removed = controlledObject; 329 iter.remove(); 330 } 331 } 332 return removed; 333 } 334 335 protected void setLastAuthentication(Authentication authentication) { 336 lastAuthentication = authentication; 337 } 338 339 protected Authentication getLastAuthentication() { 340 return lastAuthentication; 341 } 342 343 /** 344 * Validate our configuration. 345 * @throws Exception 346 */ 347 public void afterPropertiesSet() throws Exception { 348 // Ensure that all post-processors requested are registered 349 String[] actions = StringUtils.commaDelimitedListToStringArray( getPostProcessorActionsToRun() ); 350 for( int i = 0; i < actions.length; i++ ) { 351 if( !postProcessorActionIds.contains( actions[i] ) ) { 352 throw new IllegalArgumentException( "Requested post-processor action '" + actions[i] 353 + "' is not registered." ); 354 } 355 } 356 } 357 }