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 }