Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: net/sf/acegisecurity/intercept/AbstractSecurityInterceptor.java


1   /* Copyright 2004, 2005 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package net.sf.acegisecurity.intercept;
17  
18  import net.sf.acegisecurity.AccessDecisionManager;
19  import net.sf.acegisecurity.AccessDeniedException;
20  import net.sf.acegisecurity.AfterInvocationManager;
21  import net.sf.acegisecurity.Authentication;
22  import net.sf.acegisecurity.AuthenticationCredentialsNotFoundException;
23  import net.sf.acegisecurity.AuthenticationException;
24  import net.sf.acegisecurity.AuthenticationManager;
25  import net.sf.acegisecurity.ConfigAttribute;
26  import net.sf.acegisecurity.ConfigAttributeDefinition;
27  import net.sf.acegisecurity.RunAsManager;
28  import net.sf.acegisecurity.context.Context;
29  import net.sf.acegisecurity.context.ContextHolder;
30  import net.sf.acegisecurity.context.security.SecureContext;
31  import net.sf.acegisecurity.intercept.event.AuthenticationCredentialsNotFoundEvent;
32  import net.sf.acegisecurity.intercept.event.AuthenticationFailureEvent;
33  import net.sf.acegisecurity.intercept.event.AuthorizationFailureEvent;
34  import net.sf.acegisecurity.intercept.event.AuthorizedEvent;
35  import net.sf.acegisecurity.intercept.event.PublicInvocationEvent;
36  import net.sf.acegisecurity.runas.NullRunAsManager;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  
41  import org.springframework.beans.BeansException;
42  import org.springframework.beans.factory.InitializingBean;
43  
44  import org.springframework.context.ApplicationContext;
45  import org.springframework.context.ApplicationContextAware;
46  import org.springframework.util.Assert;
47  
48  import java.util.HashSet;
49  import java.util.Iterator;
50  import java.util.Set;
51  
52  
53  /**
54   * Abstract class that implements security interception for secure objects.
55   * 
56   * <P>
57   * The <code>AbstractSecurityInterceptor</code> will ensure the proper startup
58   * configuration of the security interceptor. It will also implement the
59   * proper handling of secure object invocations, being:
60   * 
61   * <ol>
62   * <li>
63   * Extract the {@link SecureContext} from the {@link ContextHolder}, handling
64   * any errors such as invalid or <code>null</code> objects.
65   * </li>
66   * <li>
67   * Obtain the {@link Authentication} object from the extracted
68   * <code>SecureContext</code>.
69   * </li>
70   * <li>
71   * Determine if the request relates to a secured or public invocation by
72   * looking up the secure object request against the {@link
73   * ObjectDefinitionSource}.
74   * </li>
75   * <li>
76   * For an invocation that is secured (there is a
77   * <code>ConfigAttributeDefinition</code> for the secure object invocation):
78   * 
79   * <ol type="a">
80   * <li>
81   * Authenticate the request against the configured {@link
82   * AuthenticationManager}, replacing the <code>Authentication</code> object on
83   * the <code>ContextHolder</code> with the returned value.
84   * </li>
85   * <li>
86   * Authorize the request against the configured {@link AccessDecisionManager}.
87   * </li>
88   * <li>
89   * Perform any run-as replacement via the configured {@link RunAsManager}.
90   * </li>
91   * <li>
92   * Pass control back to the concrete subclass, which will actually proceed with
93   * executing the object. A {@link InterceptorStatusToken} is returned so that
94   * after the subclass has finished proceeding with  execution of the object,
95   * its finally clause can ensure the <code>AbstractSecurityInterceptor</code>
96   * is re-called and tidies up correctly.
97   * </li>
98   * <li>
99   * The concrete subclass will re-call the
100  * <code>AbstractSecurityInterceptor</code> via the {@link
101  * #afterInvocation(InterceptorStatusToken, Object)} method.
102  * </li>
103  * <li>
104  * If the <code>RunAsManager</code> replaced the <code>Authentication</code>
105  * object, return the <code>ContextHolder</code> to the object that existed
106  * after the call to <code>AuthenticationManager</code>.
107  * </li>
108  * <li>
109  * If an <code>AfterInvocationManager</code> is defined, invoke the invocation
110  * manager and allow it to replace the object due to be returned to the
111  * caller.
112  * </li>
113  * </ol>
114  * 
115  * </li>
116  * <li>
117  * For an invocation that is public (there is no
118  * <code>ConfigAttributeDefinition</code> for the secure object invocation):
119  * 
120  * <ol type="a">
121  * <li>
122  * If the <code>ContextHolder</code> contains a <code>SecureContext</code>, set
123  * the <code>isAuthenticated</code> flag on the <code>Authentication</code>
124  * object to false.
125  * </li>
126  * <li>
127  * As described above, the concrete subclass will be returned an
128  * <code>InterceptorStatusToken</code> which is subsequently re-presented to
129  * the <code>AbstractSecurityInterceptor</code> after the secure object has
130  * been executed. The <code>AbstractSecurityInterceptor</code> will take no
131  * further action when its {@link #afterInvocation(InterceptorStatusToken,
132  * Object)} is called.
133  * </li>
134  * </ol>
135  * 
136  * </li>
137  * <li>
138  * Control again returns to the concrete subclass, along with the
139  * <code>Object</code> that should be returned to the caller.  The subclass
140  * will then return that  result or exception to the original caller.
141  * </li>
142  * </ol>
143  * </p>
144  *
145  * @author Ben Alex
146  * @version $Id: AbstractSecurityInterceptor.java,v 1.14 2005/04/15 01:21:34 luke_t Exp $
147  */
148 public abstract class AbstractSecurityInterceptor implements InitializingBean,
149     ApplicationContextAware {
150     //~ Static fields/initializers =============================================
151 
152     protected static final Log logger = LogFactory.getLog(AbstractSecurityInterceptor.class);
153 
154     //~ Instance fields ========================================================
155 
156     private AccessDecisionManager accessDecisionManager;
157     private AfterInvocationManager afterInvocationManager;
158     private ApplicationContext context;
159     private AuthenticationManager authenticationManager;
160     private RunAsManager runAsManager = new NullRunAsManager();
161     private boolean validateConfigAttributes = true;
162 
163     //~ Methods ================================================================
164 
165     public void setAfterInvocationManager(
166         AfterInvocationManager afterInvocationManager) {
167         this.afterInvocationManager = afterInvocationManager;
168     }
169 
170     public AfterInvocationManager getAfterInvocationManager() {
171         return afterInvocationManager;
172     }
173 
174     public void setApplicationContext(ApplicationContext applicationContext)
175         throws BeansException {
176         this.context = applicationContext;
177     }
178 
179     /**
180      * Indicates the type of secure objects the subclass will be presenting to
181      * the abstract parent for processing. This is used to ensure
182      * collaborators wired to the <code>AbstractSecurityInterceptor</code> all
183      * support the indicated secure object class.
184      *
185      * @return the type of secure object the subclass provides services for
186      */
187     public abstract Class getSecureObjectClass();
188 
189     public abstract ObjectDefinitionSource obtainObjectDefinitionSource();
190 
191     public void setAccessDecisionManager(
192         AccessDecisionManager accessDecisionManager) {
193         this.accessDecisionManager = accessDecisionManager;
194     }
195 
196     public AccessDecisionManager getAccessDecisionManager() {
197         return accessDecisionManager;
198     }
199 
200     public void setAuthenticationManager(AuthenticationManager newManager) {
201         this.authenticationManager = newManager;
202     }
203 
204     public AuthenticationManager getAuthenticationManager() {
205         return this.authenticationManager;
206     }
207 
208     public void setRunAsManager(RunAsManager runAsManager) {
209         this.runAsManager = runAsManager;
210     }
211 
212     public RunAsManager getRunAsManager() {
213         return runAsManager;
214     }
215 
216     public void setValidateConfigAttributes(boolean validateConfigAttributes) {
217         this.validateConfigAttributes = validateConfigAttributes;
218     }
219 
220     public boolean isValidateConfigAttributes() {
221         return validateConfigAttributes;
222     }
223 
224     public void afterPropertiesSet() throws Exception {
225         Assert.notNull(getSecureObjectClass(), "Subclass must provide a non-null response to getSecureObjectClass()");
226 
227         Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
228 
229         Assert.notNull(this.accessDecisionManager, "An AccessDecisionManager is required");
230 
231         Assert.notNull(this.runAsManager, "A RunAsManager is required");
232 
233         Assert.notNull(this.obtainObjectDefinitionSource(), "An ObjectDefinitionSource is required");
234 
235         if (!this.obtainObjectDefinitionSource().supports(getSecureObjectClass())) {
236             throw new IllegalArgumentException("ObjectDefinitionSource does not support secure object class: "
237                     + getSecureObjectClass());
238         }
239 
240         if (!this.runAsManager.supports(getSecureObjectClass())) {
241             throw new IllegalArgumentException("RunAsManager does not support secure object class: "
242                     + getSecureObjectClass());
243         }
244 
245         if (!this.accessDecisionManager.supports(getSecureObjectClass())) {
246             throw new IllegalArgumentException("AccessDecisionManager does not support secure object class: "
247                     + getSecureObjectClass());
248         }
249 
250         if ((this.afterInvocationManager != null)
251                 && !this.afterInvocationManager.supports(getSecureObjectClass())) {
252             throw new IllegalArgumentException("AfterInvocationManager does not support secure object class: "
253                     + getSecureObjectClass());
254         }
255 
256         if (this.validateConfigAttributes) {
257             Iterator iter = this.obtainObjectDefinitionSource()
258                     .getConfigAttributeDefinitions();
259 
260             if (iter == null) {
261                 if (logger.isWarnEnabled()) {
262                     logger.warn("Could not validate configuration attributes as the MethodDefinitionSource did not return a ConfigAttributeDefinition Iterator");
263                 }
264             } else {
265                 Set set = new HashSet();
266 
267                 while (iter.hasNext()) {
268                     ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter
269                             .next();
270                     Iterator attributes = def.getConfigAttributes();
271 
272                     while (attributes.hasNext()) {
273                         ConfigAttribute attr = (ConfigAttribute) attributes
274                                 .next();
275 
276                         if (!this.runAsManager.supports(attr)
277                                 && !this.accessDecisionManager.supports(attr)
278                                 && ((this.afterInvocationManager == null)
279                                 || !this.afterInvocationManager.supports(attr))) {
280                             set.add(attr);
281                         }
282                     }
283                 }
284 
285                 if (set.size() == 0) {
286                     if (logger.isInfoEnabled()) {
287                         logger.info("Validated configuration attributes");
288                     }
289                 } else {
290                     throw new IllegalArgumentException("Unsupported configuration attributes: "
291                             + set.toString());
292                 }
293             }
294         }
295     }
296 
297     /**
298      * Completes the work of the <code>AbstractSecurityInterceptor</code> after
299      * the secure object invocation has been complete
300      *
301      * @param token as returned by the {@link #beforeInvocation(Object)}}
302      *        method
303      * @param returnedObject any object returned from the secure object
304      *        invocation (may be<code>null</code>)
305      *
306      * @return the object the secure object invocation should ultimately return
307      *         to its caller (may be <code>null</code>)
308      */
309     protected Object afterInvocation(InterceptorStatusToken token,
310         Object returnedObject) {
311         if (token == null) {
312             // public object
313             return returnedObject;
314         }
315 
316         if (token.isContextHolderRefreshRequired()) {
317             if (logger.isDebugEnabled()) {
318                 logger.debug("Reverting to original Authentication: "
319                     + token.getAuthentication().toString());
320             }
321 
322             SecureContext secureContext = (SecureContext) ContextHolder
323                 .getContext();
324             secureContext.setAuthentication(token.getAuthentication());
325             ContextHolder.setContext(secureContext);
326         }
327 
328         if (afterInvocationManager != null) {
329             returnedObject = afterInvocationManager.decide(token
330                     .getAuthentication(), token.getSecureObject(),
331                     token.getAttr(), returnedObject);
332         }
333 
334         return returnedObject;
335     }
336 
337     protected InterceptorStatusToken beforeInvocation(Object object) {
338         Assert.notNull(object, "Object was null");
339         Assert.isTrue(getSecureObjectClass().isAssignableFrom(object.getClass()), "Security invocation attempted for object " + object
340                     + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
341                     + getSecureObjectClass());
342 
343         ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource()
344                 .getAttributes(object);
345 
346         if (attr != null) {
347             if (logger.isDebugEnabled()) {
348                 logger.debug("Secure object: " + object.toString()
349                         + "; ConfigAttributes: " + attr.toString());
350             }
351 
352             // Ensure ContextHolder presents a populated SecureContext
353             if ((ContextHolder.getContext() == null)
354                     || !(ContextHolder.getContext() instanceof SecureContext)) {
355                 credentialsNotFound("A valid SecureContext was not provided in the RequestContext",
356                         object, attr);
357             }
358 
359             SecureContext context = (SecureContext) ContextHolder.getContext();
360 
361             // We check for just the property we're interested in (we do
362             // not call Context.validate() like the ContextInterceptor)
363             if (context.getAuthentication() == null) {
364                 credentialsNotFound("Authentication credentials were not found in the SecureContext",
365                         object, attr);
366             }
367 
368             // Attempt authentication
369             Authentication authenticated;
370 
371             try {
372                 authenticated = this.authenticationManager.authenticate(context
373                         .getAuthentication());
374             } catch (AuthenticationException authenticationException) {
375                 AuthenticationFailureEvent event = new AuthenticationFailureEvent(object,
376                         attr, context.getAuthentication(),
377                         authenticationException);
378                 this.context.publishEvent(event);
379 
380                 throw authenticationException;
381             }
382 
383             authenticated.setAuthenticated(true);
384 
385             if (logger.isDebugEnabled()) {
386                 logger.debug("Authenticated: " + authenticated.toString());
387             }
388 
389             context.setAuthentication(authenticated);
390             ContextHolder.setContext((Context) context);
391 
392             // Attempt authorization
393             try {
394                 this.accessDecisionManager.decide(authenticated, object, attr);
395             } catch (AccessDeniedException accessDeniedException) {
396                 AuthorizationFailureEvent event = new AuthorizationFailureEvent(object,
397                         attr, authenticated, accessDeniedException);
398                 this.context.publishEvent(event);
399 
400                 throw accessDeniedException;
401             }
402 
403             if (logger.isDebugEnabled()) {
404                 logger.debug("Authorization successful");
405             }
406 
407             AuthorizedEvent event = new AuthorizedEvent(object, attr,
408                     authenticated);
409             this.context.publishEvent(event);
410 
411             // Attempt to run as a different user
412             Authentication runAs = this.runAsManager.buildRunAs(authenticated,
413                     object, attr);
414 
415             if (runAs == null) {
416                 if (logger.isDebugEnabled()) {
417                     logger.debug("RunAsManager did not change Authentication object");
418                 }
419 
420                 return new InterceptorStatusToken(authenticated, false, attr,
421                         object); // no further work post-invocation
422             } else {
423                 if (logger.isDebugEnabled()) {
424                     logger.debug("Switching to RunAs Authentication: "
425                             + runAs.toString());
426                 }
427 
428                 context.setAuthentication(runAs);
429                 ContextHolder.setContext((Context) context);
430 
431                 return new InterceptorStatusToken(authenticated, true, attr,
432                         object); // revert to token.Authenticated post-invocation
433             }
434         } else {
435             if (logger.isDebugEnabled()) {
436                 logger.debug("Public object - authentication not attempted");
437             }
438 
439             this.context.publishEvent(new PublicInvocationEvent(object));
440 
441             // Set Authentication object (if it exists) to be unauthenticated
442             if ((ContextHolder.getContext() != null)
443                     && ContextHolder.getContext() instanceof SecureContext) {
444                 SecureContext context = (SecureContext) ContextHolder
445                         .getContext();
446 
447                 if (context.getAuthentication() != null) {
448                     if (logger.isDebugEnabled()) {
449                         logger.debug("Authentication object detected and tagged as unauthenticated");
450                     }
451 
452                     Authentication authenticated = context.getAuthentication();
453                     authenticated.setAuthenticated(false);
454                     context.setAuthentication(authenticated);
455                     ContextHolder.setContext((Context) context);
456                 }
457             }
458 
459             return null; // no further work post-invocation
460         }
461     }
462 
463     /**
464      * Helper method which generates an exception containing the passed reason,
465      * and publishes an event to the application context.
466      * 
467      * <P>
468      * Always throws an exception.
469      * </p>
470      *
471      * @param reason to be provided in the exception detail
472      * @param secureObject that was being called
473      * @param configAttribs that were defined for the secureObject
474      */
475     private void credentialsNotFound(String reason, Object secureObject,
476         ConfigAttributeDefinition configAttribs) {
477         AuthenticationCredentialsNotFoundException exception = new AuthenticationCredentialsNotFoundException(reason);
478 
479         AuthenticationCredentialsNotFoundEvent event = new AuthenticationCredentialsNotFoundEvent(secureObject,
480                 configAttribs, exception);
481         this.context.publishEvent(event);
482 
483         throw exception;
484     }
485 }