Package org.jboss.seam.security

Source Code of org.jboss.seam.security.SecurityExtension

package org.jboss.seam.security;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessSessionBean;
import javax.enterprise.inject.spi.SessionBeanType;
import javax.enterprise.util.Nonbinding;
import javax.interceptor.InvocationContext;

import org.jboss.seam.security.annotations.Secures;
import org.jboss.seam.security.annotations.SecurityBindingType;
import org.jboss.seam.security.annotations.SecurityParameterBinding;
import org.jboss.solder.reflection.annotated.AnnotatedTypeBuilder;
import org.jboss.solder.reflection.annotated.InjectableMethod;
import org.jboss.solder.reflection.annotated.ParameterValueRedefiner;

/**
* Extension for typesafe security annotations
*
* @author Shane Bryzak
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
*/
public class SecurityExtension implements Extension {
    private BeanManager beanManager;

    /**
     * Responsible for supplying requested method invocation values to the security binding method.
     */
    public class SecurityParameterValueRedefiner implements ParameterValueRedefiner {
        private CreationalContext<?> creationalContext;
        private InvocationContext invocation;

        public SecurityParameterValueRedefiner(CreationalContext<?> creationalContext, InvocationContext invocation) {
            this.invocation = invocation;
            this.creationalContext = creationalContext;
        }

        @Override
        public Object redefineParameterValue(ParameterValue value) {
            Object result = value.getDefaultValue(creationalContext);

            InjectionPoint injectionPoint = value.getInjectionPoint();
            if (injectionPoint != null) {
                Annotated securingParameterAnnotatedType = injectionPoint.getAnnotated();
                Set<Annotation> securingParameterAnnotations = securingParameterAnnotatedType.getAnnotations();

                Set<Annotation> requiredBindingAnnotations = new HashSet<Annotation>();
                for (Annotation annotation : securingParameterAnnotations) {
                    if (annotation.annotationType().isAnnotationPresent(SecurityParameterBinding.class)) {
                        requiredBindingAnnotations.add(annotation);
                    }
                }

                if (!requiredBindingAnnotations.isEmpty()) {
                    Annotation[][] businessMethodParameterAnnotations = invocation.getMethod().getParameterAnnotations();
                    for (int i = 0; i < businessMethodParameterAnnotations.length; i++) {
                        List<Annotation> businessParameterAnnotations = Arrays.asList(businessMethodParameterAnnotations[i]);
                        for (Annotation annotation : requiredBindingAnnotations) {
                            if (businessParameterAnnotations.contains(annotation)) {
                                return invocation.getParameters()[i];
                            }
                        }
                    }
                }
            }

            return result;
        }
    }

    class Authorizer {
        private Annotation binding;
        private Map<Method, Object> memberValues = new HashMap<Method, Object>();

        private AnnotatedMethod<?> implementationMethod;
        private Bean<?> targetBean;

        private InjectableMethod<?> injectableMethod;

        public Authorizer(Annotation binding, AnnotatedMethod<?> implementationMethod) {
            this.binding = binding;
            this.implementationMethod = implementationMethod;

            try {
                for (Method m : binding.annotationType().getDeclaredMethods()) {
                    if (m.isAnnotationPresent(Nonbinding.class))
                        continue;
                    memberValues.put(m, m.invoke(binding));
                }
            } catch (InvocationTargetException ex) {
                throw new SecurityDefinitionException("Error reading security binding members", ex);
            } catch (IllegalAccessException ex) {
                throw new SecurityDefinitionException("Error reading security binding members", ex);
            }
        }

        public void authorize() {
            if (targetBean == null) {
                lookupTargetBean();
            }

            CreationalContext<?> cc = beanManager.createCreationalContext(targetBean);

            Object reference = beanManager.getReference(targetBean, implementationMethod.getJavaMember().getDeclaringClass(),
                    cc);

            Object result = injectableMethod.invoke(reference, cc, null);

            if (result.equals(Boolean.FALSE)) {
                throw new AuthorizationException("Authorization check failed");
            }
        }

        public void authorize(InvocationContext context) {
            if (targetBean == null) {
                lookupTargetBean();
            }

            CreationalContext<?> cc = beanManager.createCreationalContext(targetBean);

            Object reference = beanManager.getReference(targetBean, implementationMethod.getJavaMember().getDeclaringClass(),
                    cc);

            Object result = injectableMethod.invoke(reference, cc, new SecurityParameterValueRedefiner(cc, context));

            if (result.equals(Boolean.FALSE)) {
                throw new AuthorizationException("Authorization check failed");
            }
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        private synchronized void lookupTargetBean() {
            if (targetBean == null) {
                Method m = implementationMethod.getJavaMember();

                Set<Bean<?>> beans = beanManager.getBeans(m.getDeclaringClass());
                if (beans.size() == 1) {
                    targetBean = beans.iterator().next();
                } else if (beans.isEmpty()) {
                    throw new IllegalStateException("Exception looking up authorizer method bean - "
                            + "no beans found for method [" + m.getDeclaringClass() + "." + m.getName() + "]");
                } else if (beans.size() > 1) {
                    throw new IllegalStateException("Exception looking up authorizer method bean - "
                            + "multiple beans found for method [" + m.getDeclaringClass().getName() + "." + m.getName() + "]");
                }

                injectableMethod = new InjectableMethod(implementationMethod, targetBean, beanManager);
            }
        }

        public boolean matchesBinding(Annotation annotation) {
            if (!annotation.annotationType().equals(binding.annotationType())) {
                return false;
            }

            for (Method m : annotation.annotationType().getDeclaredMethods()) {
                if (m.isAnnotationPresent(Nonbinding.class))
                    continue;

                if (!memberValues.containsKey(m)) {
                    return false;
                }

                try {
                    Object value = m.invoke(annotation);
                    if (!memberValues.get(m).equals(value)) {
                        return false;
                    }
                } catch (InvocationTargetException ex) {
                    throw new SecurityDefinitionException("Error reading security binding members", ex);
                } catch (IllegalAccessException ex) {
                    throw new SecurityDefinitionException("Error reading security binding members", ex);
                }
            }

            return true;
        }

        public Method getImplementationMethod() {
            return implementationMethod.getJavaMember();
        }

        @Override
        public boolean equals(Object value) {
            return false;
        }

        @Override
        public int hashCode() {
            return 0;
        }
    }

    /**
     * Contains all known authorizers
     */
    private Set<Authorizer> authorizers = new HashSet<Authorizer>();

    /**
     * Contains all known secured types
     */
    private Set<AnnotatedType<?>> securedTypes = new HashSet<AnnotatedType<?>>();

    /**
     * A mapping between a secured method of a class and its authorizers
     */
    private Map<Class<?>, Map<Method, Set<Authorizer>>> methodAuthorizers = new HashMap<Class<?>, Map<Method, Set<Authorizer>>>();

    /**
     * @param <X>
     * @param event
     * @param beanManager
     */
    public <X> void processAnnotatedType(@Observes ProcessAnnotatedType<X> event, final BeanManager beanManager) {
        AnnotatedTypeBuilder<X> builder = null;
        AnnotatedType<X> type = event.getAnnotatedType();

        boolean isSecured = false;

        // Add the security interceptor to the class if the class is annotated
        // with a security binding type
        for (final Annotation annotation : type.getAnnotations()) {
            if (annotation.annotationType().isAnnotationPresent(SecurityBindingType.class)) {
                builder = new AnnotatedTypeBuilder<X>().readFromType(type);
                builder.addToClass(SecurityInterceptorBindingLiteral.INSTANCE);
                isSecured = true;
            }
        }

        // If the class isn't annotated with a security binding type, check if
        // any of its methods are, and if so, add the security interceptor to the
        // method
        if (!isSecured) {
            for (final AnnotatedMethod<? super X> m : type.getMethods()) {
                if (m.isAnnotationPresent(Secures.class)) {
                    registerAuthorizer(m);
                    continue;
                }

                for (final Annotation annotation : m.getAnnotations()) {
                    if (annotation.annotationType().isAnnotationPresent(SecurityBindingType.class)) {
                        if (builder == null) {
                            builder = new AnnotatedTypeBuilder<X>().readFromType(type);
                        }
                        builder.addToMethod(m, SecurityInterceptorBindingLiteral.INSTANCE);
                        isSecured = true;
                        break;
                    }
                }
            }
        }

        // If either the bean or any of its methods are secured, register it
        if (isSecured) {
            securedTypes.add(type);
        }

        if (builder != null) {
            event.setAnnotatedType(builder.create());
        }
    }

    public void validateBindings(@Observes AfterBeanDiscovery event, BeanManager beanManager) {
        this.beanManager = beanManager;

        for (final AnnotatedType<?> type : securedTypes) {
            // Here we simply want to validate that each type that is annotated with
            // one or more security bindings has a valid authorizer for each binding

            for (final Annotation annotation : type.getJavaClass().getAnnotations()) {
                boolean found = false;

                if (annotation.annotationType().isAnnotationPresent(SecurityBindingType.class)) {
                    // Validate the authorizer
                    for (Authorizer auth : authorizers) {
                        if (auth.matchesBinding(annotation)) {
                            found = true;
                            break;
                        }
                    }

                    if (!found) {
                        event.addDefinitionError(new SecurityDefinitionException("Secured type "
                                + type.getJavaClass().getName() + " has no matching authorizer method for security binding @"
                                + annotation.annotationType().getName()));
                    }
                }
            }

            for (final AnnotatedMethod<?> method : type.getMethods()) {
                for (final Annotation annotation : method.getAnnotations()) {
                    if (annotation.annotationType().isAnnotationPresent(SecurityBindingType.class)) {
                        registerSecuredMethod(method.getJavaMember(), type.getJavaClass());
                        break;
                    }
                }
            }
        }

        // Clear securedTypes, we don't require it any more
        securedTypes.clear();
        securedTypes = null;
    }

    /**
     * This method is invoked by the security interceptor to obtain the authorizer stack for a secured method
     *
     * @param m
     * @return
     */
    public Set<Authorizer> lookupAuthorizerStack(Method m, Class<?> targetClass) {
        if (!methodAuthorizers.containsKey(targetClass) || !methodAuthorizers.get(targetClass).containsKey(m)) {
            registerSecuredMethod(m, targetClass);
        }

        return methodAuthorizers.get(targetClass).get(m);
    }

    void checkAuthorization(Annotation binding) {
        boolean authorized = false;

        for (Authorizer authorizer : authorizers) {
            if (authorizer.matchesBinding(binding)) {
                authorizer.authorize();
                authorized = true;
            }
        }

        if (!authorized) {
            throw new AuthorizationException("Failed to process authorization request - no matching authorizer "
                    + "method for specified binding type [" + binding.annotationType().getClass().getName() + "]");
        }
    }

    protected synchronized void registerSecuredMethod(Method method, Class<?> targetClass) {
        if (!methodAuthorizers.containsKey(targetClass)) {
            methodAuthorizers.put(targetClass, new HashMap<Method, Set<Authorizer>>());
        }

        Map<Method, Set<Authorizer>> authz = methodAuthorizers.get(targetClass);

        if (!authz.containsKey(method)) {
            // Build a list of all security bindings on both the method and its declaring class
            Set<Annotation> bindings = new HashSet<Annotation>();

            Class<?> cls = targetClass;
            while (!cls.equals(Object.class)) {
                for (final Annotation annotation : cls.getAnnotations()) {
                    if (annotation.annotationType().isAnnotationPresent(SecurityBindingType.class)) {
                        bindings.add(annotation);
                    }
                }
                cls = cls.getSuperclass();
            }

            for (final Annotation annotation : method.getAnnotations()) {
                if (annotation.annotationType().isAnnotationPresent(SecurityBindingType.class)) {
                    bindings.add(annotation);
                }
            }

            Set<Authorizer> authorizerStack = new HashSet<Authorizer>();

            for (Annotation binding : bindings) {
                boolean found = false;

                // For each security binding, find a valid authorizer
                for (Authorizer authorizer : authorizers) {
                    if (authorizer.matchesBinding(binding)) {
                        if (found) {
                            StringBuilder sb = new StringBuilder();
                            sb.append("Matching authorizer methods found: [");
                            sb.append(authorizer.getImplementationMethod().getDeclaringClass().getName());
                            sb.append(".");
                            sb.append(authorizer.getImplementationMethod().getName());
                            sb.append("]");

                            for (Authorizer a : authorizerStack) {
                                if (a.matchesBinding(binding)) {
                                    sb.append(", [");
                                    sb.append(a.getImplementationMethod().getDeclaringClass().getName());
                                    sb.append(".");
                                    sb.append(a.getImplementationMethod().getName());
                                    sb.append("]");
                                }
                            }

                            throw new SecurityDefinitionException("Ambiguous authorizers found for security binding type [@"
                                    + binding.annotationType().getName() + "] on method ["
                                    + method.getDeclaringClass().getName() + "." + method.getName() + "]. " + sb.toString());
                        }

                        authorizerStack.add(authorizer);
                        found = true;
                    }
                }

                if (!found) {
                    throw new SecurityDefinitionException("No matching authorizer found for security binding type [@"
                            + binding.annotationType().getName() + "] on method [" + method.getDeclaringClass().getName() + "."
                            + method.getName() + "].");
                }
            }
            authz.put(method, authorizerStack);
        }
    }

    /**
     * Registers the specified authorizer method (i.e. a method annotated with the @Secures annotation)
     *
     * @param m
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    protected void registerAuthorizer(AnnotatedMethod<?> m) {
        if (!m.getJavaMember().getReturnType().equals(Boolean.class) && !m.getJavaMember().getReturnType().equals(Boolean.TYPE)) {
            throw new SecurityDefinitionException("Invalid authorizer method ["
                    + m.getJavaMember().getDeclaringClass().getName() + "." + m.getJavaMember().getName()
                    + "] - does not return a boolean.");
        }

        // Locate the binding type
        Annotation binding = null;

        for (Annotation a : m.getAnnotations()) {
            if (a.annotationType().isAnnotationPresent(SecurityBindingType.class)) {
                if (binding != null) {
                    throw new SecurityDefinitionException("Invalid authorizer method ["
                            + m.getJavaMember().getDeclaringClass().getName() + "." + m.getJavaMember().getName()
                            + "] - declares multiple security binding types");
                }
                binding = a;
            }
        }

        Authorizer authorizer = new Authorizer(binding, m);
        authorizers.add(authorizer);
    }

    /**
     * Ensures that any implementations of the Authenticator interface are not stateless session beans.
     *
     * @param event
     */
    public void validateAuthenticatorImplementation(@Observes ProcessSessionBean<Authenticator> event) {
        if (SessionBeanType.STATELESS.equals(event.getSessionBeanType())) {
            event.addDefinitionError(new IllegalStateException("Authenticator " + event.getBean().getClass()
                    + " cannot be a Stateless Session Bean"));
        }
    }
}
TOP

Related Classes of org.jboss.seam.security.SecurityExtension

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.