Package grails.plugin.springsecurity.web.access.intercept

Source Code of grails.plugin.springsecurity.web.access.intercept.AnnotationFilterInvocationDefinition

/* Copyright 2006-2014 SpringSource.
*
* 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 grails.plugin.springsecurity.web.access.intercept;

import grails.plugin.springsecurity.InterceptedUrl;
import grails.plugin.springsecurity.access.vote.ClosureConfigAttribute;
import grails.web.UrlConverter;
import groovy.lang.Closure;

import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletContext;
import org.codehaus.groovy.grails.commons.ControllerArtefactHandler;
import org.codehaus.groovy.grails.commons.GrailsApplication;
import org.codehaus.groovy.grails.commons.GrailsClass;
import org.codehaus.groovy.grails.commons.GrailsControllerClass;
import org.codehaus.groovy.grails.plugins.web.api.ResponseMimeTypesApi;
import grails.util.Holders;
import org.codehaus.groovy.grails.web.mapping.UrlMappingInfo;
import org.codehaus.groovy.grails.web.mapping.UrlMappingsHolder;
import org.codehaus.groovy.grails.web.servlet.mvc.GrailsParameterMap;
import org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequest;
import org.codehaus.groovy.grails.web.util.WebUtils;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

/**
* A {@link FilterInvocationSecurityMetadataSource} that uses rules defined with
* Controller annotations combined with static rules defined in
* <code>SecurityConfig.groovy</code>, e.g. for js, images, css or for rules
* that cannot be expressed in a controller like '/**'.
*
* @author <a href='mailto:burt@burtbeckwith.com'>Burt Beckwith</a>
*/
public class AnnotationFilterInvocationDefinition extends AbstractFilterInvocationDefinition {

   protected static final String SLASH = "/";

  protected UrlMappingsHolder urlMappingsHolder;
  protected GrailsApplication application;
  protected UrlConverter grailsUrlConverter;
  protected ResponseMimeTypesApi responseMimeTypesApi;

  @Override
  protected String determineUrl(final FilterInvocation filterInvocation) {
    HttpServletRequest request = filterInvocation.getHttpRequest();
    HttpServletResponse response = filterInvocation.getHttpResponse();

    GrailsWebRequest existingRequest;
    try {
      existingRequest = WebUtils.retrieveGrailsWebRequest();
    }
    catch (IllegalStateException e) {
      throw new IllegalStateException(
        "There was a problem retrieving the current GrailsWebRequest. This usually indicates a filter ordering " +
        "issue in web.xml (the 'springSecurityFilterChain' filter-mapping element must be positioned after the " +
        "'grailsWebRequest' element when using @Secured annotations) but this should be handled correctly by the " +
        "webxml plugin. Ensure that the webxml plugin is installed (it should be transitively installed as a " +
        "dependency of the spring-security-core plugin)");
    }

    String requestUrl = calculateUri(request);

    String url = null;
    try {
                        javax.servlet.ServletContext servletContext = (ServletContext)grails.util.Holders.getServletContext();
                               // servlet.ServletContext  =
      GrailsWebRequest grailsRequest = new GrailsWebRequest(request, response,servletContext );
      WebUtils.storeGrailsWebRequest(grailsRequest);

      Map<String, Object> savedParams = copyParams(grailsRequest);

      UrlMappingInfo[] urlInfos;
      if (grails23Plus) {
        urlInfos = grails.plugin.springsecurity.ReflectionUtils.matchAllUrlMappings(urlMappingsHolder, requestUrl, grailsRequest, responseMimeTypesApi);
      }
      else {
        urlInfos = urlMappingsHolder.matchAll(requestUrl);
      }

      for (UrlMappingInfo mapping : urlInfos) {
        if (grails23Plus && grails.plugin.springsecurity.ReflectionUtils.isRedirect(mapping)) {
          break;
        }

        configureMapping(mapping, grailsRequest, savedParams);

        url = findGrailsUrl(mapping);
        if (url != null) {
          break;
        }
      }
    }
    finally {
      if (existingRequest == null) {
        WebUtils.clearGrailsWebRequest();
      }
      else {
        WebUtils.storeGrailsWebRequest(existingRequest);
      }
    }

    if (!StringUtils.hasLength(url)) {
      // probably css/js/image
      url = requestUrl;
    }

    return lowercaseAndStripQuerystring(url);
  }

  protected String findGrailsUrl(final UrlMappingInfo mapping) {

    String uri = mapping.getURI();
    if (StringUtils.hasLength(uri)) {
      return uri;
    }

    String viewName = mapping.getViewName();
    if (viewName != null) {
      if (!viewName.startsWith(SLASH)) {
        viewName = SLASH + viewName;
      }
      return viewName;
    }

    String actionName = mapping.getActionName();
    if (!StringUtils.hasLength(actionName)) {
      actionName = "";
    }

    String controllerName = mapping.getControllerName();

    if (isController(controllerName, actionName)) {
      return createControllerUri(controllerName, actionName);
    }
   
    if (grails23Plus && controllerName != null) {
      String namespace = mapping.getNamespace();
      if(namespace != null) {
        String fullControllerName = resolveFullControllerName(controllerName, namespace);
        return createControllerUri(fullControllerName, actionName);
      }
    }

    return null;
  }
 
  protected String createControllerUri(String controllerName, String actionName) {
    if (!StringUtils.hasLength(actionName) || "null".equals(actionName)) {
      actionName = "index";
    }
    return (SLASH + controllerName + SLASH + actionName).trim();
  }

  protected boolean isController(final String controllerName, final String actionName) {
    return application.getArtefactForFeature(ControllerArtefactHandler.TYPE,
        SLASH + controllerName + SLASH + actionName) != null;
  }

  protected void configureMapping(final UrlMappingInfo mapping, final GrailsWebRequest grailsRequest,
      final Map<String, Object> savedParams) {

    // reset params since mapping.configure() sets values
    GrailsParameterMap params = grailsRequest.getParams();
    params.clear();
    params.putAll(savedParams);

    mapping.configure(grailsRequest);
  }

  @SuppressWarnings("unchecked")
  protected Map<String, Object> copyParams(final GrailsWebRequest grailsRequest) {
    return new LinkedHashMap<String, Object>(grailsRequest.getParams());
  }

  /**
   * Called by the plugin to set controller role info.<br/>
   *
   * Reinitialize by calling <code>ctx.objectDefinitionSource.initialize(
   *   ctx.authenticateService.securityConfig.security.annotationStaticRules,
   *   ctx.grailsUrlMappingsHolder,
   *   grailsApplication.controllerClasses)</code>
   *
   * @param staticRules data from the controllerAnnotations.staticRules config attribute
   * @param mappingsHolder mapping holder
   * @param controllerClasses all controllers
   */
  public void initialize(final Object staticRules,
      final UrlMappingsHolder mappingsHolder, final GrailsClass[] controllerClasses) {

    Assert.notNull(staticRules, "staticRules map is required");
    Assert.notNull(mappingsHolder, "urlMappingsHolder is required");

    Map<String, List<InterceptedUrl>> actionRoleMap = new LinkedHashMap<String, List<InterceptedUrl>>();
    List<InterceptedUrl> classRoleMap = new ArrayList<InterceptedUrl>();
    Map<String, List<InterceptedUrl>> actionClosureMap = new LinkedHashMap<String, List<InterceptedUrl>>();
    List<InterceptedUrl> classClosureMap = new ArrayList<InterceptedUrl>();

    resetConfigs();

    urlMappingsHolder = mappingsHolder;

    for (GrailsClass controllerClass : controllerClasses) {
      findControllerAnnotations((GrailsControllerClass)controllerClass, actionRoleMap, classRoleMap, actionClosureMap, classClosureMap);
    }

    compileStaticRules(staticRules);
    compileActionClosureMap(actionClosureMap);
    compileClassClosureMap(classClosureMap);
    compileActionMap(actionRoleMap);
    compileClassMap(classRoleMap);

    if (log.isTraceEnabled()) {
      log.trace("configs: {}", getConfigAttributeMap());
    }
  }

  protected void compileActionMap(final Map<String, List<InterceptedUrl>> map) {
    for (Map.Entry<String, List<InterceptedUrl>> controllerEntry : map.entrySet()) {
      String controllerName = controllerEntry.getKey();
      for (InterceptedUrl iu : controllerEntry.getValue()) {
        Collection<ConfigAttribute> configAttributes = iu.getConfigAttributes();
        String actionName = iu.getPattern();
        HttpMethod method = iu.getHttpMethod();
        storeMapping(controllerName, actionName, configAttributes, false, method);
        if (actionName.endsWith("Flow")) {
          // WebFlow actions end in Flow but are accessed without the suffix, so guard both
          storeMapping(controllerName, actionName.substring(0, actionName.length() - 4), configAttributes, false, method);
        }
      }
    }
  }

  protected void compileActionClosureMap(final Map<String, List<InterceptedUrl>> map) {
    for (Map.Entry<String, List<InterceptedUrl>> controllerEntry : map.entrySet()) {
      String controllerName = controllerEntry.getKey();
      List<InterceptedUrl> actionClosures = controllerEntry.getValue();
      for (InterceptedUrl iu : actionClosures) {
        String actionName = iu.getPattern();
        Class<?> closureClass = iu.getClosureClass();
        HttpMethod method = iu.getHttpMethod();
        storeMapping(controllerName, actionName, closureClass, method);
        if (actionName.endsWith("Flow")) {
          // WebFlow actions end in Flow but are accessed without the suffix, so guard both
          storeMapping(controllerName, actionName.substring(0, actionName.length() - 4), closureClass, method);
        }
      }
    }
  }

  protected void compileClassMap(final List<InterceptedUrl> classRoleMap) {
    for (InterceptedUrl iu : classRoleMap) {
      storeMapping(iu.getPattern(), null, iu.getConfigAttributes(), false, iu.getHttpMethod());
    }
  }

  protected void compileClassClosureMap(final List<InterceptedUrl> classClosureMap) {
    for (InterceptedUrl iu : classClosureMap) {
      storeMapping(iu.getPattern(), null, iu.getClosureClass(), iu.getHttpMethod());
    }
  }

  protected Closure<?> newInstance(final Class<?> closureClass) {
    try {
      Constructor<?> constructor = closureClass.getConstructor(Object.class, Object.class);
      ReflectionUtils.makeAccessible(constructor);
      return (Closure<?>) constructor.newInstance(this, this);
    }
    catch (NoSuchMethodException e) {
      ReflectionUtils.handleReflectionException(e);
    }
    catch (InstantiationException e) {
      ReflectionUtils.handleReflectionException(e);
    }
    catch (IllegalAccessException e) {
      ReflectionUtils.handleReflectionException(e);
    }
    catch (InvocationTargetException e) {
      ReflectionUtils.handleInvocationTargetException(e);
    }
    return null;
  }

  @SuppressWarnings("unchecked")
  protected void compileStaticRules(final Object staticRules) {
    List<InterceptedUrl> rules;
    if (staticRules instanceof Map) {
      rules = grails.plugin.springsecurity.ReflectionUtils.splitMap((Map<String, Object>)staticRules);
    }
    else if (staticRules instanceof List) {
      rules = grails.plugin.springsecurity.ReflectionUtils.splitMap((List<Map<String, Object>>)staticRules);
    }
    else {
      return;
    }

    for (InterceptedUrl iu : rules) {
      storeMapping(iu.getPattern(), null, iu.getConfigAttributes(), true, iu.getHttpMethod());
    }
  }

  protected void storeMapping(final String controllerNameOrPattern, final String actionName,
      final Collection<ConfigAttribute> configAttributes, final boolean isPattern, final HttpMethod method) {

    for (String pattern : generatePatterns(controllerNameOrPattern, actionName, isPattern)) {
      doStoreMapping(pattern, method, configAttributes);
    }
  }

  protected void storeMapping(final String controllerName, final String actionName, final Class<?> closureClass, final HttpMethod method) {
    if (closureClass == grails.plugin.springsecurity.annotation.Secured.class) {
      return;
    }

    for (String pattern : generatePatterns(controllerName, actionName, false)) {
      Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
      configAttributes.add(new ClosureConfigAttribute(newInstance(closureClass)));

      String key = pattern.toLowerCase();
      InterceptedUrl replaced = storeMapping(key, method, configAttributes);
      if (replaced != null) {
        log.warn("replaced rule for '{}' with tokens {} with tokens {}", new Object[] { key, replaced.getConfigAttributes(), configAttributes });
      }
    }
  }

  protected List<String> generatePatterns(final String controllerNameOrPattern, final String actionName, final boolean isPattern) {

    List<String> patterns = new ArrayList<String>();

    if (isPattern) {
      patterns.add(controllerNameOrPattern);
    }
    else {
      StringBuilder sb = new StringBuilder();
      sb.append('/').append(controllerNameOrPattern);
      if (actionName != null) {
        sb.append('/').append(actionName);
      }
      patterns.add(sb.toString());
      patterns.add(sb.toString() + ".*");

      sb.append("/**");
      patterns.add(sb.toString());
    }

    return patterns;
  }

  protected void doStoreMapping(final String fullPattern, final HttpMethod method, final Collection<ConfigAttribute> configAttributes) {

    String key = fullPattern.toString().toLowerCase();
    InterceptedUrl replaced = storeMapping(key, method, configAttributes);
    if (replaced != null) {
      log.warn("replaced rule for '" + key + "' with tokens " + replaced.getConfigAttributes() +
          " with tokens " + configAttributes);
    }
  }

  protected void findControllerAnnotations(final GrailsControllerClass controllerClass,
      final Map<String, List<InterceptedUrl>> actionRoleMap,
      final List<InterceptedUrl> classRoleMap,
      final Map<String, List<InterceptedUrl>> actionClosureMap,
      final List<InterceptedUrl> classClosureMap) {

    Class<?> clazz = controllerClass.getClazz();
    String controllerName = resolveFullControllerName(controllerClass);

    Annotation annotation = clazz.getAnnotation(org.springframework.security.access.annotation.Secured.class);
    if (annotation == null) {
      annotation = clazz.getAnnotation(grails.plugin.springsecurity.annotation.Secured.class);
      if (annotation != null) {
        Class<?> closureClass = findClosureClass((grails.plugin.springsecurity.annotation.Secured)annotation);
        if (closureClass == null) {
          classRoleMap.add(new InterceptedUrl(controllerName, getValue(annotation), getHttpMethod(annotation)));
        }
        else {
          classClosureMap.add(new InterceptedUrl(controllerName, closureClass, getHttpMethod(annotation)));
        }
      }
    }
    else {
      classRoleMap.add(new InterceptedUrl(controllerName, getValue(annotation), null));
    }

    List<InterceptedUrl> annotatedActionNames = findActionRoles(clazz);
    if (annotatedActionNames != null && !annotatedActionNames.isEmpty()) {
      actionRoleMap.put(controllerName, annotatedActionNames);
    }

    List<InterceptedUrl> closureAnnotatedActionNames = findActionClosures(clazz);
    if (closureAnnotatedActionNames != null && !closureAnnotatedActionNames.isEmpty()) {
      actionClosureMap.put(controllerName, closureAnnotatedActionNames);
    }
  }

  protected String resolveFullControllerName(
      final GrailsControllerClass controllerClass) {
    String controllerName = controllerClass.getName();
    String namespace = null;
    if (grails23Plus) {
      namespace = controllerClass.getNamespace();
      if (namespace != null) {
        namespace = grailsUrlConverter.toUrlElement(namespace);
      }
    }
    return resolveFullControllerName(grailsUrlConverter.toUrlElement(controllerName), namespace);
  }

  protected String resolveFullControllerName(String controllerNameInUrlFormat,
      String namespaceInUrlFormat) {
    StringBuilder fullControllerName = new StringBuilder();
    if (namespaceInUrlFormat != null) {
      fullControllerName.append(namespaceInUrlFormat).append(":");
    }
    fullControllerName.append(controllerNameInUrlFormat);
    return fullControllerName.toString();
  }

  protected List<InterceptedUrl> findActionRoles(final Class<?> clazz) {
    List<InterceptedUrl> actionRoles = new ArrayList<InterceptedUrl>();
    for (Method method : clazz.getDeclaredMethods()) {
      Annotation annotation = findSecuredAnnotation(method);
      if (annotation != null) {
        Collection<String> values = getValue(annotation);
        if (!values.isEmpty()) {
          actionRoles.add(new InterceptedUrl(grailsUrlConverter.toUrlElement(method.getName()), values, getHttpMethod(annotation)));
        }
      }
    }
    return actionRoles;
  }

  protected List<InterceptedUrl> findActionClosures(final Class<?> clazz) {
    List<InterceptedUrl> actionClosures = new ArrayList<InterceptedUrl>();
    for (Method method : clazz.getDeclaredMethods()) {
      grails.plugin.springsecurity.annotation.Secured annotation = method.getAnnotation(grails.plugin.springsecurity.annotation.Secured.class);
      if (annotation != null && annotation.closure() != grails.plugin.springsecurity.annotation.Secured.class) {
        actionClosures.add(new InterceptedUrl(grailsUrlConverter.toUrlElement(method.getName()), annotation.closure(), getHttpMethod(annotation)));
      }
    }
    return actionClosures;
  }

  protected Class<?> findClosureClass(final grails.plugin.springsecurity.annotation.Secured annotation) {
    Class<?> closureClass = annotation.closure();
    return closureClass == grails.plugin.springsecurity.annotation.Secured.class ? null : closureClass;
  }

  protected Annotation findSecuredAnnotation(final AccessibleObject annotatedTarget) {
    Annotation annotation;
    annotation = annotatedTarget.getAnnotation(grails.plugin.springsecurity.annotation.Secured.class);
    if (annotation != null) {
      return annotation;
    }
    annotation = annotatedTarget.getAnnotation(org.springframework.security.access.annotation.Secured.class);
    if (annotation != null) {
      return annotation;
    }
    return null;
  }

  protected Collection<String> getValue(final Annotation annotation) {
    String[] strings;
    if (annotation instanceof grails.plugin.springsecurity.annotation.Secured) {
      strings = ((grails.plugin.springsecurity.annotation.Secured)annotation).value();
    }
    else {
      strings = ((org.springframework.security.access.annotation.Secured)annotation).value();
    }
    return new LinkedHashSet<String>(Arrays.asList(strings));
  }

  protected HttpMethod getHttpMethod(final Annotation annotation) {
    String method = null;
    if (annotation instanceof grails.plugin.springsecurity.annotation.Secured) {
      method = ((grails.plugin.springsecurity.annotation.Secured)annotation).httpMethod();
      if (grails.plugin.springsecurity.annotation.Secured.ANY_METHOD.equals(method)) {
        method = null;
      }
    }
    return method == null ? null : HttpMethod.valueOf(method);
  }

  /**
   * Dependency injection for the application.
   * @param app the application
   */
  public void setApplication(GrailsApplication app) {
    application = app;
  }

  /**
   * Dependency injection for the grailsUrlConverter bean.
   * @param urlConverter the converter
   */
  public void setGrailsUrlConverter(UrlConverter urlConverter) {
    grailsUrlConverter = urlConverter;
  }

  /**
   * Dependency injection for the responseMimeTypesApi bean.
   * @param api the bean
   */
  public void setResponseMimeTypesApi(ResponseMimeTypesApi api) {
    responseMimeTypesApi = api;
  }
}
TOP

Related Classes of grails.plugin.springsecurity.web.access.intercept.AnnotationFilterInvocationDefinition

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.