瀏覽代碼

SEC-1132: Moved TextUtils to web module and StringSplit utils into Digest authentication package (as they aren't used elsewhere).

Luke Taylor 16 年之前
父節點
當前提交
1454cbb78e

+ 0 - 14
core/src/test/java/org/springframework/security/util/TextUtilsTests.java

@@ -1,14 +0,0 @@
-package org.springframework.security.util;
-
-import static org.junit.Assert.*;
-
-import org.junit.Test;
-
-public class TextUtilsTests {
-
-    @Test
-    public void charactersAreEscapedCorrectly() {
-        assertEquals("a&lt;script&gt;&#034;&#039;", TextUtils.escapeEntities("a<script>\"'"));
-    }
-    
-}

+ 1 - 1
taglibs/pom.xml

@@ -15,7 +15,7 @@
     <dependencies>
         <dependency>
             <groupId>org.springframework.security</groupId>
-            <artifactId>spring-security-core</artifactId>
+            <artifactId>spring-security-web</artifactId>
             <version>${project.version}</version>
         </dependency>
         <dependency>

+ 2 - 2
taglibs/src/main/java/org/springframework/security/taglibs/authz/AuthenticationTag.java

@@ -19,7 +19,7 @@ package org.springframework.security.taglibs.authz;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.util.TextUtils;
+import org.springframework.security.web.util.TextEscapeUtils;
 
 import org.springframework.beans.BeanWrapperImpl;
 import org.springframework.beans.BeansException;
@@ -121,7 +121,7 @@ public class AuthenticationTag extends TagSupport {
                 }
             }
         } else {
-            writeMessage(TextUtils.escapeEntities(String.valueOf(result)));
+            writeMessage(TextEscapeUtils.escapeEntities(String.valueOf(result)));
         }
         return EVAL_PAGE;
     }

+ 2 - 2
web/src/main/java/org/springframework/security/web/authentication/AuthenticationProcessingFilter.java

@@ -21,8 +21,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 
-import org.springframework.security.util.TextUtils;
 import org.springframework.security.web.FilterChainOrder;
+import org.springframework.security.web.util.TextEscapeUtils;
 import org.springframework.util.Assert;
 
 import javax.servlet.http.HttpServletRequest;
@@ -88,7 +88,7 @@ public class AuthenticationProcessingFilter extends AbstractProcessingFilter {
         HttpSession session = request.getSession(false);
 
         if (session != null || getAllowSessionCreation()) {
-            request.getSession().setAttribute(SPRING_SECURITY_LAST_USERNAME_KEY, TextUtils.escapeEntities(username));
+            request.getSession().setAttribute(SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(username));
         }
 
         // Allow subclasses to set the "details" property

+ 118 - 121
core/src/main/java/org/springframework/security/util/StringSplitUtils.java → web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java

@@ -1,74 +1,120 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * 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.util;
+package org.springframework.security.web.authentication.www;
 
-import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
-
-import java.util.HashMap;
-import java.util.Map;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
 
+abstract class DigestAuthUtils {
 
-/**
- * Provides several <code>String</code> manipulation methods.
- *
- * @author Ben Alex
- * @version $Id$
- */
-public final class StringSplitUtils {
-    //~ Static fields/initializers =====================================================================================
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
-    //~ Constructors ===================================================================================================
+    public final static String encodePasswordInA1Format(String username, String realm, String password) {
+        String a1 = username + ":" + realm + ":" + password;
+        String a1Md5 = new String(DigestUtils.md5Hex(a1));
 
-    private StringSplitUtils() {
+        return a1Md5;
     }
 
-    //~ Methods ========================================================================================================
+
+    final static String[] splitIgnoringQuotes(String str, char separatorChar) {
+        if (str == null) {
+            return null;
+        }
+
+        int len = str.length();
+
+        if (len == 0) {
+            return EMPTY_STRING_ARRAY;
+        }
+
+        List<String> list = new ArrayList<String>();
+        int i = 0;
+        int start = 0;
+        boolean match = false;
+
+        while (i < len) {
+            if (str.charAt(i) == '"') {
+                i++;
+                while (i < len) {
+                    if (str.charAt(i) == '"') {
+                        i++;
+                        break;
+                    }
+                    i++;
+                }
+                match = true;
+                continue;
+            }
+            if (str.charAt(i) == separatorChar) {
+                if (match) {
+                    list.add(str.substring(start, i));
+                    match = false;
+                }
+                start = ++i;
+                continue;
+            }
+            match = true;
+            i++;
+        }
+        if (match) {
+            list.add(str.substring(start, i));
+        }
+
+        return list.toArray(new String[list.size()]);
+    }
 
     /**
-     * Splits a <code>String</code> at the first instance of the delimiter.<p>Does not include the delimiter in
-     * the response.</p>
+     * Computes the <code>response</code> portion of a Digest authentication header. Both the server and user
+     * agent should compute the <code>response</code> independently. Provided as a static method to simplify the
+     * coding of user agents.
      *
-     * @param toSplit   the string to split
-     * @param delimiter to split the string up with
-     * @return a two element array with index 0 being before the delimiter, and index 1 being after the delimiter
-     *         (neither element includes the delimiter)
-     * @throws IllegalArgumentException if an argument was invalid
+     * @param passwordAlreadyEncoded true if the password argument is already encoded in the correct format. False if
+     *                               it is plain text.
+     * @param username               the user's login name.
+     * @param realm                  the name of the realm.
+     * @param password               the user's password in plaintext or ready-encoded.
+     * @param httpMethod             the HTTP request method (GET, POST etc.)
+     * @param uri                    the request URI.
+     * @param qop                    the qop directive, or null if not set.
+     * @param nonce                  the nonce supplied by the server
+     * @param nc                     the "nonce-count" as defined in RFC 2617.
+     * @param cnonce                 opaque string supplied by the client when qop is set.
+     * @return the MD5 of the digest authentication response, encoded in hex
+     * @throws IllegalArgumentException if the supplied qop value is unsupported.
      */
-    public static String[] split(String toSplit, String delimiter) {
-        Assert.hasLength(toSplit, "Cannot split a null or empty string");
-        Assert.hasLength(delimiter, "Cannot use a null or empty delimiter to split a string");
-
-        if (delimiter.length() != 1) {
-            throw new IllegalArgumentException("Delimiter can only be one character in length");
+    final static String generateDigest(boolean passwordAlreadyEncoded, String username, String realm, String password,
+                                        String httpMethod, String uri, String qop, String nonce, String nc, String cnonce)
+            throws IllegalArgumentException {
+        String a1Md5 = null;
+        String a2 = httpMethod + ":" + uri;
+        String a2Md5 = new String(DigestUtils.md5Hex(a2));
+
+        if (passwordAlreadyEncoded) {
+            a1Md5 = password;
+        } else {
+            a1Md5 = DigestAuthUtils.encodePasswordInA1Format(username, realm, password);
         }
 
-        int offset = toSplit.indexOf(delimiter);
+        String digest;
 
-        if (offset < 0) {
-            return null;
+        if (qop == null) {
+            // as per RFC 2069 compliant clients (also reaffirmed by RFC 2617)
+            digest = a1Md5 + ":" + nonce + ":" + a2Md5;
+        } else if ("auth".equals(qop)) {
+            // As per RFC 2617 compliant clients
+            digest = a1Md5 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + a2Md5;
+        } else {
+            throw new IllegalArgumentException("This method does not support a qop: '" + qop + "'");
         }
 
-        String beforeDelimiter = toSplit.substring(0, offset);
-        String afterDelimiter = toSplit.substring(offset + 1);
+        String digestMd5 = new String(DigestUtils.md5Hex(digest));
 
-        return new String[]{beforeDelimiter, afterDelimiter};
+        return digestMd5;
     }
 
     /**
@@ -84,7 +130,7 @@ public final class StringSplitUtils {
      * @return a <code>Map</code> representing the array contents, or <code>null</code> if the array to process was
      *         null or empty
      */
-    public static Map<String, String> splitEachArrayElementAndCreateMap(String[] array, String delimiter, String removeCharacters) {
+    final static Map<String, String> splitEachArrayElementAndCreateMap(String[] array, String delimiter, String removeCharacters) {
         if ((array == null) || (array.length == 0)) {
             return null;
         }
@@ -112,83 +158,34 @@ public final class StringSplitUtils {
         return map;
     }
 
-    public static String substringBeforeLast(String str, String separator) {
-        if (str == null || separator == null || str.length() == 0 || separator.length() == 0) {
-            return str;
-        }
-        int pos = str.lastIndexOf(separator);
-        if (pos == -1) {
-            return str;
-        }
-        return str.substring(0, pos);
-    }
-
-    public static String substringAfterLast(String str, String separator) {
-        if (str == null || str.length() == 0) {
-            return str;
-        }
-        if (separator == null || separator.length() == 0) {
-            return "";
-        }
-        int pos = str.lastIndexOf(separator);
-        if (pos == -1 || pos == (str.length() - separator.length())) {
-            return "";
-        }
-        return str.substring(pos + separator.length());
-    }
 
     /**
-     * Splits a given string on the given separator character, skips the contents of quoted substrings
-     * when looking for separators.
-     * Introduced for use in DigestProcessingFilter (see SEC-506).
-     * <p/>
-     * This was copied and modified from commons-lang StringUtils
+     * Splits a <code>String</code> at the first instance of the delimiter.<p>Does not include the delimiter in
+     * the response.</p>
+     *
+     * @param toSplit   the string to split
+     * @param delimiter to split the string up with
+     * @return a two element array with index 0 being before the delimiter, and index 1 being after the delimiter
+     *         (neither element includes the delimiter)
+     * @throws IllegalArgumentException if an argument was invalid
      */
-    public static String[] splitIgnoringQuotes(String str, char separatorChar) {
-        if (str == null) {
-            return null;
-        }
-
-        int len = str.length();
+    final static String[] split(String toSplit, String delimiter) {
+        Assert.hasLength(toSplit, "Cannot split a null or empty string");
+        Assert.hasLength(delimiter, "Cannot use a null or empty delimiter to split a string");
 
-        if (len == 0) {
-            return EMPTY_STRING_ARRAY;
+        if (delimiter.length() != 1) {
+            throw new IllegalArgumentException("Delimiter can only be one character in length");
         }
 
-        List<String> list = new ArrayList<String>();
-        int i = 0;
-        int start = 0;
-        boolean match = false;
+        int offset = toSplit.indexOf(delimiter);
 
-        while (i < len) {
-            if (str.charAt(i) == '"') {
-                i++;
-                while (i < len) {
-                    if (str.charAt(i) == '"') {
-                        i++;
-                        break;
-                    }
-                    i++;
-                }
-                match = true;
-                continue;
-            }
-            if (str.charAt(i) == separatorChar) {
-                if (match) {
-                    list.add(str.substring(start, i));
-                    match = false;
-                }
-                start = ++i;
-                continue;
-            }
-            match = true;
-            i++;
-        }
-        if (match) {
-            list.add(str.substring(start, i));
+        if (offset < 0) {
+            return null;
         }
 
-        return list.toArray(new String[list.size()]);
-    }
+        String beforeDelimiter = toSplit.substring(0, offset);
+        String afterDelimiter = toSplit.substring(offset + 1);
 
+        return new String[]{beforeDelimiter, afterDelimiter};
+    }
 }

+ 15 - 72
web/src/main/java/org/springframework/security/web/authentication/www/DigestProcessingFilter.java

@@ -44,7 +44,6 @@ import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.security.core.userdetails.cache.NullUserCache;
-import org.springframework.security.util.StringSplitUtils;
 import org.springframework.security.web.FilterChainOrder;
 import org.springframework.security.web.SpringSecurityFilter;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
@@ -81,6 +80,7 @@ import org.springframework.util.StringUtils;
 public class DigestProcessingFilter extends SpringSecurityFilter implements Filter, InitializingBean, MessageSourceAware {
     //~ Static fields/initializers =====================================================================================
 
+
     private static final Log logger = LogFactory.getLog(DigestProcessingFilter.class);
 
     //~ Instance fields ================================================================================================
@@ -110,17 +110,17 @@ public class DigestProcessingFilter extends SpringSecurityFilter implements Filt
         if ((header != null) && header.startsWith("Digest ")) {
             String section212response = header.substring(7);
 
-            String[] headerEntries = StringSplitUtils.splitIgnoringQuotes(section212response, ',');
-            Map<String,String> headerMap = StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
+            String[] headerEntries = DigestAuthUtils.splitIgnoringQuotes(section212response, ',');
+            Map<String,String> headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
 
-            String username = (String) headerMap.get("username");
-            String realm = (String) headerMap.get("realm");
-            String nonce = (String) headerMap.get("nonce");
-            String uri = (String) headerMap.get("uri");
-            String responseDigest = (String) headerMap.get("response");
-            String qop = (String) headerMap.get("qop"); // RFC 2617 extension
-            String nc = (String) headerMap.get("nc"); // RFC 2617 extension
-            String cnonce = (String) headerMap.get("cnonce"); // RFC 2617 extension
+            String username = headerMap.get("username");
+            String realm = headerMap.get("realm");
+            String nonce = headerMap.get("nonce");
+            String uri = headerMap.get("uri");
+            String responseDigest = headerMap.get("response");
+            String qop = headerMap.get("qop"); // RFC 2617 extension
+            String nc = headerMap.get("nc"); // RFC 2617 extension
+            String cnonce = headerMap.get("cnonce"); // RFC 2617 extension
 
             // Check all required parameters were supplied (ie RFC 2069)
             if ((username == null) || (realm == null) || (nonce == null) || (uri == null) || (response == null)) {
@@ -241,8 +241,8 @@ public class DigestProcessingFilter extends SpringSecurityFilter implements Filt
             String serverDigestMd5;
 
             // Don't catch IllegalArgumentException (already checked validity)
-            serverDigestMd5 = generateDigest(passwordAlreadyEncoded, username, realm, user.getPassword(),
-                    ((HttpServletRequest) request).getMethod(), uri, qop, nonce, nc, cnonce);
+            serverDigestMd5 = DigestAuthUtils.generateDigest(passwordAlreadyEncoded, username, realm, user.getPassword(),
+                    request.getMethod(), uri, qop, nonce, nc, cnonce);
 
             // If digest is incorrect, try refreshing from backend and recomputing
             if (!serverDigestMd5.equals(responseDigest) && !loadedFromDao) {
@@ -263,8 +263,8 @@ public class DigestProcessingFilter extends SpringSecurityFilter implements Filt
                 userCache.putUserInCache(user);
 
                 // Don't catch IllegalArgumentException (already checked validity)
-                serverDigestMd5 = generateDigest(passwordAlreadyEncoded, username, realm, user.getPassword(),
-                        ((HttpServletRequest) request).getMethod(), uri, qop, nonce, nc, cnonce);
+                serverDigestMd5 = DigestAuthUtils.generateDigest(passwordAlreadyEncoded, username, realm, user.getPassword(),
+                        request.getMethod(), uri, qop, nonce, nc, cnonce);
             }
 
             // If digest is still incorrect, definitely reject authentication attempt
@@ -277,7 +277,6 @@ public class DigestProcessingFilter extends SpringSecurityFilter implements Filt
                 fail(request, response,
                         new BadCredentialsException(messages.getMessage("DigestProcessingFilter.incorrectResponse",
                                 "Incorrect response")));
-
                 return;
             }
 
@@ -309,13 +308,6 @@ public class DigestProcessingFilter extends SpringSecurityFilter implements Filt
         chain.doFilter(request, response);
     }
 
-    public static String encodePasswordInA1Format(String username, String realm, String password) {
-        String a1 = username + ":" + realm + ":" + password;
-        String a1Md5 = new String(DigestUtils.md5Hex(a1));
-
-        return a1Md5;
-    }
-
     private void fail(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
             throws IOException, ServletException {
         SecurityContextHolder.getContext().setAuthentication(null);
@@ -327,55 +319,6 @@ public class DigestProcessingFilter extends SpringSecurityFilter implements Filt
         authenticationEntryPoint.commence(request, response, failed);
     }
 
-    /**
-     * Computes the <code>response</code> portion of a Digest authentication header. Both the server and user
-     * agent should compute the <code>response</code> independently. Provided as a static method to simplify the
-     * coding of user agents.
-     *
-     * @param passwordAlreadyEncoded true if the password argument is already encoded in the correct format. False if
-     *                               it is plain text.
-     * @param username               the user's login name.
-     * @param realm                  the name of the realm.
-     * @param password               the user's password in plaintext or ready-encoded.
-     * @param httpMethod             the HTTP request method (GET, POST etc.)
-     * @param uri                    the request URI.
-     * @param qop                    the qop directive, or null if not set.
-     * @param nonce                  the nonce supplied by the server
-     * @param nc                     the "nonce-count" as defined in RFC 2617.
-     * @param cnonce                 opaque string supplied by the client when qop is set.
-     * @return the MD5 of the digest authentication response, encoded in hex
-     * @throws IllegalArgumentException if the supplied qop value is unsupported.
-     */
-    public static String generateDigest(boolean passwordAlreadyEncoded, String username, String realm, String password,
-                                        String httpMethod, String uri, String qop, String nonce, String nc, String cnonce)
-            throws IllegalArgumentException {
-        String a1Md5 = null;
-        String a2 = httpMethod + ":" + uri;
-        String a2Md5 = new String(DigestUtils.md5Hex(a2));
-
-        if (passwordAlreadyEncoded) {
-            a1Md5 = password;
-        } else {
-            a1Md5 = encodePasswordInA1Format(username, realm, password);
-        }
-
-        String digest;
-
-        if (qop == null) {
-            // as per RFC 2069 compliant clients (also reaffirmed by RFC 2617)
-            digest = a1Md5 + ":" + nonce + ":" + a2Md5;
-        } else if ("auth".equals(qop)) {
-            // As per RFC 2617 compliant clients
-            digest = a1Md5 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + a2Md5;
-        } else {
-            throw new IllegalArgumentException("This method does not support a qop: '" + qop + "'");
-        }
-
-        String digestMd5 = new String(DigestUtils.md5Hex(digest));
-
-        return digestMd5;
-    }
-
     public DigestProcessingFilterEntryPoint getAuthenticationEntryPoint() {
         return authenticationEntryPoint;
     }

+ 4 - 4
core/src/main/java/org/springframework/security/util/TextUtils.java → web/src/main/java/org/springframework/security/web/util/TextEscapeUtils.java

@@ -1,14 +1,14 @@
-package org.springframework.security.util;
+package org.springframework.security.web.util;
 
 /**
- * Utilities for working with Strings and text.
+ * Utility for escaping characters in HTML strings.
  *
  * @author Luke Taylor
  * @version $Id$
  */
-public abstract class TextUtils {
+public abstract class TextEscapeUtils {
 
-    public static String escapeEntities(String s) {
+    public final static String escapeEntities(String s) {
         if (s == null || s.length() == 0) {
             return s;
         }

+ 17 - 17
core/src/test/java/org/springframework/security/util/StringSplitUtilsTests.java → web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthUtilsTests.java

@@ -13,7 +13,7 @@
  * limitations under the License.
  */
 
-package org.springframework.security.util;
+package org.springframework.security.web.authentication.www;
 
 import junit.framework.TestCase;
 
@@ -28,7 +28,7 @@ import java.util.Map;
  * @author Ben Alex
  * @version $Id$
  */
-public class StringSplitUtilsTests extends TestCase {
+public class DigestAuthUtilsTests extends TestCase {
     //~ Constructors ===================================================================================================
 
     //~ Methods ========================================================================================================
@@ -37,7 +37,7 @@ public class StringSplitUtilsTests extends TestCase {
         // note it ignores malformed entries (ie those without an equals sign)
         String unsplit = "username=\"rod\", invalidEntryThatHasNoEqualsSign, realm=\"Contacts Realm\", nonce=\"MTEwOTAyMzU1MTQ4NDo1YzY3OWViYWM5NDNmZWUwM2UwY2NmMDBiNDQzMTQ0OQ==\", uri=\"/spring-security-sample-contacts-filter/secure/adminPermission.htm?contactId=4\", response=\"38644211cf9ac3da63ab639807e2baff\", qop=auth, nc=00000004, cnonce=\"2b8d329a8571b99a\"";
         String[] headerEntries = StringUtils.commaDelimitedListToStringArray(unsplit);
-        Map<String, String> headerMap = StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
+        Map<String, String> headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
 
         assertEquals("rod", headerMap.get("username"));
         assertEquals("Contacts Realm", headerMap.get("realm"));
@@ -54,7 +54,7 @@ public class StringSplitUtilsTests extends TestCase {
     public void testSplitEachArrayElementAndCreateMapRespectsInstructionNotToRemoveCharacters() {
         String unsplit = "username=\"rod\", realm=\"Contacts Realm\", nonce=\"MTEwOTAyMzU1MTQ4NDo1YzY3OWViYWM5NDNmZWUwM2UwY2NmMDBiNDQzMTQ0OQ==\", uri=\"/spring-security-sample-contacts-filter/secure/adminPermission.htm?contactId=4\", response=\"38644211cf9ac3da63ab639807e2baff\", qop=auth, nc=00000004, cnonce=\"2b8d329a8571b99a\"";
         String[] headerEntries = StringUtils.commaDelimitedListToStringArray(unsplit);
-        Map<String, String> headerMap = StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", null);
+        Map<String, String> headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", null);
 
         assertEquals("\"rod\"", headerMap.get("username"));
         assertEquals("\"Contacts Realm\"", headerMap.get("realm"));
@@ -69,47 +69,47 @@ public class StringSplitUtilsTests extends TestCase {
     }
 
     public void testSplitEachArrayElementAndCreateMapReturnsNullIfArrayEmptyOrNull() {
-        assertNull(StringSplitUtils.splitEachArrayElementAndCreateMap(null, "=", "\""));
-        assertNull(StringSplitUtils.splitEachArrayElementAndCreateMap(new String[]{}, "=", "\""));
+        assertNull(DigestAuthUtils.splitEachArrayElementAndCreateMap(null, "=", "\""));
+        assertNull(DigestAuthUtils.splitEachArrayElementAndCreateMap(new String[]{}, "=", "\""));
     }
 
     public void testSplitNormalOperation() {
         String unsplit = "username=\"rod==\"";
-        assertEquals("username", StringSplitUtils.split(unsplit, "=")[0]);
-        assertEquals("\"rod==\"", StringSplitUtils.split(unsplit, "=")[1]); // should not remove quotes or extra equals
+        assertEquals("username", DigestAuthUtils.split(unsplit, "=")[0]);
+        assertEquals("\"rod==\"", DigestAuthUtils.split(unsplit, "=")[1]); // should not remove quotes or extra equals
     }
 
     public void testSplitRejectsNullsAndIncorrectLengthStrings() {
         try {
-            StringSplitUtils.split(null, "="); // null
+            DigestAuthUtils.split(null, "="); // null
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
             assertTrue(true);
         }
 
         try {
-            StringSplitUtils.split("", "="); // empty string
+            DigestAuthUtils.split("", "="); // empty string
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
             assertTrue(true);
         }
 
         try {
-            StringSplitUtils.split("sdch=dfgf", null); // null
+            DigestAuthUtils.split("sdch=dfgf", null); // null
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
             assertTrue(true);
         }
 
         try {
-            StringSplitUtils.split("fvfv=dcdc", ""); // empty string
+            DigestAuthUtils.split("fvfv=dcdc", ""); // empty string
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
             assertTrue(true);
         }
 
         try {
-            StringSplitUtils.split("dfdc=dcdc", "BIGGER_THAN_ONE_CHARACTER");
+            DigestAuthUtils.split("dfdc=dcdc", "BIGGER_THAN_ONE_CHARACTER");
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
             assertTrue(true);
@@ -117,11 +117,11 @@ public class StringSplitUtilsTests extends TestCase {
     }
 
     public void testSplitWorksWithDifferentDelimiters() {
-        assertEquals(2, StringSplitUtils.split("18/rod", "/").length);
-        assertNull(StringSplitUtils.split("18/rod", "!"));
+        assertEquals(2, DigestAuthUtils.split("18/rod", "/").length);
+        assertNull(DigestAuthUtils.split("18/rod", "!"));
 
         // only guarantees to split at FIRST delimiter, not EACH delimiter
-        assertEquals(2, StringSplitUtils.split("18|rod|foo|bar", "|").length);
+        assertEquals(2, DigestAuthUtils.split("18|rod|foo|bar", "|").length);
     }
 
 
@@ -129,7 +129,7 @@ public class StringSplitUtilsTests extends TestCase {
         String header = "Digest username=\"hamilton,bob\", realm=\"bobs,ok,realm\", nonce=\"the,nonce\", " +
                 "uri=\"the,Uri\", response=\"the,response,Digest\", qop=theqop, nc=thenc, cnonce=\"the,cnonce\"";
 
-        String[] parts = StringSplitUtils.splitIgnoringQuotes(header, ',');
+        String[] parts = DigestAuthUtils.splitIgnoringQuotes(header, ',');
 
         assertEquals(8, parts.length);
     }

+ 5 - 12
web/src/test/java/org/springframework/security/web/authentication/www/DigestProcessingFilterEntryPointTests.java

@@ -15,24 +15,17 @@
 
 package org.springframework.security.web.authentication.www;
 
-import junit.framework.TestCase;
-
+import java.util.Map;
 
-import org.springframework.security.authentication.DisabledException;
-import org.springframework.security.util.StringSplitUtils;
-import org.springframework.security.web.authentication.www.DigestProcessingFilterEntryPoint;
-import org.springframework.security.web.authentication.www.NonceExpiredException;
+import junit.framework.TestCase;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.codec.digest.DigestUtils;
-
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
-
+import org.springframework.security.authentication.DisabledException;
 import org.springframework.util.StringUtils;
 
-import java.util.Map;
-
 
 /**
  * Tests {@link DigestProcessingFilterEntryPoint}.
@@ -114,7 +107,7 @@ public class DigestProcessingFilterEntryPointTests extends TestCase {
         // Break up response header
         String header = response.getHeader("WWW-Authenticate").toString().substring(7);
         String[] headerEntries = StringUtils.commaDelimitedListToStringArray(header);
-        Map<String,String> headerMap = StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
+        Map<String,String> headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
 
         assertEquals("hello", headerMap.get("realm"));
         assertEquals("auth", headerMap.get("qop"));
@@ -144,7 +137,7 @@ public class DigestProcessingFilterEntryPointTests extends TestCase {
         // Break up response header
         String header = response.getHeader("WWW-Authenticate").toString().substring(7);
         String[] headerEntries = StringUtils.commaDelimitedListToStringArray(header);
-        Map<String,String> headerMap = StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
+        Map<String,String> headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
 
         assertEquals("hello", headerMap.get("realm"));
         assertEquals("auth", headerMap.get("qop"));

+ 27 - 38
web/src/test/java/org/springframework/security/web/authentication/www/DigestProcessingFilterTests.java

@@ -17,44 +17,33 @@ package org.springframework.security.web.authentication.www;
 
 import static org.junit.Assert.*;
 
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.digest.DigestUtils;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.security.MockFilterConfig;
-
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.cache.NullUserCache;
 import org.springframework.security.core.userdetails.memory.InMemoryDaoImpl;
 import org.springframework.security.core.userdetails.memory.UserMap;
 import org.springframework.security.core.userdetails.memory.UserMapEditor;
-
-
-
-import org.springframework.security.util.StringSplitUtils;
-import org.springframework.security.web.authentication.www.DigestProcessingFilter;
-import org.springframework.security.web.authentication.www.DigestProcessingFilterEntryPoint;
-
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.digest.DigestUtils;
-
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-
 import org.springframework.util.StringUtils;
 
-import java.io.IOException;
-
-import java.util.Map;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-
 
 /**
  * Tests {@link DigestProcessingFilter}.
@@ -153,7 +142,7 @@ public class DigestProcessingFilterTests {
     public void testExpiredNonceReturnsForbiddenWithStaleHeader()
             throws Exception {
         String nonce = generateNonce(0);
-        String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
                 REQUEST_URI, QOP, nonce, NC, CNONCE);
 
         request.addHeader("Authorization",
@@ -168,7 +157,7 @@ public class DigestProcessingFilterTests {
 
         String header = response.getHeader("WWW-Authenticate").toString().substring(7);
         String[] headerEntries = StringUtils.commaDelimitedListToStringArray(header);
-        Map<String,String> headerMap = StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
+        Map<String,String> headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
         assertEquals("true", headerMap.get("stale"));
     }
 
@@ -222,7 +211,7 @@ public class DigestProcessingFilterTests {
     public void testNonBase64EncodedNonceReturnsForbidden() throws Exception {
         String nonce = "NOT_BASE_64_ENCODED";
 
-        String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
                 REQUEST_URI, QOP, nonce, NC, CNONCE);
 
         request.addHeader("Authorization",
@@ -237,7 +226,7 @@ public class DigestProcessingFilterTests {
     @Test
     public void testNonceWithIncorrectSignatureForNumericFieldReturnsForbidden() throws Exception {
         String nonce = new String(Base64.encodeBase64("123456:incorrectStringPassword".getBytes()));
-        String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
                 REQUEST_URI, QOP, nonce, NC, CNONCE);
 
         request.addHeader("Authorization",
@@ -252,7 +241,7 @@ public class DigestProcessingFilterTests {
     @Test
     public void testNonceWithNonNumericFirstElementReturnsForbidden() throws Exception {
         String nonce = new String(Base64.encodeBase64("hello:ignoredSecondElement".getBytes()));
-        String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
                 REQUEST_URI, QOP, nonce, NC, CNONCE);
 
         request.addHeader("Authorization",
@@ -267,7 +256,7 @@ public class DigestProcessingFilterTests {
     @Test
     public void testNonceWithoutTwoColonSeparatedElementsReturnsForbidden() throws Exception {
         String nonce = new String(Base64.encodeBase64("a base 64 string without a colon".getBytes()));
-        String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
                 REQUEST_URI, QOP, nonce, NC, CNONCE);
 
         request.addHeader("Authorization",
@@ -281,8 +270,8 @@ public class DigestProcessingFilterTests {
 
     @Test
     public void testNormalOperationWhenPasswordIsAlreadyEncoded() throws Exception {
-        String encodedPassword = DigestProcessingFilter.encodePasswordInA1Format(USERNAME, REALM, PASSWORD);
-        String responseDigest = DigestProcessingFilter.generateDigest(true, USERNAME, REALM, encodedPassword, "GET",
+        String encodedPassword = DigestAuthUtils.encodePasswordInA1Format(USERNAME, REALM, PASSWORD);
+        String responseDigest = DigestAuthUtils.generateDigest(true, USERNAME, REALM, encodedPassword, "GET",
                 REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
         request.addHeader("Authorization",
@@ -297,7 +286,7 @@ public class DigestProcessingFilterTests {
 
     @Test
     public void testNormalOperationWhenPasswordNotAlreadyEncoded() throws Exception {
-        String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
                 REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
         request.addHeader("Authorization",
@@ -336,7 +325,7 @@ public class DigestProcessingFilterTests {
 
     @Test
     public void successfulLoginThenFailedLoginResultsInSessionLosingToken() throws Exception {
-        String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
                 REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
         request.addHeader("Authorization",
@@ -347,7 +336,7 @@ public class DigestProcessingFilterTests {
         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
 
         // Now retry, giving an invalid nonce
-        responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, "WRONG_PASSWORD", "GET",
+        responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, "WRONG_PASSWORD", "GET",
                 REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
         request = new MockHttpServletRequest();
@@ -365,7 +354,7 @@ public class DigestProcessingFilterTests {
     public void wrongCnonceBasedOnDigestReturnsForbidden() throws Exception {
         String cnonce = "NOT_SAME_AS_USED_FOR_DIGEST_COMPUTATION";
 
-        String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
                 REQUEST_URI, QOP, NONCE, NC, "DIFFERENT_CNONCE");
 
         request.addHeader("Authorization",
@@ -380,7 +369,7 @@ public class DigestProcessingFilterTests {
     @Test
     public void wrongDigestReturnsForbidden() throws Exception {
         String password = "WRONG_PASSWORD";
-        String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, password, "GET",
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, password, "GET",
                 REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
         request.addHeader("Authorization",
@@ -395,7 +384,7 @@ public class DigestProcessingFilterTests {
     @Test
     public void wrongRealmReturnsForbidden() throws Exception {
         String realm = "WRONG_REALM";
-        String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, realm, PASSWORD, "GET",
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, realm, PASSWORD, "GET",
                 REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
         request.addHeader("Authorization",
@@ -409,7 +398,7 @@ public class DigestProcessingFilterTests {
 
     @Test
     public void wrongUsernameReturnsForbidden() throws Exception {
-        String responseDigest = DigestProcessingFilter.generateDigest(false, "NOT_A_KNOWN_USER", REALM, PASSWORD,
+        String responseDigest = DigestAuthUtils.generateDigest(false, "NOT_A_KNOWN_USER", REALM, PASSWORD,
                 "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
         request.addHeader("Authorization",

+ 15 - 0
web/src/test/java/org/springframework/security/web/util/TextEscapeUtilsTests.java

@@ -0,0 +1,15 @@
+package org.springframework.security.web.util;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.springframework.security.web.util.TextEscapeUtils;
+
+public class TextEscapeUtilsTests {
+
+    @Test
+    public void charactersAreEscapedCorrectly() {
+        assertEquals("a&lt;script&gt;&#034;&#039;", TextEscapeUtils.escapeEntities("a<script>\"'"));
+    }
+    
+}