Refactoring to support "after invocation" processing.

This commit is contained in:
Ben Alex 2004-11-15 01:43:48 +00:00
parent 03a530b36b
commit 5f6aa9c49e
6 changed files with 283 additions and 97 deletions

View File

@ -0,0 +1,103 @@
/* Copyright 2004 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity;
/**
* Reviews the <code>Object</code> returned from a secure object invocation,
* being able to modify the <code>Object</code> or throw an {@link
* AccessDeniedException}.
*
* <p>
* Typically used to ensure the principal is permitted to access the domain
* object instance returned by a service layer bean. Can also be used to
* mutate the domain object instance so the principal is only able to access
* authorised bean properties or <code>Collection</code> elements. Often used
* in conjunction with an {@link net.sf.acegisecurity.acl.AclManager} to
* obtain the access control list applicable for the domain object instance.
* </p>
*
* <p>
* Special consideration should be given to using an
* <code>AfterInvocationManager</code> on bean methods that modify a database.
* Typically an <code>AfterInvocationManager</code> is used with read-only
* methods, such as <code>public DomainObject getById(id)</code>. If used with
* methods that modify a database, a transaction manager should be used to
* ensure any <code>AccessDeniedException</code> will cause a rollback of the
* changes made by the transaction.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface AfterInvocationManager {
//~ Methods ================================================================
/**
* Given the details of a secure object invocation including its returned
* <code>Object</code>, make an access control decision or optionally
* modify the returned <code>Object</code>.
*
* @param authentication the caller that invoked the method
* @param object the secured object that was called
* @param config the configuration attributes associated with the secured
* object that was invoked
* @param returnedObject the <code>Object</code> that was returned from the
* secure object invocation
*
* @return the <code>Object</code> that will ultimately be returned to the
* caller (if an implementation does not wish to modify the object
* to be returned to the caller, the implementation should simply
* return the same object it was passed by the
* <code>returnedObject</code> method argument)
*
* @throws AccessDeniedException if access is denied
*/
public Object decide(Authentication authentication, Object object,
ConfigAttributeDefinition config, Object returnedObject)
throws AccessDeniedException;
/**
* Indicates whether this <code>AfterInvocationManager</code> is able to
* process "after invocation" requests presented with the passed
* <code>ConfigAttribute</code>.
*
* <p>
* This allows the <code>AbstractSecurityInterceptor</code> to check every
* configuration attribute can be consumed by the configured
* <code>AccessDecisionManager</code> and/or <code>RunAsManager</code>
* and/or <code>AfterInvocationManager</code>.
* </p>
*
* @param attribute a configuration attribute that has been configured
* against the <code>AbstractSecurityInterceptor</code>
*
* @return true if this <code>AfterInvocationManager</code> can support the
* passed configuration attribute
*/
public boolean supports(ConfigAttribute attribute);
/**
* Indicates whether the <code>AfterInvocationManager</code> implementation
* is able to provide access control decisions for the indicated secured
* object type.
*
* @param clazz the class that is being queried
*
* @return <code>true</code> if the implementation can process the
* indicated class
*/
public boolean supports(Class clazz);
}

View File

@ -17,6 +17,7 @@ package net.sf.acegisecurity.intercept;
import net.sf.acegisecurity.AccessDecisionManager;
import net.sf.acegisecurity.AccessDeniedException;
import net.sf.acegisecurity.AfterInvocationManager;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationCredentialsNotFoundException;
import net.sf.acegisecurity.AuthenticationException;
@ -74,7 +75,7 @@ import java.util.Set;
* For an invocation that is secured (there is a
* <code>ConfigAttributeDefinition</code> for the secure object invocation):
*
* <ol>
* <ol type="a">
* <li>
* Authenticate the request against the configured {@link
* AuthenticationManager}, replacing the <code>Authentication</code> object on
@ -103,6 +104,11 @@ import java.util.Set;
* object, return the <code>ContextHolder</code> to the object that existed
* after the call to <code>AuthenticationManager</code>.
* </li>
* <li>
* If an <code>AfterInvocationManager</code> is defined, invoke the invocation
* manager and allow it to replace the object due to be returned to the
* caller.
* </li>
* </ol>
*
* </li>
@ -110,7 +116,7 @@ import java.util.Set;
* For an invocation that is public (there is no
* <code>ConfigAttributeDefinition</code> for the secure object invocation):
*
* <ol>
* <ol type="a">
* <li>
* If the <code>ContextHolder</code> contains a <code>SecureContext</code>, set
* the <code>isAuthenticated</code> flag on the <code>Authentication</code>
@ -128,9 +134,9 @@ import java.util.Set;
*
* </li>
* <li>
* Control again returns to the concrete subclass, which will return to the
* caller any result or exception that occurred when it proceeded with the
* execution of the secure object.
* Control again returns to the concrete subclass, along with the
* <code>Object</code> that should be returned to the caller. The subclass
* will then return that result or exception to the original caller.
* </li>
* </ol>
* </p>
@ -147,6 +153,7 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
//~ Instance fields ========================================================
private AccessDecisionManager accessDecisionManager;
private AfterInvocationManager afterInvocationManager;
private ApplicationContext context;
private AuthenticationManager authenticationManager;
private RunAsManager runAsManager = new NullRunAsManager();
@ -154,11 +161,30 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
//~ Methods ================================================================
public void setAfterInvocationManager(
AfterInvocationManager afterInvocationManager) {
this.afterInvocationManager = afterInvocationManager;
}
public AfterInvocationManager getAfterInvocationManager() {
return afterInvocationManager;
}
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.context = applicationContext;
}
/**
* 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 the <code>AbstractSecurityInterceptor</code> all
* support the indicated secure object class.
*
* @return the type of secure object the subclass provides services for
*/
public abstract Class getSecureObjectClass();
public abstract ObjectDefinitionSource obtainObjectDefinitionSource();
public void setAccessDecisionManager(
@ -223,51 +249,116 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
logger.warn(
"Could not validate configuration attributes as the MethodDefinitionSource did not return a ConfigAttributeDefinition Iterator");
}
} else {
Set set = new HashSet();
return;
}
while (iter.hasNext()) {
ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter
.next();
Iterator attributes = def.getConfigAttributes();
Set set = new HashSet();
while (attributes.hasNext()) {
ConfigAttribute attr = (ConfigAttribute) attributes
.next();
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)) {
set.add(attr);
if (!this.runAsManager.supports(attr)
&& !this.accessDecisionManager.supports(attr)
&& ((this.afterInvocationManager == null)
|| !this.afterInvocationManager.supports(attr))) {
set.add(attr);
}
}
}
}
if (set.size() == 0) {
if (logger.isInfoEnabled()) {
logger.info("Validated configuration attributes");
if (set.size() == 0) {
if (logger.isInfoEnabled()) {
logger.info("Validated configuration attributes");
}
} else {
throw new IllegalArgumentException(
"Unsupported configuration attributes: "
+ set.toString());
}
} else {
throw new IllegalArgumentException(
"Unsupported configuration attributes: " + set.toString());
}
}
if (getSecureObjectClass() == null) {
throw new IllegalArgumentException(
"Subclass must provide a non-null response to getSecureObjectClass()");
}
if (!this.accessDecisionManager.supports(getSecureObjectClass())) {
throw new IllegalArgumentException(
"AccessDecisionManager does not support secure object class: "
+ getSecureObjectClass());
}
boolean result = this.obtainObjectDefinitionSource().supports(getSecureObjectClass());
if (!result) {
throw new IllegalArgumentException(
"ObjectDefinitionSource does not support secure object class: "
+ getSecureObjectClass());
}
if (!this.runAsManager.supports(getSecureObjectClass())) {
throw new IllegalArgumentException(
"RunAsManager does not support secure object class: "
+ getSecureObjectClass());
}
if ((this.afterInvocationManager != null)
&& !this.afterInvocationManager.supports(getSecureObjectClass())) {
throw new IllegalArgumentException(
"AfterInvocationManager does not support secure object class: "
+ getSecureObjectClass());
}
if (!this.obtainObjectDefinitionSource().supports(getSecureObjectClass())) {
throw new IllegalArgumentException(
"ObjectDefinitionSource does not support secure object class: "
+ getSecureObjectClass());
}
}
protected void afterInvocation(InterceptorStatusToken token) {
/**
* Completes the work of the <code>AbstractSecurityInterceptor</code> 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 be<code>null</code>)
*
* @return the object the secure object invocation should ultimately return
* to its caller (may be <code>null</code>)
*/
protected Object afterInvocation(InterceptorStatusToken token,
Object returnedObject) {
if (token == null) {
return;
// public object
return returnedObject;
}
if (logger.isDebugEnabled()) {
logger.debug("Reverting to original Authentication: "
+ token.getAuthenticated().toString());
if (token.isContextHolderRefreshRequired()) {
if (logger.isDebugEnabled()) {
logger.debug("Reverting to original Authentication: "
+ token.getAuthentication().toString());
}
SecureContext secureContext = (SecureContext) ContextHolder
.getContext();
secureContext.setAuthentication(token.getAuthentication());
ContextHolder.setContext(secureContext);
}
SecureContext secureContext = (SecureContext) ContextHolder.getContext();
secureContext.setAuthentication(token.getAuthenticated());
ContextHolder.setContext(secureContext);
if (afterInvocationManager != null) {
returnedObject = afterInvocationManager.decide(token
.getAuthentication(), token.getSecureObject(),
token.getAttr(), returnedObject);
}
return returnedObject;
}
protected InterceptorStatusToken beforeInvocation(Object object) {
@ -275,10 +366,11 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
throw new IllegalArgumentException("Object was null");
}
if (!this.obtainObjectDefinitionSource().supports(object.getClass())) {
if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException(
"ObjectDefinitionSource does not support objects of type "
+ object.getClass());
"Security invocation attempted for object " + object
+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+ getSecureObjectClass());
}
ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource()
@ -365,7 +457,8 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
"RunAsManager did not change Authentication object");
}
return null; // no further work post-invocation
return new InterceptorStatusToken(authenticated, false, attr,
object); // no further work post-invocation
} else {
if (logger.isDebugEnabled()) {
logger.debug("Switching to RunAs Authentication: "
@ -375,10 +468,8 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
context.setAuthentication(runAs);
ContextHolder.setContext((Context) context);
InterceptorStatusToken token = new InterceptorStatusToken();
token.setAuthenticated(authenticated);
return token; // revert to token.Authenticated post-invocation
return new InterceptorStatusToken(authenticated, true, attr,
object); // revert to token.Authenticated post-invocation
}
} else {
if (logger.isDebugEnabled()) {

View File

@ -16,6 +16,7 @@
package net.sf.acegisecurity.intercept;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.ConfigAttributeDefinition;
/**
@ -23,14 +24,9 @@ import net.sf.acegisecurity.Authentication;
*
* <P>
* This class reflects the status of the security interception, so that the
* final call to <code>AbstractSecurityInterceptor</code> can tidy up
* correctly.
* </p>
*
* <P>
* Whilst this class currently only wraps a single object, it has been modelled
* as a class so that future changes to the operation of
* <code>AbstractSecurityInterceptor</code> are abstracted from subclasses.
* final call to {@link
* net.sf.acegisecurity.intercept.AbstractSecurityInterceptor#afterInvocation(InterceptorStatusToken,
* Object)} can tidy up correctly.
* </p>
*
* @author Ben Alex
@ -39,15 +35,41 @@ import net.sf.acegisecurity.Authentication;
public class InterceptorStatusToken {
//~ Instance fields ========================================================
private Authentication authenticated;
private Authentication authentication;
private ConfigAttributeDefinition attr;
private Object secureObject;
private boolean contextHolderRefreshRequired;
//~ Constructors ===========================================================
public InterceptorStatusToken(Authentication authentication,
boolean contextHolderRefreshRequired, ConfigAttributeDefinition attr,
Object secureObject) {
this.authentication = authentication;
this.contextHolderRefreshRequired = contextHolderRefreshRequired;
this.attr = attr;
this.secureObject = secureObject;
}
protected InterceptorStatusToken() {
throw new IllegalArgumentException("Cannot use default constructor");
}
//~ Methods ================================================================
public void setAuthenticated(Authentication authenticated) {
this.authenticated = authenticated;
public ConfigAttributeDefinition getAttr() {
return attr;
}
public Authentication getAuthenticated() {
return authenticated;
public Authentication getAuthentication() {
return authentication;
}
public boolean isContextHolderRefreshRequired() {
return contextHolderRefreshRequired;
}
public Object getSecureObject() {
return secureObject;
}
}

View File

@ -58,18 +58,8 @@ public class MethodSecurityInterceptor extends AbstractSecurityInterceptor
return this.objectDefinitionSource;
}
public void afterPropertiesSet() {
super.afterPropertiesSet();
if (!this.getAccessDecisionManager().supports(MethodInvocation.class)) {
throw new IllegalArgumentException(
"AccessDecisionManager does not support MethodInvocation");
}
if (!this.getRunAsManager().supports(MethodInvocation.class)) {
throw new IllegalArgumentException(
"RunAsManager does not support MethodInvocation");
}
public Class getSecureObjectClass() {
return MethodInvocation.class;
}
/**
@ -83,13 +73,13 @@ public class MethodSecurityInterceptor extends AbstractSecurityInterceptor
* @throws Throwable if any error occurs
*/
public Object invoke(MethodInvocation mi) throws Throwable {
Object result;
Object result = null;
InterceptorStatusToken token = super.beforeInvocation(mi);
try {
result = mi.proceed();
} finally {
super.afterInvocation(token);
result = super.afterInvocation(token, result);
}
return result;

View File

@ -35,7 +35,7 @@ import org.aspectj.lang.JoinPoint;
* </p>
*
* <p>
* The secure object type is <code>org.aspectj.lang.JointPoint</code>, which is
* The secure object type is <code>org.aspectj.lang.JoinPoint</code>, which is
* passed from the relevant <code>around()</code> advice. The
* <code>around()</code> advice also passes an anonymous implementation of
* {@link AspectJCallback} which contains the call for AspectJ to continue
@ -64,18 +64,8 @@ public class AspectJSecurityInterceptor extends AbstractSecurityInterceptor {
return this.objectDefinitionSource;
}
public void afterPropertiesSet() {
super.afterPropertiesSet();
if (!this.getAccessDecisionManager().supports(JoinPoint.class)) {
throw new IllegalArgumentException(
"AccessDecisionManager does not support JointPoint");
}
if (!this.getRunAsManager().supports(JoinPoint.class)) {
throw new IllegalArgumentException(
"RunAsManager does not support JointPoint");
}
public Class getSecureObjectClass() {
return JoinPoint.class;
}
/**
@ -91,13 +81,13 @@ public class AspectJSecurityInterceptor extends AbstractSecurityInterceptor {
* @return The returned value from the method invocation
*/
public Object invoke(JoinPoint jp, AspectJCallback advisorProceed) {
Object result;
Object result = null;
InterceptorStatusToken token = super.beforeInvocation(jp);
try {
result = advisorProceed.proceedWithObject();
} finally {
super.afterInvocation(token);
result = super.afterInvocation(token, result);
}
return result;

View File

@ -59,18 +59,8 @@ public class FilterSecurityInterceptor extends AbstractSecurityInterceptor {
return this.objectDefinitionSource;
}
public void afterPropertiesSet() {
super.afterPropertiesSet();
if (!this.getAccessDecisionManager().supports(FilterInvocation.class)) {
throw new IllegalArgumentException(
"AccessDecisionManager does not support FilterInvocation");
}
if (!this.getRunAsManager().supports(FilterInvocation.class)) {
throw new IllegalArgumentException(
"RunAsManager does not support FilterInvocation");
}
public Class getSecureObjectClass() {
return FilterInvocation.class;
}
public void invoke(FilterInvocation fi) throws Throwable {
@ -79,7 +69,7 @@ public class FilterSecurityInterceptor extends AbstractSecurityInterceptor {
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token);
super.afterInvocation(token, null);
}
}