Browse Source

SEC-2357: Fix package cycles

Rob Winch 12 years ago
parent
commit
45ad74a0bd

+ 137 - 9
web/src/main/java/org/springframework/security/web/util/AntPathRequestMatcher.java

@@ -14,7 +14,12 @@ package org.springframework.security.web.util;
 
 import javax.servlet.http.HttpServletRequest;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.http.HttpMethod;
 import org.springframework.util.AntPathMatcher;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
 
 /**
  * Matcher which compares a pre-defined ant-style pattern against the URL
@@ -39,7 +44,13 @@ import org.springframework.util.AntPathMatcher;
  * @see org.springframework.util.AntPathMatcher
  */
 public final class AntPathRequestMatcher implements RequestMatcher {
-    private final org.springframework.security.web.util.matchers.AntPathRequestMatcher delegate;
+    private static final Log logger = LogFactory.getLog(AntPathRequestMatcher.class);
+    private static final String MATCH_ALL = "/**";
+
+    private final Matcher matcher;
+    private final String pattern;
+    private final HttpMethod httpMethod;
+    private final boolean caseSensitive;
 
     /**
      * Creates a matcher with the specific pattern which will match all HTTP
@@ -79,7 +90,28 @@ public final class AntPathRequestMatcher implements RequestMatcher {
      *            true if the matcher should consider case, else false
      */
     public AntPathRequestMatcher(String pattern, String httpMethod, boolean caseSensitive) {
-        this.delegate = new org.springframework.security.web.util.matchers.AntPathRequestMatcher(pattern, httpMethod, caseSensitive);
+        Assert.hasText(pattern, "Pattern cannot be null or empty");
+        this.caseSensitive = caseSensitive;
+
+        if (pattern.equals(MATCH_ALL) || pattern.equals("**")) {
+            pattern = MATCH_ALL;
+            matcher = null;
+        } else {
+            if(!caseSensitive) {
+                pattern = pattern.toLowerCase();
+            }
+
+            // If the pattern ends with {@code /**} and has no other wildcards, then optimize to a sub-path match
+            if (pattern.endsWith(MATCH_ALL) && pattern.indexOf('?') == -1 &&
+                    pattern.indexOf("*") == pattern.length() - 2) {
+                matcher = new SubpathMatcher(pattern.substring(0, pattern.length() - 3));
+            } else {
+                matcher = new SpringAntMatcher(pattern);
+            }
+        }
+
+        this.pattern = pattern;
+        this.httpMethod = StringUtils.hasText(httpMethod) ? HttpMethod.valueOf(httpMethod) : null;
     }
 
     /**
@@ -89,29 +121,125 @@ public final class AntPathRequestMatcher implements RequestMatcher {
      *    {@code servletPath} + {@code pathInfo} of the request.
      */
     public boolean matches(HttpServletRequest request) {
-        return this.delegate.matches(request);
+        if (httpMethod != null && request.getMethod() != null && httpMethod != HttpMethod.valueOf(request.getMethod())) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Request '" + request.getMethod() + " " + getRequestPath(request) + "'"
+                        + " doesn't match '" + httpMethod  + " " + pattern);
+            }
+
+            return false;
+        }
+
+        if (pattern.equals(MATCH_ALL)) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Request '" + getRequestPath(request) + "' matched by universal pattern '/**'");
+            }
+
+            return true;
+        }
+
+        String url = getRequestPath(request);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Checking match of request : '" + url + "'; against '" + pattern + "'");
+        }
+
+        return matcher.matches(url);
     }
 
-    public org.springframework.security.web.util.matchers.AntPathRequestMatcher getDelegate() {
-        return delegate;
+    private String getRequestPath(HttpServletRequest request) {
+        String url = request.getServletPath();
+
+        if (request.getPathInfo() != null) {
+            url += request.getPathInfo();
+        }
+
+        if(!caseSensitive) {
+            url = url.toLowerCase();
+        }
+
+        return url;
     }
 
     public String getPattern() {
-        return delegate.getPattern();
+        return pattern;
+    }
+
+    public HttpMethod getHttpMethod() {
+        return httpMethod;
+    }
+
+    public boolean isCaseSensitive() {
+        return caseSensitive;
     }
 
     @Override
     public boolean equals(Object obj) {
-        return delegate.equals(obj);
+        if (!(obj instanceof AntPathRequestMatcher)) {
+            return false;
+        }
+        AntPathRequestMatcher other = (AntPathRequestMatcher)obj;
+        return this.pattern.equals(other.pattern) &&
+            this.httpMethod == other.httpMethod &&
+            this.caseSensitive == other.caseSensitive;
     }
 
     @Override
     public int hashCode() {
-        return delegate.hashCode();
+        int code = 31 ^ pattern.hashCode();
+        if (httpMethod != null) {
+            code ^= httpMethod.hashCode();
+        }
+        return code;
     }
 
     @Override
     public String toString() {
-        return delegate.toString();
+        StringBuilder sb = new StringBuilder();
+        sb.append("Ant [pattern='").append(pattern).append("'");
+
+        if (httpMethod != null) {
+            sb.append(", ").append(httpMethod);
+        }
+
+        sb.append("]");
+
+        return sb.toString();
+    }
+
+    private static interface Matcher {
+        boolean matches(String path);
+    }
+
+    private static class SpringAntMatcher implements Matcher {
+        private static final AntPathMatcher antMatcher = new AntPathMatcher();
+
+        private final String pattern;
+
+        private SpringAntMatcher(String pattern) {
+            this.pattern = pattern;
+        }
+
+        public boolean matches(String path) {
+            return antMatcher.match(pattern, path);
+        }
+    }
+
+    /**
+     * Optimized matcher for trailing wildcards
+     */
+    private static class SubpathMatcher implements Matcher {
+        private final String subpath;
+        private final int length;
+
+        private SubpathMatcher(String subpath) {
+            assert !subpath.contains("*");
+            this.subpath = subpath;
+            this.length = subpath.length();
+        }
+
+        public boolean matches(String path) {
+            return path.startsWith(subpath) && (path.length() == length || path.charAt(length) == '/');
+        }
     }
 }

+ 3 - 4
web/src/main/java/org/springframework/security/web/util/AnyRequestMatcher.java

@@ -10,19 +10,18 @@ import javax.servlet.http.HttpServletRequest;
  * @deprecated use org.springframework.security.web.util.matchers.AnyRequestMatcher.INSTANCE instead
  */
 public final class AnyRequestMatcher implements RequestMatcher {
-    private final RequestMatcher delegate = org.springframework.security.web.util.matchers.AnyRequestMatcher.INSTANCE;
 
     public boolean matches(HttpServletRequest request) {
-        return delegate.matches(request);
+        return true;
     }
 
     @Override
     public boolean equals(Object obj) {
-        return delegate.equals(obj);
+        return obj instanceof AnyRequestMatcher;
     }
 
     @Override
     public int hashCode() {
-        return delegate.hashCode();
+        return 1;
     }
 }

+ 9 - 4
web/src/main/java/org/springframework/security/web/util/ELRequestMatcher.java

@@ -19,6 +19,9 @@ package org.springframework.security.web.util;
 import javax.servlet.http.HttpServletRequest;
 
 import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
 import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
 
 /**
@@ -36,14 +39,16 @@ import org.springframework.security.web.authentication.DelegatingAuthenticationE
  */
 public class ELRequestMatcher implements RequestMatcher {
 
-    private final org.springframework.security.web.util.matchers.ELRequestMatcher delegate;
+    private final Expression expression;
 
     public ELRequestMatcher(String el) {
-        delegate = new org.springframework.security.web.util.matchers.ELRequestMatcher(el);
+        SpelExpressionParser parser = new SpelExpressionParser();
+        expression = parser.parseExpression(el);
     }
 
     public boolean matches(HttpServletRequest request) {
-        return delegate.matches(request);
+        EvaluationContext context = createELContext(request);
+        return expression.getValue(context, Boolean.class).booleanValue();
     }
 
     /**
@@ -52,7 +57,7 @@ public class ELRequestMatcher implements RequestMatcher {
      * @return EL root context which is used to evaluate the expression
      */
     public EvaluationContext createELContext(HttpServletRequest request) {
-        return delegate.createELContext(request);
+        return new StandardEvaluationContext(new ELRequestMatcherContext(request));
     }
 
 }

+ 48 - 0
web/src/main/java/org/springframework/security/web/util/ELRequestMatcherContext.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * 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.springframework.security.web.util;
+
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.util.StringUtils;
+
+class ELRequestMatcherContext {
+
+    private final HttpServletRequest request;
+
+    public ELRequestMatcherContext(HttpServletRequest request) {
+        this.request = request;
+    }
+
+    public boolean hasIpAddress(String ipAddress) {
+        return (new IpAddressMatcher(ipAddress).matches(request));
+    }
+
+    public boolean hasHeader(String headerName, String value) {
+        String header = request.getHeader(headerName);
+        if (!StringUtils.hasText(header)) {
+            return false;
+        }
+
+        if (header.contains(value)) {
+            return true;
+        }
+
+        return false;
+    }
+
+}

+ 91 - 0
web/src/main/java/org/springframework/security/web/util/IpAddressMatcher.java

@@ -0,0 +1,91 @@
+package org.springframework.security.web.util;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * Matches a request based on IP Address or subnet mask matching against the remote address.
+ * <p>
+ * Both IPv6 and IPv4 addresses are supported, but a matcher which is configured with an IPv4 address will
+ * never match a request which returns an IPv6 address, and vice-versa.
+ *
+ * @deprecated use {@link org.springframework.security.web.util.matchers.IpAddressMatcher}
+ * @author Luke Taylor
+ * @since 3.0.2
+ */
+public final class IpAddressMatcher implements RequestMatcher {
+    private final int nMaskBits;
+    private final InetAddress requiredAddress;
+
+    /**
+     * Takes a specific IP address or a range specified using the
+     * IP/Netmask (e.g. 192.168.1.0/24 or 202.24.0.0/14).
+     *
+     * @param ipAddress the address or range of addresses from which the request must come.
+     */
+    public IpAddressMatcher(String ipAddress) {
+
+        if (ipAddress.indexOf('/') > 0) {
+            String[] addressAndMask = StringUtils.split(ipAddress, "/");
+            ipAddress = addressAndMask[0];
+            nMaskBits = Integer.parseInt(addressAndMask[1]);
+        } else {
+            nMaskBits = -1;
+        }
+        requiredAddress = parseAddress(ipAddress);
+    }
+
+    public boolean matches(HttpServletRequest request) {
+        return matches(request.getRemoteAddr());
+    }
+
+    public boolean matches(String address) {
+        InetAddress remoteAddress = parseAddress(address);
+
+        if (!requiredAddress.getClass().equals(remoteAddress.getClass())) {
+            return false;
+        }
+
+        if (nMaskBits < 0) {
+            return remoteAddress.equals(requiredAddress);
+        }
+
+        byte[] remAddr = remoteAddress.getAddress();
+        byte[] reqAddr = requiredAddress.getAddress();
+
+        int oddBits = nMaskBits % 8;
+        int nMaskBytes = nMaskBits/8 + (oddBits == 0 ? 0 : 1);
+        byte[] mask = new byte[nMaskBytes];
+
+        Arrays.fill(mask, 0, oddBits == 0 ? mask.length : mask.length - 1, (byte)0xFF);
+
+        if (oddBits != 0) {
+            int finalByte = (1 << oddBits) - 1;
+            finalByte <<= 8-oddBits;
+            mask[mask.length - 1] = (byte) finalByte;
+        }
+
+ //       System.out.println("Mask is " + new sun.misc.HexDumpEncoder().encode(mask));
+
+        for (int i=0; i < mask.length; i++) {
+            if ((remAddr[i] & mask[i]) != (reqAddr[i] & mask[i])) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private InetAddress parseAddress(String address) {
+        try {
+            return InetAddress.getByName(address);
+        } catch (UnknownHostException e) {
+            throw new IllegalArgumentException("Failed to parse address" + address, e);
+        }
+    }
+}

+ 43 - 4
web/src/main/java/org/springframework/security/web/util/RegexRequestMatcher.java

@@ -16,6 +16,11 @@ import java.util.regex.Pattern;
 
 import javax.servlet.http.HttpServletRequest;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.util.StringUtils;
+
 /**
  * Uses a regular expression to decide whether a supplied the URL of a supplied {@code HttpServletRequest}.
  *
@@ -25,13 +30,17 @@ import javax.servlet.http.HttpServletRequest;
  * by default. Case-insensitive matching can be used by using the constructor which takes the {@code caseInsensitive}
  * argument.
  *
+ * @deprecated use {@link org.springframework.security.web.util.matchers.RegexRequestMatcher}
+ *
  * @author Luke Taylor
  * @author Rob Winch
  * @since 3.1
- * @deprecated use org.springframework.security.web.util.matchers.RegexRequestMatcher
  */
 public final class RegexRequestMatcher implements RequestMatcher {
-    private final org.springframework.security.web.util.matchers.RegexRequestMatcher delegate;
+    private final static Log logger = LogFactory.getLog(RegexRequestMatcher.class);
+
+    private final Pattern pattern;
+    private final HttpMethod httpMethod;
 
     /**
      * Creates a case-sensitive {@code Pattern} instance to match against the request.
@@ -51,7 +60,12 @@ public final class RegexRequestMatcher implements RequestMatcher {
      * @param caseInsensitive if true, the pattern will be compiled with the {@link Pattern#CASE_INSENSITIVE} flag set.
      */
     public RegexRequestMatcher(String pattern, String httpMethod, boolean caseInsensitive) {
-        this.delegate = new org.springframework.security.web.util.matchers.RegexRequestMatcher(pattern, httpMethod, caseInsensitive);
+        if (caseInsensitive) {
+            this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
+        } else {
+            this.pattern = Pattern.compile(pattern);
+        }
+        this.httpMethod = StringUtils.hasText(httpMethod) ? HttpMethod.valueOf(httpMethod) : null;
     }
 
     /**
@@ -62,6 +76,31 @@ public final class RegexRequestMatcher implements RequestMatcher {
      * @return true if the pattern matches the URL, false otherwise.
      */
     public boolean matches(HttpServletRequest request) {
-        return delegate.matches(request);
+        if (httpMethod != null && request.getMethod() != null && httpMethod != HttpMethod.valueOf(request.getMethod())) {
+            return false;
+        }
+
+        String url = request.getServletPath();
+        String pathInfo = request.getPathInfo();
+        String query = request.getQueryString();
+
+        if (pathInfo != null || query != null) {
+            StringBuilder sb = new StringBuilder(url);
+
+            if (pathInfo != null) {
+                sb.append(pathInfo);
+            }
+
+            if (query != null) {
+                sb.append('?').append(query);
+            }
+            url = sb.toString();
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Checking match of request : '" + url + "'; against '" + pattern + "'");
+        }
+
+        return pattern.matcher(url).matches();
     }
 }

+ 1 - 2
web/src/main/java/org/springframework/security/web/util/RequestMatcherEditor.java

@@ -18,7 +18,6 @@ package org.springframework.security.web.util;
 
 import java.beans.PropertyEditorSupport;
 
-import org.springframework.security.web.util.matchers.ELRequestMatcher;
 import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
 
 /**
@@ -34,7 +33,7 @@ public class RequestMatcherEditor extends PropertyEditorSupport {
 
     @Override
     public void setAsText(String text) throws IllegalArgumentException {
-        setValue(new ELRequestMatcher(text));
+        setValue(new org.springframework.security.web.util.matchers.ELRequestMatcher(text));
     }
 
 }

+ 10 - 8
web/src/main/java/org/springframework/security/web/util/matchers/AntPathRequestMatcher.java

@@ -169,17 +169,19 @@ public final class AntPathRequestMatcher implements RequestMatcher {
     @SuppressWarnings("deprecation")
     @Override
     public boolean equals(Object obj) {
-        org.springframework.security.web.util.matchers.AntPathRequestMatcher other;
+
         if (obj instanceof org.springframework.security.web.util.AntPathRequestMatcher) {
-            other = ((org.springframework.security.web.util.AntPathRequestMatcher) obj).getDelegate();
+            org.springframework.security.web.util.AntPathRequestMatcher other = (org.springframework.security.web.util.AntPathRequestMatcher) obj;
+            return this.pattern.equals(other.getPattern()) &&
+                    this.httpMethod == other.getHttpMethod() &&
+                    this.caseSensitive == other.isCaseSensitive();
         } else if(obj instanceof AntPathRequestMatcher) {
-            other = (AntPathRequestMatcher) obj;
-        } else {
-            return false;
+            org.springframework.security.web.util.matchers.AntPathRequestMatcher other = (AntPathRequestMatcher) obj;
+            return this.pattern.equals(other.pattern) &&
+                    this.httpMethod == other.httpMethod &&
+                    this.caseSensitive == other.caseSensitive;
         }
-        return this.pattern.equals(other.pattern) &&
-            this.httpMethod == other.httpMethod &&
-            this.caseSensitive == other.caseSensitive;
+        return false;
     }
 
     @Override