Moved to net.sf.acegisecurity.intercept.method.

This commit is contained in:
Ben Alex 2004-04-02 12:03:18 +00:00
parent 738fd2161d
commit 3ece12c386
5 changed files with 0 additions and 780 deletions

View File

@ -1,158 +0,0 @@
/* 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;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.metadata.Attributes;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
/**
* Stores a {@link ConfigAttributeDefinition} for each method signature defined
* by Commons Attributes.
*
* <P>
* This class will only detect those attributes which are defined for the:
*
* <ul>
* <li>
* The class-wide attributes defined for the intercepted class.
* </li>
* <li>
* The class-wide attributes defined for interfaces explicitly implemented by
* the intercepted class.
* </li>
* <li>
* The method-specific attributes defined for the intercepted method of the
* intercepted class.
* </li>
* <li>
* The method-specific attributes defined by any explicitly implemented
* interface if that interface contains a method signature matching that of
* the intercepted method.
* </li>
* </ul>
* </p>
*
* <P>
* Note that attributes defined against parent classes (either for their
* methods or interfaces) are not detected. The attributes must be defined
* against an explicit method or interface on the intercepted class.
* </p>
*
* <p>
* Attributes detected that do not implement {@link ConfigAttribute} will be
* ignored.
* </p>
*
* @author Cameron Braid
* @author Ben Alex
* @version $Id$
*/
public class MethodDefinitionAttributes implements MethodDefinitionSource {
//~ Instance fields ========================================================
private Attributes attributes;
//~ Methods ================================================================
public void setAttributes(Attributes attributes) {
this.attributes = attributes;
}
public ConfigAttributeDefinition getAttributes(MethodInvocation invocation) {
ConfigAttributeDefinition definition = new ConfigAttributeDefinition();
Class interceptedClass = invocation.getMethod().getDeclaringClass();
// add the class level attributes for the implementing class
addClassAttributes(definition, interceptedClass);
// add the class level attributes for the implemented interfaces
addClassAttributes(definition, interceptedClass.getInterfaces());
// add the method level attributes for the implemented method
addMethodAttributes(definition, invocation.getMethod());
// add the method level attributes for the implemented intreface methods
addInterfaceMethodAttributes(definition, invocation.getMethod());
return definition;
}
public Iterator getConfigAttributeDefinitions() {
return null;
}
private void add(ConfigAttributeDefinition definition, Collection attribs) {
for (Iterator iter = attribs.iterator(); iter.hasNext();) {
Object o = (Object) iter.next();
if (o instanceof ConfigAttribute) {
definition.addConfigAttribute((ConfigAttribute) o);
}
}
}
private void addClassAttributes(ConfigAttributeDefinition definition,
Class clazz) {
addClassAttributes(definition, new Class[] {clazz});
}
private void addClassAttributes(ConfigAttributeDefinition definition,
Class[] clazz) {
for (int i = 0; i < clazz.length; i++) {
Collection classAttributes = attributes.getAttributes(clazz[i]);
if (classAttributes != null) {
add(definition, classAttributes);
}
}
}
private void addInterfaceMethodAttributes(
ConfigAttributeDefinition definition, Method method) {
Class[] interfaces = method.getDeclaringClass().getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
Class clazz = interfaces[i];
try {
Method m = clazz.getDeclaredMethod(method.getName(),
method.getParameterTypes());
addMethodAttributes(definition, m);
} catch (Exception e) {
// this won't happen since we are getting a method from an interface that
// the declaring class implements
}
}
}
private void addMethodAttributes(ConfigAttributeDefinition definition,
Method method) {
// add the method level attributes
Collection methodAttributes = attributes.getAttributes(method);
if (methodAttributes != null) {
add(definition, methodAttributes);
}
}
}

View File

@ -1,175 +0,0 @@
/* 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;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Stores a {@link ConfigAttributeDefinition} for each method signature defined
* in a bean context.
*
* @author Ben Alex
* @version $Id$
*/
public class MethodDefinitionMap implements MethodDefinitionSource {
private static final Log logger = LogFactory.getLog(MethodDefinitionMap.class);
/** Map from Method to ApplicationDefinition */
protected Map methodMap = new HashMap();
/** Map from Method to name pattern used for registration */
private Map nameMap = new HashMap();
public ConfigAttributeDefinition getAttributes(MethodInvocation invocation) {
return (ConfigAttributeDefinition) this.methodMap.get(invocation.getMethod());
}
public Iterator getConfigAttributeDefinitions() {
return methodMap.values().iterator();
}
public int getMethodMapSize() {
return this.methodMap.size();
}
/**
* Add required authorities for a secure method. Method names can end or
* start with "" for matching multiple methods.
*
* @param method the method to be secured
* @param attr required authorities associated with the method
*/
public void addSecureMethod(Method method, ConfigAttributeDefinition attr) {
logger.info("Adding secure method [" + method + "] with attributes [" +
attr + "]");
this.methodMap.put(method, attr);
}
/**
* Add required authorities for a secure method. Method names can end or
* start with "" for matching multiple methods.
*
* @param name class and method name, separated by a dot
* @param attr required authorities associated with the method
*
* @throws IllegalArgumentException DOCUMENT ME!
*/
public void addSecureMethod(String name, ConfigAttributeDefinition attr) {
int lastDotIndex = name.lastIndexOf(".");
if (lastDotIndex == -1) {
throw new IllegalArgumentException("'" + name +
"' is not a valid method name: format is FQN.methodName");
}
String className = name.substring(0, lastDotIndex);
String methodName = name.substring(lastDotIndex + 1);
try {
Class clazz = Class.forName(className, true,
Thread.currentThread().getContextClassLoader());
addSecureMethod(clazz, methodName, attr);
} catch (ClassNotFoundException ex) {
throw new IllegalArgumentException("Class '" + className +
"' not found");
}
}
/**
* Add required authorities for a secure method. Method names can end or
* start with "" for matching multiple methods.
*
* @param clazz target interface or class
* @param mappedName mapped method name
* @param attr required authorities associated with the method
*
* @throws IllegalArgumentException DOCUMENT ME!
*/
public void addSecureMethod(Class clazz, String mappedName,
ConfigAttributeDefinition attr) {
String name = clazz.getName() + '.' + mappedName;
logger.debug("Adding secure method [" + name + "] with attributes [" +
attr + "]");
Method[] methods = clazz.getDeclaredMethods();
List matchingMethods = new ArrayList();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(mappedName) ||
isMatch(methods[i].getName(), mappedName)) {
matchingMethods.add(methods[i]);
}
}
if (matchingMethods.isEmpty()) {
throw new IllegalArgumentException("Couldn't find method '" +
mappedName + "' on " + clazz);
}
// register all matching methods
for (Iterator it = matchingMethods.iterator(); it.hasNext();) {
Method method = (Method) it.next();
String regMethodName = (String) this.nameMap.get(method);
if ((regMethodName == null) ||
(!regMethodName.equals(name) &&
(regMethodName.length() <= name.length()))) {
// no already registered method name, or more specific
// method name specification now -> (re-)register method
if (regMethodName != null) {
logger.debug("Replacing attributes for secure method [" +
method + "]: current name [" + name +
"] is more specific than [" + regMethodName + "]");
}
this.nameMap.put(method, name);
addSecureMethod(method, attr);
} else {
logger.debug("Keeping attributes for secure method [" + method +
"]: current name [" + name +
"] is not more specific than [" + regMethodName + "]");
}
}
}
/**
* Return if the given method name matches the mapped name. The default
* implementation checks for "xxx" and "xxx" matches.
*
* @param methodName the method name of the class
* @param mappedName the name in the descriptor
*
* @return if the names match
*/
private boolean isMatch(String methodName, String mappedName) {
return (mappedName.endsWith("*") &&
methodName.startsWith(mappedName.substring(0, mappedName.length() - 1))) ||
(mappedName.startsWith("*") &&
methodName.endsWith(mappedName.substring(1, mappedName.length())));
}
}

View File

@ -1,52 +0,0 @@
/* 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;
import org.aopalliance.intercept.MethodInvocation;
import java.util.Iterator;
/**
* Implemented by classes that store {@link ConfigAttributeDefinition}s and can
* identify the appropriate <code>ConfigAttributeDefinition</code> that
* applies for the current method call.
*
* @author Ben Alex
* @version $Id$
*/
public interface MethodDefinitionSource {
//~ Methods ================================================================
/**
* DOCUMENT ME!
*
* @param invocation the method being called
*
* @return the <code>ConfigAttributeDefinition</code> that applies to the
* passed method call
*/
public ConfigAttributeDefinition getAttributes(MethodInvocation invocation);
/**
* If available, all of the <code>ConfigAttributeDefinition</code>s defined
* by the implementing class.
*
* @return an iterator over all the <code>ConfigAttributeDefinition</code>s
* or <code>null</code> if unsupported
*/
public Iterator getConfigAttributeDefinitions();
}

View File

@ -1,78 +0,0 @@
/* 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;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.propertyeditors.PropertiesEditor;
import java.beans.PropertyEditorSupport;
import java.util.Iterator;
import java.util.Properties;
/**
* Property editor to assist with the setup of {@link MethodDefinitionSource}.
*
* <p>
* The class creates and populates an {@link MethodDefinitionMap}.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class MethodDefinitionSourceEditor extends PropertyEditorSupport {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(MethodDefinitionSourceEditor.class);
//~ Methods ================================================================
public void setAsText(String s) throws IllegalArgumentException {
MethodDefinitionMap source = new MethodDefinitionMap();
if ((s == null) || "".equals(s)) {
// Leave value in property editor null
} else {
// Use properties editor to tokenize the string
PropertiesEditor propertiesEditor = new PropertiesEditor();
propertiesEditor.setAsText(s);
Properties props = (Properties) propertiesEditor.getValue();
// Now we have properties, process each one individually
ConfigAttributeEditor configAttribEd = new ConfigAttributeEditor();
for (Iterator iter = props.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String value = props.getProperty(name);
// Convert value to series of security configuration attributes
configAttribEd.setAsText(value);
ConfigAttributeDefinition attr = (ConfigAttributeDefinition) configAttribEd
.getValue();
// Register name and attribute
source.addSecureMethod(name, attr);
}
}
setValue(source);
}
}

View File

@ -1,317 +0,0 @@
/* 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;
import net.sf.acegisecurity.context.Context;
import net.sf.acegisecurity.context.ContextHolder;
import net.sf.acegisecurity.context.SecureContext;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Intercepts calls to an object and applies security.
*
* <p>
* A method is treated as public unless it has one or more configuration
* attributes defined via {@link
* #setMethodDefinitionSource(MethodDefinitionSource)}. If public, no
* authentication will be attempted, which means an unauthenticated {@link
* Authentication} object may be present in the {@link ContextHolder} (if any
* such an unauthenticated <code>Authentication</code> object exists, its
* {@link Authentication#isAuthenticated()} method will return
* <code>false</code> once the <code>SecurityInterceptor</code> has
* intercepted the public method).
* </p>
*
* <p>
* For those methods to be secured by the interceptor, one or more
* configuration attributes must be defined. These attributes are stored as
* {@link ConfigAttribute} objects.
* </p>
*
* <p>
* The presence of a configuration attribute for a given method will force
* authentication to be attempted via the {@link AuthenticationManager}
* configured against the interceptor. If successfully authenticated, the
* configured {@link AccessDecisionManager} will be passed the {@link
* ConfigAttributeDefinition} applicable for the method invocation, the
* method invocation itself, and the <code>Authentication</code> object. The
* <code>AccessDecisionManager</code> which will then make the authorization
* decision.
* </p>
*
* <p>
* There shouldn't be any requirement to customise the behaviour of the
* <code>SecurityInterceptor</code>, as all security decisions are made by the
* <code>AuthenticationProvider</code> and <code>AccessDecisionManager</code>
* interfaces, which can of course be replaced with different concrete
* implementations.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class SecurityInterceptor implements MethodInterceptor, InitializingBean {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(SecurityInterceptor.class);
//~ Instance fields ========================================================
private AccessDecisionManager accessDecisionManager;
private AuthenticationManager authenticationManager;
private MethodDefinitionSource methodDefinitionSource;
private RunAsManager runAsManager;
private boolean validateConfigAttributes = true;
//~ Methods ================================================================
public void setAccessDecisionManager(
AccessDecisionManager accessDecisionManager) {
this.accessDecisionManager = accessDecisionManager;
}
public AccessDecisionManager getAccessDecisionManager() {
return accessDecisionManager;
}
public void setAuthenticationManager(AuthenticationManager newManager) {
this.authenticationManager = newManager;
}
public AuthenticationManager getAuthenticationManager() {
return this.authenticationManager;
}
public void setMethodDefinitionSource(MethodDefinitionSource newSource) {
this.methodDefinitionSource = newSource;
}
public MethodDefinitionSource getMethodDefinitionSource() {
return this.methodDefinitionSource;
}
public void setRunAsManager(RunAsManager runAsManager) {
this.runAsManager = runAsManager;
}
public RunAsManager getRunAsManager() {
return runAsManager;
}
public void setValidateConfigAttributes(boolean validateConfigAttributes) {
this.validateConfigAttributes = validateConfigAttributes;
}
public boolean isValidateConfigAttributes() {
return validateConfigAttributes;
}
public void afterPropertiesSet() {
if (this.authenticationManager == null) {
throw new IllegalArgumentException(
"An AuthenticationManager is required");
}
if (this.accessDecisionManager == null) {
throw new IllegalArgumentException(
"An AccessDecisionManager is required");
}
if (this.runAsManager == null) {
throw new IllegalArgumentException("A RunAsManager is required");
}
if (this.methodDefinitionSource == null) {
throw new IllegalArgumentException(
"A MethodDefinitionSource is required");
}
if (this.validateConfigAttributes) {
Iterator iter = this.methodDefinitionSource
.getConfigAttributeDefinitions();
if (iter == null) {
if (logger.isWarnEnabled()) {
logger.warn(
"Could not validate configuration attributes as the MethodDefinitionSource did not return a ConfigAttributeDefinition Iterator");
}
return;
}
Set set = 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)) {
set.add(attr);
}
}
}
if (set.size() == 0) {
if (logger.isInfoEnabled()) {
logger.info("Validated configuration attributes");
}
} else {
throw new IllegalArgumentException(
"Unsupported configuration attributes: " + set.toString());
}
}
}
/**
* Does the work of authenticating and authorizing the request. Throws
* {@link AcegiSecurityException} and its subclasses.
*
* @param mi The method being invoked which requires a security decision
*
* @return The returned value from the method invocation
*
* @throws Throwable if any error occurs
* @throws AuthenticationCredentialsNotFoundException if the
* <code>ContextHolder</code> does not contain a valid
* <code>SecureContext</code> which in turn contains an
* <code>Authentication</code> object
*/
public Object invoke(MethodInvocation mi) throws Throwable {
if (logger.isDebugEnabled()) {
logger.debug("Intercepted request for method " + mi.getMethod());
}
ConfigAttributeDefinition attr = this.methodDefinitionSource
.getAttributes(mi);
if (attr != null) {
if (logger.isDebugEnabled()) {
logger.debug("Secure method configuration "
+ attr.getConfigAttributes().toString());
}
// Ensure ContextHolder presents a populated SecureContext
if ((ContextHolder.getContext() == null)
|| !(ContextHolder.getContext() instanceof SecureContext)) {
throw new AuthenticationCredentialsNotFoundException(
"A valid SecureContext was not provided in the RequestContext");
}
SecureContext context = (SecureContext) ContextHolder.getContext();
// We check for just the property we're interested in (we do
// not call Context.validate() like the ContextInterceptor)
if (context.getAuthentication() == null) {
throw new AuthenticationCredentialsNotFoundException(
"Authentication credentials were not found in the SecureContext");
}
// Attempt authentication
Authentication authenticated = this.authenticationManager
.authenticate(context.getAuthentication());
authenticated.setAuthenticated(true);
logger.debug("Authenticated: " + authenticated.toString());
context.setAuthentication(authenticated);
ContextHolder.setContext((Context) context);
// Attempt authorization
this.accessDecisionManager.decide(authenticated, mi, attr);
if (logger.isDebugEnabled()) {
logger.debug("Authorization successful");
}
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated,
mi, attr);
if (runAs == null) {
if (logger.isDebugEnabled()) {
logger.debug(
"RunAsManager did not change Authentication object");
}
Object ret = mi.proceed();
return ret;
} else {
if (logger.isDebugEnabled()) {
logger.debug("Switching to RunAs Authentication: "
+ runAs.toString());
}
context.setAuthentication(runAs);
ContextHolder.setContext((Context) context);
Object ret = mi.proceed();
if (logger.isDebugEnabled()) {
logger.debug("Reverting to original Authentication: "
+ authenticated.toString());
}
context.setAuthentication(authenticated);
ContextHolder.setContext((Context) context);
return ret;
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Public method - authentication not attempted");
}
// Set Authentication object (if it exists) to be unauthenticated
if ((ContextHolder.getContext() != null)
&& ContextHolder.getContext() instanceof SecureContext) {
SecureContext context = (SecureContext) ContextHolder
.getContext();
if (context.getAuthentication() != null) {
if (logger.isDebugEnabled()) {
logger.debug(
"Authentication object detected and tagged as unauthenticated");
}
Authentication authenticated = context.getAuthentication();
authenticated.setAuthenticated(false);
context.setAuthentication(authenticated);
ContextHolder.setContext((Context) context);
}
}
Object ret = mi.proceed();
return ret;
}
}
}