/* 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 org.riotfamily.core.security.policy; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.List; import java.util.Map; import org.hibernate.Hibernate; import org.riotfamily.common.util.FormatUtils; import org.riotfamily.common.util.Generics; import org.riotfamily.core.security.auth.RiotUser; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * AuthorizationPolicy that delegates permission checks to individual methods * via reflection. It looks for methods with the name of the action and either * one or two parameters (the first one must be assignment-compatible with * {@link RiotUser}) and a <code>Permission</code> return type. The action name is * uncapitalized and converted to camel-case. * * @author Felix Gnass [fgnass at neteye dot de] * @since 7.0 */ public class ReflectionPolicy implements AuthorizationPolicy { private Object delegate = this; private Map<SignaturePattern, PermissionMethod> methods = Generics.newHashMap(); private int order; public int getOrder() { return order; } public void setOrder(int order) { this.order = order; } public void setDelegate(Object delegate) { this.delegate = delegate; this.methods.clear(); } public final Permission getPermission(RiotUser user, String action, Object object, Object context) { PermissionMethod method = getMethod(new SignaturePattern(action, object, context)); if (method != null) { return method.invoke(delegate, user, action, object, context); } return Permission.ABSTAIN; } private PermissionMethod getMethod(SignaturePattern signature) { PermissionMethod method = methods.get(signature); if (method == null) { if (!methods.containsKey(signature)) { method = signature.find(delegate.getClass().getDeclaredMethods()); methods.put(signature, method); } } return method; } private enum Arguments { V1(0 ,1, 1), V2(0 ,1, 0), V3(0 ,0, 1), V4(1 ,1, 1), V5(1 ,1, 0), V6(1 ,0, 1); private boolean action; private boolean object; private boolean context; Arguments(int action, int object, int context) { this.action = action > 0; this.object = object > 0; this.context = context > 0; } public Object[] buildArgs(RiotUser user, String action, Object object, Object context) { List<Object> args = Generics.newArrayList(); args.add(user); if (this.action) { args.add(action); } if (this.object) { args.add(object); } if (this.context) { args.add(context); } return args.toArray(); } } private static class PermissionMethod { private Method method; private Arguments arguments; public PermissionMethod(Method method, Arguments arguments) { this.method = method; this.arguments = arguments; } public Permission invoke(Object delegate, RiotUser user, String action, Object object, Object context) { Object[] args = arguments.buildArgs(user, action, object, context); try { return (Permission) method.invoke(delegate, args); } catch (IllegalArgumentException e) { throw new RuntimeException( String.format("Failed to invoke %s with arguments %s", method.toString(), StringUtils.arrayToCommaDelimitedString(args)), e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } } private static class SignaturePattern { private String action; private Class<?> objectClass; private Class<?> contextClass; public SignaturePattern(String action, Object obj, Object context) { this.action = StringUtils.uncapitalize(FormatUtils.xmlToCamelCase(action)); if (obj != null) { objectClass = Hibernate.getClass(obj); } if (context != null) { contextClass = context.getClass(); } } public PermissionMethod find(Method[] methods) { for (Arguments arguments : Arguments.values()) { Method method = find(methods, arguments); if (method != null) { return new PermissionMethod(method, arguments); } } return null; } private Method find(Method[] methods, Arguments arguments) { Method match = null; for (int i = 0; i < methods.length; i++) { Method method = methods[i]; if (matches(method, arguments)) { Assert.isNull(match, "Found ambigious signatures: " + method + " and " + match); match = method; } } return match; } private boolean matches(Method method, Arguments arguments) { if (!Modifier.isPublic(method.getModifiers())) { return false; } if (!method.getReturnType().equals(Permission.class)) { return false; } Class<?>[] paramTypes = method.getParameterTypes(); if (arguments.action) { if (method.getName().equals("getPermission")) { return paramTypesMatch(paramTypes, 2, arguments) && paramTypes[1].equals(String.class); } } else { if (method.getName().equals(action)) { return paramTypesMatch(paramTypes, 1, arguments); } } return false; } private boolean paramTypesMatch(Class<?>[] paramTypes, int offset, Arguments arguments) { int i = offset; if (arguments.object) { if (objectClass == null) { return false; } if (paramTypes.length <= i || !paramTypes[i].isAssignableFrom(objectClass)) { return false; } i++; } if (arguments.context) { if (contextClass == null) { return false; } if (paramTypes.length <= i || !paramTypes[i].isAssignableFrom(contextClass)) { return false; } i++; } return paramTypes.length == i; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(action); if (objectClass != null) { sb.append(' '); sb.append(objectClass.getName()); } if (contextClass != null) { sb.append(' '); sb.append(contextClass.getName()); } return sb.toString(); } @Override public int hashCode() { return toString().hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof SignaturePattern) { SignaturePattern other = (SignaturePattern) obj; return ObjectUtils.nullSafeEquals(action, other.action) && ObjectUtils.nullSafeEquals(objectClass, other.objectClass) && ObjectUtils.nullSafeEquals(contextClass, other.contextClass); } return false; } } }