package org.jboss.seam.security;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.jboss.seam.annotations.intercept.AroundInvoke;
import org.jboss.seam.annotations.intercept.Interceptor;
import org.jboss.seam.annotations.intercept.InterceptorType;
import org.jboss.seam.annotations.security.PermissionCheck;
import org.jboss.seam.annotations.security.Restrict;
import org.jboss.seam.annotations.security.RoleCheck;
import org.jboss.seam.async.AsynchronousInterceptor;
import org.jboss.seam.intercept.AbstractInterceptor;
import org.jboss.seam.intercept.InvocationContext;
import org.jboss.seam.util.Strings;
/**
* Provides authorization services for component invocations.
*
* @author Shane Bryzak
*/
@Interceptor(type=InterceptorType.CLIENT,
around=AsynchronousInterceptor.class)
public class SecurityInterceptor extends AbstractInterceptor implements Serializable
{
private static final long serialVersionUID = -6567750187000766925L;
/**
* You may encounter a JVM bug where the field initializer is not evaluated for a transient field after deserialization.
* @see "http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6252102"
*/
private transient volatile Map<Method,Restriction> restrictions = new HashMap<Method,Restriction>();
private class Restriction
{
private String expression;
private String permissionTarget;
private String permissionAction;
private Map<String, Object> methodRestrictions;
private Map<Integer,Set<String>> paramRestrictions;
private Set<String> roleRestrictions;
public void setExpression(String expression)
{
this.expression = expression;
}
public void setPermissionTarget(String target)
{
this.permissionTarget = target;
}
public void setPermissionAction(String action)
{
this.permissionAction = action;
}
public void addMethodRestriction(Object target, String action)
{
if (methodRestrictions == null)
{
methodRestrictions = new HashMap<String, Object>();
}
methodRestrictions.put(action, target);
}
public void addRoleRestriction(String role)
{
if (roleRestrictions == null)
{
roleRestrictions = new HashSet<String>();
}
roleRestrictions.add(role);
}
public void addParameterRestriction(int index, String action)
{
Set<String> actions = null;
if (paramRestrictions == null)
{
paramRestrictions = new HashMap<Integer,Set<String>>();
}
if (!paramRestrictions.containsKey(index))
{
actions = new HashSet<String>();
paramRestrictions.put(index, actions);
}
else
{
actions = paramRestrictions.get(index);
}
actions.add(action);
}
public void check(Object[] parameters)
{
if (Identity.isSecurityEnabled())
{
if (expression != null)
{
Identity.instance().checkRestriction(expression);
}
if (methodRestrictions != null)
{
for (String action : methodRestrictions.keySet())
{
Identity.instance().checkPermission(methodRestrictions.get(action), action);
}
}
if (paramRestrictions != null)
{
for (Integer idx : paramRestrictions.keySet())
{
Set<String> actions = paramRestrictions.get(idx);
for (String action : actions)
{
Identity.instance().checkPermission(parameters[idx], action);
}
}
}
if (roleRestrictions != null)
{
for (String role : roleRestrictions)
{
Identity.instance().checkRole(role);
}
}
if (permissionTarget != null && permissionAction != null)
{
Identity.instance().checkPermission(permissionTarget, permissionAction);
}
}
}
}
@AroundInvoke
public Object aroundInvoke(InvocationContext invocation) throws Exception
{
Method interfaceMethod = invocation.getMethod();
if (!"hashCode".equals(interfaceMethod.getName()))
{
Restriction restriction = getRestriction(interfaceMethod);
if ( restriction != null ) restriction.check(invocation.getParameters());
}
return invocation.proceed();
}
private Restriction getRestriction(Method interfaceMethod) throws Exception
{
// see field declaration as to why this is done
if (restrictions == null)
{
synchronized(this)
{
restrictions = new HashMap<Method, Restriction>();
}
}
if (!restrictions.containsKey(interfaceMethod))
{
synchronized(restrictions)
{
// FIXME this logic should be abstracted rather than sitting in the middle of this interceptor
if (!restrictions.containsKey(interfaceMethod))
{
Restriction restriction = null;
Method method = getComponent().getBeanClass().getMethod(
interfaceMethod.getName(), interfaceMethod.getParameterTypes() );
Restrict restrict = null;
if ( method.isAnnotationPresent(Restrict.class) )
{
restrict = method.getAnnotation(Restrict.class);
}
else if ( getComponent().getBeanClass().isAnnotationPresent(Restrict.class) )
{
if ( !getComponent().isLifecycleMethod(method) )
{
restrict = getComponent().getBeanClass().getAnnotation(Restrict.class);
}
}
if (restrict != null)
{
if (restriction == null) restriction = new Restriction();
if ( Strings.isEmpty(restrict.value()) )
{
restriction.setPermissionTarget(getComponent().getName());
restriction.setPermissionAction(method.getName());
}
else
{
restriction.setExpression(restrict.value());
}
}
for (Annotation annotation : method.getDeclaringClass().getAnnotations())
{
if (annotation.annotationType().isAnnotationPresent(RoleCheck.class))
{
if (restriction == null) restriction = new Restriction();
restriction.addRoleRestriction(annotation.annotationType().getSimpleName().toLowerCase());
}
}
for (Annotation annotation : method.getAnnotations())
{
if (annotation.annotationType().isAnnotationPresent(PermissionCheck.class))
{
PermissionCheck permissionCheck = annotation.annotationType().getAnnotation(
PermissionCheck.class);
Method valueMethod = null;
for (Method m : annotation.annotationType().getDeclaredMethods())
{
valueMethod = m;
break;
}
if (valueMethod != null)
{
if (restriction == null) restriction = new Restriction();
Object target = valueMethod.invoke(annotation);
if (!target.equals(void.class))
{
restriction.addMethodRestriction(target,
getPermissionAction(permissionCheck, annotation));
}
}
}
if (annotation.annotationType().isAnnotationPresent(RoleCheck.class))
{
if (restriction == null) restriction = new Restriction();
restriction.addRoleRestriction(annotation.annotationType().getSimpleName().toLowerCase());
}
}
for (int i = 0; i < method.getParameterAnnotations().length; i++)
{
Annotation[] annotations = method.getParameterAnnotations()[i];
for (Annotation annotation : annotations)
{
if (annotation.annotationType().isAnnotationPresent(PermissionCheck.class))
{
PermissionCheck permissionCheck = annotation.annotationType().getAnnotation(
PermissionCheck.class);
if (restriction == null) restriction = new Restriction();
restriction.addParameterRestriction(i,
getPermissionAction(permissionCheck, annotation));
}
}
}
restrictions.put(interfaceMethod, restriction);
return restriction;
}
}
}
return restrictions.get(interfaceMethod);
}
private String getPermissionAction(PermissionCheck check, Annotation annotation)
{
if (!"".equals(check.value()))
{
return check.value();
}
else
{
return annotation.annotationType().getSimpleName().toLowerCase();
}
}
public boolean isInterceptorEnabled()
{
return getComponent().isSecure() && !getComponent().beanClassHasAnnotation("javax.jws.WebService");
}
}