From 0e46e5307c9085cd8511a6c2b1db3eb01748b2c1 Mon Sep 17 00:00:00 2001
From: Vishal Puri The
- * AbstractSecurityInterceptor will ensure the proper startup configuration of the security interceptor.
- * It will also implement the proper handling of secure object invocations, being:
- *
- *
- * ConfigAttributeDefinition for the secure
- * object invocation):
- *
- *
- * false, or the {@link #alwaysReauthenticate} is true, authenticate the request
- * against the configured {@link AuthenticationManager}. When authenticated, replace the
- * Authentication object on the SecurityContextHolder with the returned value.AbstractSecurityInterceptor
- * is re-called and tidies up correctly.AbstractSecurityInterceptor via the
- * {@link #afterInvocation(InterceptorStatusToken, Object)} method.RunAsManager replaced the Authentication object, return
- * the SecurityContextHolder to the object that existed after the call to
- * AuthenticationManager.AfterInvocationManager is defined, invoke the invocation manager and
- * allow it to replace the object due to be returned to the caller.ConfigAttributeDefinition for the secure
- * object invocation):
- *
- *
- * InterceptorStatusToken which is subsequently re-presented to the
- * AbstractSecurityInterceptor after the secure object has been executed. The
- * AbstractSecurityInterceptor will take no further action when its {@link
- * #afterInvocation(InterceptorStatusToken, Object)} is called.Object that should be
- * returned to the caller. The subclass will then return that result or exception to the original caller.
+ * The AbstractSecurityInterceptor will ensure the proper startup
+ * configuration of the security interceptor. It will also implement the proper
+ * handling of secure object invocations, being:
+ *
ConfigAttributeDefinition for the secure object invocation):
+ * false, or the {@link #alwaysReauthenticate} is
+ * true, authenticate the request against the configured
+ * {@link AuthenticationManager}. When authenticated, replace the
+ * Authentication object on the
+ * SecurityContextHolder with the returned value.AbstractSecurityInterceptor
+ * is re-called and tidies up correctly.AbstractSecurityInterceptor via the
+ * {@link #afterInvocation(InterceptorStatusToken, Object)} method.RunAsManager replaced the
+ * Authentication object, return the
+ * SecurityContextHolder to the object that existed after the
+ * call to AuthenticationManager.AfterInvocationManager is defined, invoke the
+ * invocation manager and allow it to replace the object due to be returned to
+ * the caller.ConfigAttributeDefinition for the secure object invocation):
+ * InterceptorStatusToken which is subsequently re-presented to
+ * the AbstractSecurityInterceptor after the secure object has
+ * been executed. The AbstractSecurityInterceptor will take no
+ * further action when its {@link #afterInvocation(InterceptorStatusToken,
+ * Object)} is called.Object that should be returned to the caller. The subclass
+ * will then return that result or exception to the original caller.AbstractSecurityInterceptor after the secure object invocation
- * has been complete
- *
- * @param token as returned by the {@link #beforeInvocation(Object)}} method
- * @param returnedObject any object returned from the secure object invocation (may benull)
- *
- * @return the object the secure object invocation should ultimately return to its caller (may be
- * null)
- */
- protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
- if (token == null) {
- // public object
- return returnedObject;
- }
-
- if (token.isContextHolderRefreshRequired()) {
- if (logger.isDebugEnabled()) {
- logger.debug("Reverting to original Authentication: " + token.getAuthentication().toString());
- }
-
- SecurityContextHolder.getContext().setAuthentication(token.getAuthentication());
- }
-
- if (afterInvocationManager != null) {
- // Attempt after invocation handling
- try {
- returnedObject = afterInvocationManager.decide(token.getAuthentication(), token.getSecureObject(),
- token.getAttr(), returnedObject);
- } catch (AccessDeniedException accessDeniedException) {
- AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(),
- token.getAttr(), token.getAuthentication(), accessDeniedException);
- publishEvent(event);
-
- throw accessDeniedException;
- }
- }
-
- return returnedObject;
- }
-
- public void afterPropertiesSet() throws Exception {
- Assert.notNull(getSecureObjectClass(), "Subclass must provide a non-null response to getSecureObjectClass()");
-
- Assert.notNull(this.messages, "A message source must be set");
- Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
-
- Assert.notNull(this.accessDecisionManager, "An AccessDecisionManager is required");
-
- Assert.notNull(this.runAsManager, "A RunAsManager is required");
-
- Assert.notNull(this.obtainObjectDefinitionSource(), "An ObjectDefinitionSource is required");
-
- Assert.isTrue(this.obtainObjectDefinitionSource().supports(getSecureObjectClass()),
- "ObjectDefinitionSource does not support secure object class: " + getSecureObjectClass());
-
- Assert.isTrue(this.runAsManager.supports(getSecureObjectClass()),
- "RunAsManager does not support secure object class: " + getSecureObjectClass());
-
- Assert.isTrue(this.accessDecisionManager.supports(getSecureObjectClass()),
- "AccessDecisionManager does not support secure object class: " + getSecureObjectClass());
-
- if (this.afterInvocationManager != null) {
- Assert.isTrue(this.afterInvocationManager.supports(getSecureObjectClass()),
- "AfterInvocationManager does not support secure object class: " + getSecureObjectClass());
- }
-
- if (this.validateConfigAttributes) {
- Iterator iter = this.obtainObjectDefinitionSource().getConfigAttributeDefinitions();
-
- if (iter == null) {
- logger.warn("Could not validate configuration attributes as the MethodDefinitionSource did not return "
- + "a ConfigAttributeDefinition Iterator");
- return;
- }
-
- Set unsupportedAttrs = new HashSet();
-
- while (iter.hasNext()) {
- ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter.next();
- Iterator attributes = def.getConfigAttributes();
-
- while (attributes.hasNext()) {
- ConfigAttribute attr = (ConfigAttribute) attributes.next();
-
- if (!this.runAsManager.supports(attr) && !this.accessDecisionManager.supports(attr)
- && ((this.afterInvocationManager == null) || !this.afterInvocationManager.supports(attr))) {
- unsupportedAttrs.add(attr);
- }
- }
- }
-
- if (unsupportedAttrs.size() != 0) {
- throw new IllegalArgumentException("Unsupported configuration attributes: " + unsupportedAttrs);
- }
-
- logger.info("Validated configuration attributes");
- }
- }
-
- protected InterceptorStatusToken beforeInvocation(Object object) {
- Assert.notNull(object, "Object was null");
-
- if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
- throw new IllegalArgumentException("Security invocation attempted for object "
- + object.getClass().getName()
- + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
- + getSecureObjectClass());
- }
-
- ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource().getAttributes(object);
-
- if (attr == null) {
- if(rejectPublicInvocations) {
- throw new IllegalArgumentException(
- "No public invocations are allowed via this AbstractSecurityInterceptor. "
- + "This indicates a configuration error because the "
- + "AbstractSecurityInterceptor.rejectPublicInvocations property is set to 'true'");
- }
-
- if (logger.isDebugEnabled()) {
- logger.debug("Public object - authentication not attempted");
- }
-
- publishEvent(new PublicInvocationEvent(object));
-
- return null; // no further work post-invocation
- }
-
-
- if (logger.isDebugEnabled()) {
- logger.debug("Secure object: " + object.toString() + "; ConfigAttributes: " + attr.toString());
- }
+ MessageSourceAware, ApplicationContextAware {
+ // ~ Static fields/initializers
+ // =====================================================================================
- if (SecurityContextHolder.getContext().getAuthentication() == null) {
- credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
- "An Authentication object was not found in the SecurityContext"), object, attr);
- }
-
- // Attempt authentication if not already authenticated, or user always wants reauthentication
- Authentication authenticated;
-
- if (!SecurityContextHolder.getContext().getAuthentication().isAuthenticated() || alwaysReauthenticate) {
- try {
- authenticated = this.authenticationManager.authenticate(SecurityContextHolder.getContext()
- .getAuthentication());
- } catch (AuthenticationException authenticationException) {
- throw authenticationException;
- }
-
- // We don't authenticated.setAuthentication(true), because each provider should do that
- if (logger.isDebugEnabled()) {
- logger.debug("Successfully Authenticated: " + authenticated.toString());
- }
-
- SecurityContextHolder.getContext().setAuthentication(authenticated);
- } else {
- authenticated = SecurityContextHolder.getContext().getAuthentication();
-
- if (logger.isDebugEnabled()) {
- logger.debug("Previously Authenticated: " + authenticated.toString());
- }
- }
-
- // Attempt authorization
- try {
- this.accessDecisionManager.decide(authenticated, object, attr);
- } catch (AccessDeniedException accessDeniedException) {
- AuthorizationFailureEvent event = new AuthorizationFailureEvent(object, attr, authenticated,
- accessDeniedException);
- publishEvent(event);
-
- throw accessDeniedException;
- }
-
- if (logger.isDebugEnabled()) {
- logger.debug("Authorization successful");
- }
-
- AuthorizedEvent event = new AuthorizedEvent(object, attr, authenticated);
- publishEvent(event);
-
- // Attempt to run as a different user
- Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attr);
-
- if (runAs == null) {
- if (logger.isDebugEnabled()) {
- logger.debug("RunAsManager did not change Authentication object");
- }
-
- // no further work post-invocation
- return new InterceptorStatusToken(authenticated, false, attr, object);
- } else {
- if (logger.isDebugEnabled()) {
- logger.debug("Switching to RunAs Authentication: " + runAs.toString());
- }
-
- SecurityContextHolder.getContext().setAuthentication(runAs);
-
- // revert to token.Authenticated post-invocation
- return new InterceptorStatusToken(authenticated, true, attr, object);
- }
- }
-
- /**
- * Helper method which generates an exception containing the passed reason, and publishes an event to the
- * application context.Always throws an exception.
- * - * @param reason to be provided in the exception detail - * @param secureObject that was being called - * @param configAttribs that were defined for the secureObject - */ - private void credentialsNotFound(String reason, Object secureObject, ConfigAttributeDefinition configAttribs) { - AuthenticationCredentialsNotFoundException exception = new AuthenticationCredentialsNotFoundException(reason); - - AuthenticationCredentialsNotFoundEvent event = new AuthenticationCredentialsNotFoundEvent(secureObject, - configAttribs, exception); - publishEvent(event); - - throw exception; - } - - public AccessDecisionManager getAccessDecisionManager() { - return accessDecisionManager; - } - - public AfterInvocationManager getAfterInvocationManager() { - return afterInvocationManager; - } - - public AuthenticationManager getAuthenticationManager() { - return this.authenticationManager; - } - - public RunAsManager getRunAsManager() { - return runAsManager; - } - - /** - * Indicates the type of secure objects the subclass will be presenting to the abstract parent for - * processing. This is used to ensure collaborators wired to theAbstractSecurityInterceptor all
- * support the indicated secure object class.
- *
- * @return the type of secure object the subclass provides services for
- */
- public abstract Class getSecureObjectClass();
-
- public boolean isAlwaysReauthenticate() {
- return alwaysReauthenticate;
- }
-
- public boolean isRejectPublicInvocations() {
- return rejectPublicInvocations;
- }
-
- public boolean isValidateConfigAttributes() {
- return validateConfigAttributes;
- }
-
- public abstract ObjectDefinitionSource obtainObjectDefinitionSource();
-
- public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
- this.accessDecisionManager = accessDecisionManager;
- }
-
- public void setAfterInvocationManager(AfterInvocationManager afterInvocationManager) {
- this.afterInvocationManager = afterInvocationManager;
- }
-
- /**
- * Indicates whether the AbstractSecurityInterceptor should ignore the {@link
- * Authentication#isAuthenticated()} property. Defaults to false, meaning by default the
- * Authentication.isAuthenticated() property is trusted and re-authentication will not occur if the
- * principal has already been authenticated.
- *
- * @param alwaysReauthenticate true to force AbstractSecurityInterceptor to disregard the
- * value of Authentication.isAuthenticated() and always re-authenticate the request (defaults
- * to false).
- */
- public void setAlwaysReauthenticate(boolean alwaysReauthenticate) {
- this.alwaysReauthenticate = alwaysReauthenticate;
- }
-
- public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
- this.eventPublisher = applicationEventPublisher;
- }
-
- public void setAuthenticationManager(AuthenticationManager newManager) {
- this.authenticationManager = newManager;
- }
-
- public void setMessageSource(MessageSource messageSource) {
- this.messages = new MessageSourceAccessor(messageSource);
- }
-
- /**
- * By rejecting public invocations (and setting this property to true), essentially you are
- * ensuring that every secure object invocation advised by AbstractSecurityInterceptor has a
- * configuration attribute defined. This is useful to ensure a "fail safe" mode where undeclared secure objects
- * will be rejected and configuration omissions detected early. An IllegalArgumentException will be
- * thrown by the AbstractSecurityInterceptor if you set this property to true and an
- * attempt is made to invoke a secure object that has no configuration attributes.
- *
- * @param rejectPublicInvocations set to true to reject invocations of secure objects that have no
- * configuration attributes (by default it is false which treats undeclared secure objects as
- * "public" or unauthorized)
- */
- public void setRejectPublicInvocations(boolean rejectPublicInvocations) {
- this.rejectPublicInvocations = rejectPublicInvocations;
- }
-
- public void setRunAsManager(RunAsManager runAsManager) {
- this.runAsManager = runAsManager;
- }
-
- public void setValidateConfigAttributes(boolean validateConfigAttributes) {
- this.validateConfigAttributes = validateConfigAttributes;
- }
-
- private void publishEvent(ApplicationEvent event) {
- if (this.eventPublisher != null) {
- this.eventPublisher.publishEvent(event);
- }
- }
+ protected static final Log logger = LogFactory.getLog(AbstractSecurityInterceptor.class);
+
+ // ~ Instance fields
+ // ================================================================================================
+
+ private AccessDecisionManager accessDecisionManager;
+
+ private AfterInvocationManager afterInvocationManager;
+
+ private ApplicationEventPublisher eventPublisher;
+
+ private AuthenticationManager authenticationManager;
+
+ protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
+
+ private RunAsManager runAsManager = new NullRunAsManager();
+
+ private boolean alwaysReauthenticate = false;
+
+ private boolean rejectPublicInvocations = false;
+
+ private boolean validateConfigAttributes = true;
+
+ private boolean isSetAuthenticationManagerInvoked = false;
+
+ private boolean isSetAccessDecisionManagerInvoked = false;
+
+ private ApplicationContext applicationContext;
+
+ // ~ Methods
+ // ========================================================================================================
+
+ /**
+ * Completes the work of the AbstractSecurityInterceptor
+ * after the secure object invocation has been complete
+ *
+ * @param token as returned by the {@link #beforeInvocation(Object)}}
+ * method
+ * @param returnedObject any object returned from the secure object
+ * invocation (may benull)
+ *
+ * @return the object the secure object invocation should ultimately return
+ * to its caller (may be null)
+ */
+ protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
+ if (token == null) {
+ // public object
+ return returnedObject;
+ }
+
+ if (token.isContextHolderRefreshRequired()) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Reverting to original Authentication: " + token.getAuthentication().toString());
+ }
+
+ SecurityContextHolder.getContext().setAuthentication(token.getAuthentication());
+ }
+
+ if (afterInvocationManager != null) {
+ // Attempt after invocation handling
+ try {
+ returnedObject = afterInvocationManager.decide(token.getAuthentication(), token.getSecureObject(),
+ token.getAttr(), returnedObject);
+ }
+ catch (AccessDeniedException accessDeniedException) {
+ AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token
+ .getAttr(), token.getAuthentication(), accessDeniedException);
+ publishEvent(event);
+
+ throw accessDeniedException;
+ }
+ }
+
+ return returnedObject;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(getSecureObjectClass(), "Subclass must provide a non-null response to getSecureObjectClass()");
+
+ Assert.notNull(this.messages, "A message source must be set");
+
+ if (!isSetAuthenticationManagerInvoked) {
+ autoDetectAuthenticationManager();
+ }
+ Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
+
+ if (!isSetAccessDecisionManagerInvoked) {
+ autoDetectAccessDecisionManager();
+ }
+ Assert.notNull(this.accessDecisionManager, "An AccessDecisionManager is required");
+
+ Assert.notNull(this.runAsManager, "A RunAsManager is required");
+
+ Assert.notNull(this.obtainObjectDefinitionSource(), "An ObjectDefinitionSource is required");
+
+ Assert.isTrue(this.obtainObjectDefinitionSource().supports(getSecureObjectClass()),
+ "ObjectDefinitionSource does not support secure object class: " + getSecureObjectClass());
+
+ Assert.isTrue(this.runAsManager.supports(getSecureObjectClass()),
+ "RunAsManager does not support secure object class: " + getSecureObjectClass());
+
+ Assert.isTrue(this.accessDecisionManager.supports(getSecureObjectClass()),
+ "AccessDecisionManager does not support secure object class: " + getSecureObjectClass());
+
+ if (this.afterInvocationManager != null) {
+ Assert.isTrue(this.afterInvocationManager.supports(getSecureObjectClass()),
+ "AfterInvocationManager does not support secure object class: " + getSecureObjectClass());
+ }
+
+ if (this.validateConfigAttributes) {
+ Iterator iter = this.obtainObjectDefinitionSource().getConfigAttributeDefinitions();
+
+ if (iter == null) {
+ logger.warn("Could not validate configuration attributes as the MethodDefinitionSource did not return "
+ + "a ConfigAttributeDefinition Iterator");
+ return;
+ }
+
+ Set unsupportedAttrs = new HashSet();
+
+ while (iter.hasNext()) {
+ ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter.next();
+ Iterator attributes = def.getConfigAttributes();
+
+ while (attributes.hasNext()) {
+ ConfigAttribute attr = (ConfigAttribute) attributes.next();
+
+ if (!this.runAsManager.supports(attr) && !this.accessDecisionManager.supports(attr)
+ && ((this.afterInvocationManager == null) || !this.afterInvocationManager.supports(attr))) {
+ unsupportedAttrs.add(attr);
+ }
+ }
+ }
+
+ if (unsupportedAttrs.size() != 0) {
+ throw new IllegalArgumentException("Unsupported configuration attributes: " + unsupportedAttrs);
+ }
+
+ logger.info("Validated configuration attributes");
+ }
+ }
+
+ /**
+ * Introspects the Applicationcontext for the single instance
+ * of AccessDecisionManager. If more than one instance of
+ * AccessDecisionManager is found, the method uses the first
+ * one detected.
+ *
+ * @param applicationContext to locate the instance
+ */
+ private void autoDetectAccessDecisionManager() {
+ if (applicationContext != null) {
+ Map map = applicationContext.getBeansOfType(AccessDecisionManager.class);
+ if (map.size() > 0)
+ setAccessDecisionManager((AccessDecisionManager) map.values().iterator().next());
+ }
+ }
+
+ /**
+ * Introspects the Applicationcontext for the single instance
+ * of AuthenticationManager. If found invoke
+ * setAuthenticationManager method by providing the found instance of
+ * authenticationManager as a method parameter. If more than one instance of
+ * AuthenticationManager is found, the method throws
+ * IllegalStateException.
+ *
+ * @param applicationContext to locate the instance
+ */
+ private void autoDetectAuthenticationManager() {
+ if (applicationContext != null) {
+ Map map = applicationContext.getBeansOfType(AuthenticationManager.class);
+ if (map.size() > 1) {
+ throw new IllegalArgumentException(
+ "More than one AuthenticationManager beans detected please refer to the one using "
+ + " [ authenticationManager ] " + "property");
+ }
+ else if (map.size() == 1) {
+ setAuthenticationManager((AuthenticationManager) map.values().iterator().next());
+ }
+ }
+
+ }
+
+ protected InterceptorStatusToken beforeInvocation(Object object) {
+ Assert.notNull(object, "Object was null");
+
+ if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
+ throw new IllegalArgumentException("Security invocation attempted for object "
+ + object.getClass().getName()
+ + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+ + getSecureObjectClass());
+ }
+
+ ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource().getAttributes(object);
+
+ if (attr == null) {
+ if (rejectPublicInvocations) {
+ throw new IllegalArgumentException(
+ "No public invocations are allowed via this AbstractSecurityInterceptor. "
+ + "This indicates a configuration error because the "
+ + "AbstractSecurityInterceptor.rejectPublicInvocations property is set to 'true'");
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Public object - authentication not attempted");
+ }
+
+ publishEvent(new PublicInvocationEvent(object));
+
+ return null; // no further work post-invocation
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Secure object: " + object.toString() + "; ConfigAttributes: " + attr.toString());
+ }
+
+ if (SecurityContextHolder.getContext().getAuthentication() == null) {
+ credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
+ "An Authentication object was not found in the SecurityContext"), object, attr);
+ }
+
+ // Attempt authentication if not already authenticated, or user always
+ // wants reauthentication
+ Authentication authenticated;
+
+ if (!SecurityContextHolder.getContext().getAuthentication().isAuthenticated() || alwaysReauthenticate) {
+ try {
+ authenticated = this.authenticationManager.authenticate(SecurityContextHolder.getContext()
+ .getAuthentication());
+ }
+ catch (AuthenticationException authenticationException) {
+ throw authenticationException;
+ }
+
+ // We don't authenticated.setAuthentication(true), because each
+ // provider should do that
+ if (logger.isDebugEnabled()) {
+ logger.debug("Successfully Authenticated: " + authenticated.toString());
+ }
+
+ SecurityContextHolder.getContext().setAuthentication(authenticated);
+ }
+ else {
+ authenticated = SecurityContextHolder.getContext().getAuthentication();
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Previously Authenticated: " + authenticated.toString());
+ }
+ }
+
+ // Attempt authorization
+ try {
+ this.accessDecisionManager.decide(authenticated, object, attr);
+ }
+ catch (AccessDeniedException accessDeniedException) {
+ AuthorizationFailureEvent event = new AuthorizationFailureEvent(object, attr, authenticated,
+ accessDeniedException);
+ publishEvent(event);
+
+ throw accessDeniedException;
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Authorization successful");
+ }
+
+ AuthorizedEvent event = new AuthorizedEvent(object, attr, authenticated);
+ publishEvent(event);
+
+ // Attempt to run as a different user
+ Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attr);
+
+ if (runAs == null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("RunAsManager did not change Authentication object");
+ }
+
+ // no further work post-invocation
+ return new InterceptorStatusToken(authenticated, false, attr, object);
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Switching to RunAs Authentication: " + runAs.toString());
+ }
+
+ SecurityContextHolder.getContext().setAuthentication(runAs);
+
+ // revert to token.Authenticated post-invocation
+ return new InterceptorStatusToken(authenticated, true, attr, object);
+ }
+ }
+
+ /**
+ * Helper method which generates an exception containing the passed reason,
+ * and publishes an event to the application context.
+ * + * Always throws an exception. + *
+ * + * @param reason to be provided in the exception detail + * @param secureObject that was being called + * @param configAttribs that were defined for the secureObject + */ + private void credentialsNotFound(String reason, Object secureObject, ConfigAttributeDefinition configAttribs) { + AuthenticationCredentialsNotFoundException exception = new AuthenticationCredentialsNotFoundException(reason); + + AuthenticationCredentialsNotFoundEvent event = new AuthenticationCredentialsNotFoundEvent(secureObject, + configAttribs, exception); + publishEvent(event); + + throw exception; + } + + public AccessDecisionManager getAccessDecisionManager() { + return accessDecisionManager; + } + + public AfterInvocationManager getAfterInvocationManager() { + return afterInvocationManager; + } + + public AuthenticationManager getAuthenticationManager() { + return this.authenticationManager; + } + + public RunAsManager getRunAsManager() { + return runAsManager; + } + + /** + * Indicates the type of secure objects the subclass will be presenting to + * the abstract parent for processing. This is used to ensure collaborators + * wired to theAbstractSecurityInterceptor all support the
+ * indicated secure object class.
+ *
+ * @return the type of secure object the subclass provides services for
+ */
+ public abstract Class getSecureObjectClass();
+
+ public boolean isAlwaysReauthenticate() {
+ return alwaysReauthenticate;
+ }
+
+ public boolean isRejectPublicInvocations() {
+ return rejectPublicInvocations;
+ }
+
+ public boolean isValidateConfigAttributes() {
+ return validateConfigAttributes;
+ }
+
+ public abstract ObjectDefinitionSource obtainObjectDefinitionSource();
+
+ public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
+ isSetAccessDecisionManagerInvoked = true;
+ this.accessDecisionManager = accessDecisionManager;
+ }
+
+ public void setAfterInvocationManager(AfterInvocationManager afterInvocationManager) {
+ this.afterInvocationManager = afterInvocationManager;
+ }
+
+ /**
+ * Indicates whether the AbstractSecurityInterceptor should
+ * ignore the {@link Authentication#isAuthenticated()} property. Defaults to
+ * false, meaning by default the
+ * Authentication.isAuthenticated() property is trusted and
+ * re-authentication will not occur if the principal has already been
+ * authenticated.
+ *
+ * @param alwaysReauthenticate true to force
+ * AbstractSecurityInterceptor to disregard the value of
+ * Authentication.isAuthenticated() and always
+ * re-authenticate the request (defaults to false).
+ */
+ public void setAlwaysReauthenticate(boolean alwaysReauthenticate) {
+ this.alwaysReauthenticate = alwaysReauthenticate;
+ }
+
+ public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
+ this.eventPublisher = applicationEventPublisher;
+ }
+
+ public void setAuthenticationManager(AuthenticationManager newManager) {
+ isSetAuthenticationManagerInvoked = true;
+ this.authenticationManager = newManager;
+ }
+
+ public void setMessageSource(MessageSource messageSource) {
+ this.messages = new MessageSourceAccessor(messageSource);
+ }
+
+ /**
+ * By rejecting public invocations (and setting this property to
+ * true), essentially you are ensuring that every secure
+ * object invocation advised by AbstractSecurityInterceptor
+ * has a configuration attribute defined. This is useful to ensure a "fail
+ * safe" mode where undeclared secure objects will be rejected and
+ * configuration omissions detected early. An
+ * IllegalArgumentException will be thrown by the
+ * AbstractSecurityInterceptor if you set this property to
+ * true and an attempt is made to invoke a secure object that
+ * has no configuration attributes.
+ *
+ * @param rejectPublicInvocations set to true to reject
+ * invocations of secure objects that have no configuration attributes (by
+ * default it is false which treats undeclared secure objects
+ * as "public" or unauthorized)
+ */
+ public void setRejectPublicInvocations(boolean rejectPublicInvocations) {
+ this.rejectPublicInvocations = rejectPublicInvocations;
+ }
+
+ public void setRunAsManager(RunAsManager runAsManager) {
+ this.runAsManager = runAsManager;
+ }
+
+ public void setValidateConfigAttributes(boolean validateConfigAttributes) {
+ this.validateConfigAttributes = validateConfigAttributes;
+ }
+
+ private void publishEvent(ApplicationEvent event) {
+ if (this.eventPublisher != null) {
+ this.eventPublisher.publishEvent(event);
+ }
+ }
+
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
}
diff --git a/core/src/main/java/org/acegisecurity/vote/AbstractAccessDecisionManager.java b/core/src/main/java/org/acegisecurity/vote/AbstractAccessDecisionManager.java
index 2854291c23..3b8291d7d7 100644
--- a/core/src/main/java/org/acegisecurity/vote/AbstractAccessDecisionManager.java
+++ b/core/src/main/java/org/acegisecurity/vote/AbstractAccessDecisionManager.java
@@ -15,126 +15,159 @@
package org.acegisecurity.vote;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
import org.acegisecurity.AccessDecisionManager;
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.AcegiMessageSource;
import org.acegisecurity.ConfigAttribute;
-
+import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
-
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
-
+import org.springframework.core.OrderComparator;
+import org.springframework.core.Ordered;
import org.springframework.util.Assert;
-import java.util.Iterator;
-import java.util.List;
-
-
/**
- * Abstract implementation of {@link AccessDecisionManager}.Handles configuration of a bean context defined list - * of {@link AccessDecisionVoter}s and the access control behaviour if all voters abstain from voting (defaults to - * deny access).
+ * Abstract implementation of {@link AccessDecisionManager}. + *+ * Handles configuration of a bean context defined list of + * {@link AccessDecisionVoter}s and the access control behaviour if all voters + * abstain from voting (defaults to deny access). + *
*/ public abstract class AbstractAccessDecisionManager implements AccessDecisionManager, InitializingBean, - MessageSourceAware { - //~ Instance fields ================================================================================================ + MessageSourceAware, ApplicationContextAware { + // ~ Instance fields + // ================================================================================================ - private List decisionVoters; - protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor(); - private boolean allowIfAllAbstainDecisions = false; + private List decisionVoters; - //~ Methods ======================================================================================================== + protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor(); - public void afterPropertiesSet() throws Exception { - checkIfValidList(this.decisionVoters); - Assert.notNull(this.messages, "A message source must be set"); - } + private boolean allowIfAllAbstainDecisions = false; - protected final void checkAllowIfAllAbstainDecisions() { - if (!this.isAllowIfAllAbstainDecisions()) { - throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", - "Access is denied")); - } - } + private boolean isSetDecisionVotersInvoked = false; - private void checkIfValidList(List listToCheck) { - if ((listToCheck == null) || (listToCheck.size() == 0)) { - throw new IllegalArgumentException("A list of AccessDecisionVoters is required"); - } - } + private ApplicationContext applicationContext; - public List getDecisionVoters() { - return this.decisionVoters; - } + // ~ Methods + // ======================================================================================================== - public boolean isAllowIfAllAbstainDecisions() { - return allowIfAllAbstainDecisions; - } + public void afterPropertiesSet() throws Exception { + if (!isSetDecisionVotersInvoked) { + autoDetectVoters(); + } + Assert.notEmpty(this.decisionVoters, "A list of AccessDecisionVoters is required"); + Assert.notNull(this.messages, "A message source must be set"); + } - public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) { - this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions; - } + private void autoDetectVoters() { + Map map = this.applicationContext.getBeansOfType(AccessDecisionVoter.class); + List list = new ArrayList(); + for(Iterator it = map.values().iterator(); it.hasNext();) { + list.add((it.next())); + } + Collections.sort(list, new OrderComparator()); + setDecisionVoters(list); + } - public void setDecisionVoters(List newList) { - checkIfValidList(newList); + protected final void checkAllowIfAllAbstainDecisions() { + if (!this.isAllowIfAllAbstainDecisions()) { + throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", + "Access is denied")); + } + } - Iterator iter = newList.iterator(); + public List getDecisionVoters() { + return this.decisionVoters; + } - while (iter.hasNext()) { - Object currentObject = null; + public boolean isAllowIfAllAbstainDecisions() { + return allowIfAllAbstainDecisions; + } - try { - currentObject = iter.next(); + public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) { + this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions; + } - AccessDecisionVoter attemptToCast = (AccessDecisionVoter) currentObject; - } catch (ClassCastException cce) { - throw new IllegalArgumentException("AccessDecisionVoter " + currentObject.getClass().getName() - + " must implement AccessDecisionVoter"); - } - } + public void setDecisionVoters(List newList) { + isSetDecisionVotersInvoked = true; + Assert.notEmpty(newList); - this.decisionVoters = newList; - } + Iterator iter = newList.iterator(); - public void setMessageSource(MessageSource messageSource) { - this.messages = new MessageSourceAccessor(messageSource); - } + while (iter.hasNext()) { + Object currentObject = null; - public boolean supports(ConfigAttribute attribute) { - Iterator iter = this.decisionVoters.iterator(); + try { + currentObject = iter.next(); - while (iter.hasNext()) { - AccessDecisionVoter voter = (AccessDecisionVoter) iter.next(); + AccessDecisionVoter attemptToCast = (AccessDecisionVoter) currentObject; + } + catch (ClassCastException cce) { + throw new IllegalArgumentException("AccessDecisionVoter " + currentObject.getClass().getName() + + " must implement AccessDecisionVoter"); + } + } - if (voter.supports(attribute)) { - return true; - } - } + this.decisionVoters = newList; + } - return false; - } + public void setMessageSource(MessageSource messageSource) { + this.messages = new MessageSourceAccessor(messageSource); + } - /** - * Iterates through allAccessDecisionVoters and ensures each can support the presented class.If
- * one or more voters cannot support the presented class, false is returned.
AccessDecisionVoters and ensures
+ * each can support the presented class.
+ *
+ * If one or more voters cannot support the presented class,
+ * false is returned.
+ *
Votes if any {@link ConfigAttribute#getAttribute()} starts with a prefix indicating that it is a role. The
- * default prefix string is ROLE_, but this may be overriden to any value. It may also be set to empty,
- * which means that essentially any attribute will be voted on. As described further below, the effect of an empty
- * prefix may not be quite desireable.
Abstains from voting if no configuration attribute commences with the role prefix. Votes to grant access if
- * there is an exact matching {@link org.acegisecurity.GrantedAuthority} to a ConfigAttribute starting
- * with the role prefix. Votes to deny access if there is no exact matching GrantedAuthority to a
- * ConfigAttribute starting with the role prefix.
An empty role prefix means that the voter will vote for every ConfigAttribute. When there are different - * categories of ConfigAttributes used, this will not be optimal since the voter will be voting for attributes which - * do not represent roles. However, this option may be of some use when using preexisting role names without a prefix, - * and no ability exists to prefix them with a role prefix on reading them in, such as provided for example in {@link - * org.acegisecurity.userdetails.jdbc.JdbcDaoImpl}.
- *All comparisons and prefixes are case sensitive.
- * + *
+ * Votes if any {@link ConfigAttribute#getAttribute()} starts with a prefix
+ * indicating that it is a role. The default prefix string is ROLE_,
+ * but this may be overriden to any value. It may also be set to empty, which
+ * means that essentially any attribute will be voted on. As described further
+ * below, the effect of an empty prefix may not be quite desireable.
+ *
+ * Abstains from voting if no configuration attribute commences with the role
+ * prefix. Votes to grant access if there is an exact matching
+ * {@link org.acegisecurity.GrantedAuthority} to a ConfigAttribute
+ * starting with the role prefix. Votes to deny access if there is no exact
+ * matching GrantedAuthority to a ConfigAttribute
+ * starting with the role prefix.
+ *
+ * An empty role prefix means that the voter will vote for every + * ConfigAttribute. When there are different categories of ConfigAttributes + * used, this will not be optimal since the voter will be voting for attributes + * which do not represent roles. However, this option may be of some use when + * using preexisting role names without a prefix, and no ability exists to + * prefix them with a role prefix on reading them in, such as provided for + * example in {@link org.acegisecurity.userdetails.jdbc.JdbcDaoImpl}. + *
+ *+ * All comparisons and prefixes are case sensitive. + *
+ * * @author Ben Alex * @author colin sampaleanu * @version $Id$ */ -public class RoleVoter implements AccessDecisionVoter { - //~ Instance fields ================================================================================================ +public class RoleVoter implements AccessDecisionVoter, Ordered { + // ~ Static fields/initializers + // ===================================================================================== + public static int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE; - private String rolePrefix = "ROLE_"; + // ~ Instance fields + // ================================================================================================ - //~ Methods ======================================================================================================== + private String rolePrefix = "ROLE_"; - public String getRolePrefix() { - return rolePrefix; - } + private int order = DEFAULT_ORDER; - /** - * Allows the default role prefix ofROLE_ to be overriden. May be set to an empty value,
- * although this is usually not desireable.
- *
- * @param rolePrefix the new prefix
- */
- public void setRolePrefix(String rolePrefix) {
- this.rolePrefix = rolePrefix;
- }
+ // ~ Methods
+ // ========================================================================================================
- public boolean supports(ConfigAttribute attribute) {
- if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getRolePrefix())) {
- return true;
- } else {
- return false;
- }
- }
+ public String getRolePrefix() {
+ return rolePrefix;
+ }
- /**
- * This implementation supports any type of class, because it does not query the presented secure object.
- *
- * @param clazz the secure object
- *
- * @return always true
- */
- public boolean supports(Class clazz) {
- return true;
- }
+ /**
+ * Allows the default role prefix of ROLE_ to be overriden.
+ * May be set to an empty value, although this is usually not desireable.
+ *
+ * @param rolePrefix the new prefix
+ */
+ public void setRolePrefix(String rolePrefix) {
+ this.rolePrefix = rolePrefix;
+ }
- public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
- int result = ACCESS_ABSTAIN;
- Iterator iter = config.getConfigAttributes();
+ public boolean supports(ConfigAttribute attribute) {
+ if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getRolePrefix())) {
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
- while (iter.hasNext()) {
- ConfigAttribute attribute = (ConfigAttribute) iter.next();
+ /**
+ * This implementation supports any type of class, because it does not query
+ * the presented secure object.
+ *
+ * @param clazz the secure object
+ *
+ * @return always true
+ */
+ public boolean supports(Class clazz) {
+ return true;
+ }
- if (this.supports(attribute)) {
- result = ACCESS_DENIED;
+ public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
+ int result = ACCESS_ABSTAIN;
+ Iterator iter = config.getConfigAttributes();
- // Attempt to find a matching granted authority
- for (int i = 0; i < authentication.getAuthorities().length; i++) {
- if (attribute.getAttribute().equals(authentication.getAuthorities()[i].getAuthority())) {
- return ACCESS_GRANTED;
- }
- }
- }
- }
+ while (iter.hasNext()) {
+ ConfigAttribute attribute = (ConfigAttribute) iter.next();
+
+ if (this.supports(attribute)) {
+ result = ACCESS_DENIED;
+
+ // Attempt to find a matching granted authority
+ for (int i = 0; i < authentication.getAuthorities().length; i++) {
+ if (attribute.getAttribute().equals(authentication.getAuthorities()[i].getAuthority())) {
+ return ACCESS_GRANTED;
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ public int getOrder() {
+ return order;
+ }
- return result;
- }
}
diff --git a/core/src/test/java/org/acegisecurity/vote/DenyAgainVoter.java b/core/src/test/java/org/acegisecurity/vote/DenyAgainVoter.java
index ace32801ce..888aa5ede7 100644
--- a/core/src/test/java/org/acegisecurity/vote/DenyAgainVoter.java
+++ b/core/src/test/java/org/acegisecurity/vote/DenyAgainVoter.java
@@ -18,45 +18,64 @@ package org.acegisecurity.vote;
import org.acegisecurity.Authentication;
import org.acegisecurity.ConfigAttribute;
import org.acegisecurity.ConfigAttributeDefinition;
+import org.springframework.core.Ordered;
import java.util.Iterator;
-
/**
- * Implementation of an {@link AccessDecisionVoter} for unit testing.If the {@link
- * ConfigAttribute#getAttribute()} has a value of DENY_AGAIN_FOR_SURE, the voter will vote to deny
- * access.
All comparisons are case sensitive.
- * + * Implementation of an {@link AccessDecisionVoter} for unit testing. + *
+ * If the {@link ConfigAttribute#getAttribute()} has a value of
+ * DENY_AGAIN_FOR_SURE, the voter will vote to deny access.
+ *
+ * All comparisons are case sensitive. + *
+ * * @author Ben Alex * @version $Id$ */ -public class DenyAgainVoter implements AccessDecisionVoter { - //~ Methods ======================================================================================================== +public class DenyAgainVoter implements AccessDecisionVoter, Ordered { + public static int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE; - public boolean supports(ConfigAttribute attribute) { - if ("DENY_AGAIN_FOR_SURE".equals(attribute.getAttribute())) { - return true; - } else { - return false; - } - } + private int order = DEFAULT_ORDER; - public boolean supports(Class clazz) { - return true; - } + // ~ Methods + // ======================================================================================================== - public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) { - Iterator iter = config.getConfigAttributes(); + public boolean supports(ConfigAttribute attribute) { + if ("DENY_AGAIN_FOR_SURE".equals(attribute.getAttribute())) { + return true; + } + else { + return false; + } + } - while (iter.hasNext()) { - ConfigAttribute attribute = (ConfigAttribute) iter.next(); + public boolean supports(Class clazz) { + return true; + } - if (this.supports(attribute)) { - return ACCESS_DENIED; - } - } + public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) { + Iterator iter = config.getConfigAttributes(); + + while (iter.hasNext()) { + ConfigAttribute attribute = (ConfigAttribute) iter.next(); + + if (this.supports(attribute)) { + return ACCESS_DENIED; + } + } + + return ACCESS_ABSTAIN; + } + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return order; + } - return ACCESS_ABSTAIN; - } }