diff --git a/core/src/main/java/org/acegisecurity/ui/digestauth/DigestProcessingFilter.java b/core/src/main/java/org/acegisecurity/ui/digestauth/DigestProcessingFilter.java
index d6da7b545a..9bb7dfc7a0 100644
--- a/core/src/main/java/org/acegisecurity/ui/digestauth/DigestProcessingFilter.java
+++ b/core/src/main/java/org/acegisecurity/ui/digestauth/DigestProcessingFilter.java
@@ -68,20 +68,20 @@ import javax.servlet.http.HttpServletResponse;
* SecurityContextHolder
.
For a detailed background on what this filter is designed to process, * refer to RFC 2617 (which superseded RFC 2069, although this * filter support clients that implement either RFC 2617 or RFC 2069).
- *This filter can be used to provide Digest authentication services to both remoting protocol clients (such as + *
This filter can be used to provide Digest authentication services to both remoting protocol clients (such as * Hessian and SOAP) as well as standard user agents (such as Internet Explorer and FireFox).
- *This Digest implementation has been designed to avoid needing to store session state between invocations. + *
This Digest implementation has been designed to avoid needing to store session state between invocations. * All session management information is stored in the "nonce" that is sent to the client by the {@link * DigestProcessingFilterEntryPoint}.
- *If authentication is successful, the resulting {@link org.acegisecurity.Authentication Authentication} + *
If authentication is successful, the resulting {@link org.acegisecurity.Authentication Authentication}
* object will be placed into the SecurityContextHolder
.
If authentication fails, an {@link org.acegisecurity.ui.AuthenticationEntryPoint AuthenticationEntryPoint} + *
If authentication fails, an {@link org.acegisecurity.ui.AuthenticationEntryPoint AuthenticationEntryPoint} * implementation is called. This must always be {@link DigestProcessingFilterEntryPoint}, which will prompt the user * to authenticate again via Digest authentication.
- *Note there are limitations to Digest authentication, although it is a more comprehensive and secure solution + *
Note there are limitations to Digest authentication, although it is a more comprehensive and secure solution * than Basic authentication. Please see RFC 2617 section 4 for a full discussion on the advantages of Digest * authentication over Basic authentication, including commentary on the limitations that it still imposes.
- *Do not use this class directly. Instead configure web.xml
to use the {@link
+ *
Do not use this class directly. Instead configure web.xml
to use the {@link
* org.acegisecurity.util.FilterToBeanProxy}.
String
at the first instance of the delimiter.Does not include the delimiter in * the response.
* - * @param toSplit the string to split + * @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[] split(String toSplit, String delimiter) { @@ -65,7 +68,7 @@ public final class StringSplitUtils { String beforeDelimiter = toSplit.substring(0, offset); String afterDelimiter = toSplit.substring(offset + 1); - return new String[] {beforeDelimiter, afterDelimiter}; + return new String[]{beforeDelimiter, afterDelimiter}; } /** @@ -74,11 +77,10 @@ public final class StringSplitUtils { * then generated, with the left of the delimiter providing the key, and the right of the delimiter providing the * value.Will trim both the key and value before adding to the Map
.
null
if no removal should occur
- *
+ * operation (typically the quotation mark symbol) or null
if no removal should occur
* @return a Map
representing the array contents, or null
if the array to process was
* null or empty
*/
@@ -135,4 +137,58 @@ public final class StringSplitUtils {
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).
+ *
+ * This was copied and modified from commons-lang StringUtils
+ */
+ public static String[] splitIgnoringQuotes(String str, char separatorChar) {
+ if (str == null) {
+ return null;
+ }
+
+ int len = str.length();
+
+ if (len == 0) {
+ return EMPTY_STRING_ARRAY;
+ }
+
+ List list = new ArrayList();
+ 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 (String[]) list.toArray(new String[list.size()]);
+ }
+
}
diff --git a/core/src/test/java/org/acegisecurity/ui/digestauth/DigestProcessingFilterTests.java b/core/src/test/java/org/acegisecurity/ui/digestauth/DigestProcessingFilterTests.java
index 27a38da519..8f3f7afef2 100644
--- a/core/src/test/java/org/acegisecurity/ui/digestauth/DigestProcessingFilterTests.java
+++ b/core/src/test/java/org/acegisecurity/ui/digestauth/DigestProcessingFilterTests.java
@@ -62,25 +62,28 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
private static final String NC = "00000002";
private static final String CNONCE = "c822c727a648aba7";
- private static final String REALM = "The Correct Realm Name";
+ private static final String REALM = "The Actual, Correct Realm Name";
private static final String KEY = "acegi";
private static final String QOP = "auth";
- private static final String USERNAME = "marissa";
+ private static final String USERNAME = "marissa,ok";
private static final String PASSWORD = "koala";
private static final String REQUEST_URI = "/some_file.html";
- /** A standard valid nonce with a validity period of 60 seconds */
+ /**
+ * A standard valid nonce with a validity period of 60 seconds
+ */
private static final String NONCE = generateNonce(60);
//~ Instance fields ================================================================================================
-// private ApplicationContext ctx;
+ // private ApplicationContext ctx;
private DigestProcessingFilter filter;
private MockHttpServletRequest request;
//~ Constructors ===================================================================================================
- public DigestProcessingFilterTests() {}
+ public DigestProcessingFilterTests() {
+ }
public DigestProcessingFilterTests(String arg0) {
super(arg0);
@@ -89,13 +92,13 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
//~ Methods ========================================================================================================
private String createAuthorizationHeader(String username, String realm, String nonce, String uri,
- String responseDigest, String qop, String nc, String cnonce) {
+ String responseDigest, String qop, String nc, String cnonce) {
return "Digest username=\"" + username + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", uri=\"" + uri
- + "\", response=\"" + responseDigest + "\", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + cnonce + "\"";
+ + "\", response=\"" + responseDigest + "\", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + cnonce + "\"";
}
private MockHttpServletResponse executeFilterInContainerSimulator(Filter filter, ServletRequest request,
- boolean expectChainToProceed) throws ServletException, IOException {
+ boolean expectChainToProceed) throws ServletException, IOException {
filter.init(new MockFilterConfig());
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -118,10 +121,6 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
return new String(Base64.encodeBase64(nonceValue.getBytes()));
}
- public static void main(String[] args) {
- junit.textui.TestRunner.run(DigestProcessingFilterTests.class);
- }
-
protected void setUp() throws Exception {
super.setUp();
SecurityContextHolder.clearContext();
@@ -129,7 +128,7 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
// Create User Details Service
InMemoryDaoImpl dao = new InMemoryDaoImpl();
UserMapEditor editor = new UserMapEditor();
- editor.setAsText("marissa=koala,ROLE_ONE,ROLE_TWO,enabled\r\n");
+ editor.setAsText("marissa,ok=koala,ROLE_ONE,ROLE_TWO,enabled\r\n");
dao.setUserMap((UserMap) editor.getValue());
DigestProcessingFilterEntryPoint ep = new DigestProcessingFilterEntryPoint();
@@ -150,7 +149,7 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testDoFilterWithNonHttpServletRequestDetected()
- throws Exception {
+ throws Exception {
DigestProcessingFilter filter = new DigestProcessingFilter();
try {
@@ -162,7 +161,7 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testDoFilterWithNonHttpServletResponseDetected()
- throws Exception {
+ throws Exception {
DigestProcessingFilter filter = new DigestProcessingFilter();
try {
@@ -174,13 +173,13 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testExpiredNonceReturnsForbiddenWithStaleHeader()
- throws Exception {
+ throws Exception {
String nonce = generateNonce(0);
String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
REQUEST_URI, QOP, nonce, NC, CNONCE);
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+ createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
Thread.sleep(1000); // ensures token expired
@@ -196,7 +195,7 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testFilterIgnoresRequestsContainingNoAuthorizationHeader()
- throws Exception {
+ throws Exception {
executeFilterInContainerSimulator(filter, request, true);
assertNull(SecurityContextHolder.getContext().getAuthentication());
@@ -217,7 +216,7 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testInvalidDigestAuthorizationTokenGeneratesError()
- throws Exception {
+ throws Exception {
String token = "NOT_A_VALID_TOKEN_AS_MISSING_COLON";
request.addHeader("Authorization", "Digest " + new String(Base64.encodeBase64(token.getBytes())));
@@ -238,14 +237,14 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testNonBase64EncodedNonceReturnsForbidden()
- throws Exception {
+ throws Exception {
String nonce = "NOT_BASE_64_ENCODED";
String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
REQUEST_URI, QOP, nonce, NC, CNONCE);
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+ createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
@@ -254,13 +253,13 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testNonceWithIncorrectSignatureForNumericFieldReturnsForbidden()
- throws Exception {
+ throws Exception {
String nonce = new String(Base64.encodeBase64("123456:incorrectStringPassword".getBytes()));
String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
REQUEST_URI, QOP, nonce, NC, CNONCE);
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+ createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
@@ -269,13 +268,13 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testNonceWithNonNumericFirstElementReturnsForbidden()
- throws Exception {
+ throws Exception {
String nonce = new String(Base64.encodeBase64("hello:ignoredSecondElement".getBytes()));
String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
REQUEST_URI, QOP, nonce, NC, CNONCE);
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+ createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
@@ -284,13 +283,13 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testNonceWithoutTwoColonSeparatedElementsReturnsForbidden()
- throws Exception {
+ 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",
REQUEST_URI, QOP, nonce, NC, CNONCE);
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+ createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
@@ -299,38 +298,38 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testNormalOperationWhenPasswordIsAlreadyEncoded()
- throws Exception {
+ throws Exception {
String encodedPassword = DigestProcessingFilter.encodePasswordInA1Format(USERNAME, REALM, PASSWORD);
String responseDigest = DigestProcessingFilter.generateDigest(true, USERNAME, REALM, encodedPassword, "GET",
REQUEST_URI, QOP, NONCE, NC, CNONCE);
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+ createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
executeFilterInContainerSimulator(filter, request, true);
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
assertEquals(USERNAME,
- ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
+ ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
}
public void testNormalOperationWhenPasswordNotAlreadyEncoded()
- throws Exception {
+ throws Exception {
String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
REQUEST_URI, QOP, NONCE, NC, CNONCE);
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+ createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
executeFilterInContainerSimulator(filter, request, true);
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
assertEquals(USERNAME,
- ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
+ ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
}
public void testOtherAuthorizationSchemeIsIgnored()
- throws Exception {
+ throws Exception {
request.addHeader("Authorization", "SOME_OTHER_AUTHENTICATION_SCHEME");
executeFilterInContainerSimulator(filter, request, true);
@@ -339,7 +338,7 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testStartupDetectsMissingAuthenticationEntryPoint()
- throws Exception {
+ throws Exception {
try {
DigestProcessingFilter filter = new DigestProcessingFilter();
filter.setUserDetailsService(new InMemoryDaoImpl());
@@ -351,7 +350,7 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testStartupDetectsMissingUserDetailsService()
- throws Exception {
+ throws Exception {
try {
DigestProcessingFilter filter = new DigestProcessingFilter();
filter.setAuthenticationEntryPoint(new DigestProcessingFilterEntryPoint());
@@ -363,12 +362,12 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testSuccessLoginThenFailureLoginResultsInSessionLosingToken()
- throws Exception {
+ throws Exception {
String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
REQUEST_URI, QOP, NONCE, NC, CNONCE);
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+ createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
executeFilterInContainerSimulator(filter, request, true);
@@ -380,7 +379,7 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
request = new MockHttpServletRequest();
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+ createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
@@ -390,14 +389,14 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
}
public void testWrongCnonceBasedOnDigestReturnsForbidden()
- throws Exception {
+ throws Exception {
String cnonce = "NOT_SAME_AS_USED_FOR_DIGEST_COMPUTATION";
String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
REQUEST_URI, QOP, NONCE, NC, "DIFFERENT_CNONCE");
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, cnonce));
+ createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, cnonce));
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
@@ -411,7 +410,7 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
REQUEST_URI, QOP, NONCE, NC, CNONCE);
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+ createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
@@ -425,7 +424,7 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
REQUEST_URI, QOP, NONCE, NC, CNONCE);
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, realm, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+ createAuthorizationHeader(USERNAME, realm, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
@@ -438,7 +437,7 @@ public class DigestProcessingFilterTests extends MockObjectTestCase {
"GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
request.addHeader("Authorization",
- createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+ createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
diff --git a/core/src/test/java/org/acegisecurity/util/StringSplitUtilsTests.java b/core/src/test/java/org/acegisecurity/util/StringSplitUtilsTests.java
index b4a7c53573..07b203ad9f 100644
--- a/core/src/test/java/org/acegisecurity/util/StringSplitUtilsTests.java
+++ b/core/src/test/java/org/acegisecurity/util/StringSplitUtilsTests.java
@@ -32,6 +32,7 @@ public class StringSplitUtilsTests extends TestCase {
//~ Constructors ===================================================================================================
// ===========================================================
+
public StringSplitUtilsTests() {
super();
}
@@ -43,6 +44,7 @@ public class StringSplitUtilsTests extends TestCase {
//~ Methods ========================================================================================================
// ================================================================
+
public static void main(String[] args) {
junit.textui.TestRunner.run(StringSplitUtilsTests.class);
}
@@ -57,7 +59,7 @@ public class StringSplitUtilsTests extends TestCase {
assertEquals("Contacts Realm", headerMap.get("realm"));
assertEquals("MTEwOTAyMzU1MTQ4NDo1YzY3OWViYWM5NDNmZWUwM2UwY2NmMDBiNDQzMTQ0OQ==", headerMap.get("nonce"));
assertEquals("/acegi-security-sample-contacts-filter/secure/adminPermission.htm?contactId=4",
- headerMap.get("uri"));
+ headerMap.get("uri"));
assertEquals("38644211cf9ac3da63ab639807e2baff", headerMap.get("response"));
assertEquals("auth", headerMap.get("qop"));
assertEquals("00000004", headerMap.get("nc"));
@@ -74,7 +76,7 @@ public class StringSplitUtilsTests extends TestCase {
assertEquals("\"Contacts Realm\"", headerMap.get("realm"));
assertEquals("\"MTEwOTAyMzU1MTQ4NDo1YzY3OWViYWM5NDNmZWUwM2UwY2NmMDBiNDQzMTQ0OQ==\"", headerMap.get("nonce"));
assertEquals("\"/acegi-security-sample-contacts-filter/secure/adminPermission.htm?contactId=4\"",
- headerMap.get("uri"));
+ headerMap.get("uri"));
assertEquals("\"38644211cf9ac3da63ab639807e2baff\"", headerMap.get("response"));
assertEquals("auth", headerMap.get("qop"));
assertEquals("00000004", headerMap.get("nc"));
@@ -84,7 +86,7 @@ public class StringSplitUtilsTests extends TestCase {
public void testSplitEachArrayElementAndCreateMapReturnsNullIfArrayEmptyOrNull() {
assertNull(StringSplitUtils.splitEachArrayElementAndCreateMap(null, "=", "\""));
- assertNull(StringSplitUtils.splitEachArrayElementAndCreateMap(new String[] {}, "=", "\""));
+ assertNull(StringSplitUtils.splitEachArrayElementAndCreateMap(new String[]{}, "=", "\""));
}
public void testSplitNormalOperation() {
@@ -137,4 +139,14 @@ public class StringSplitUtilsTests extends TestCase {
// only guarantees to split at FIRST delimiter, not EACH delimiter
assertEquals(2, StringSplitUtils.split("18|marissa|foo|bar", "|").length);
}
+
+
+ public void testAuthorizationHeaderWithCommasIsSplitCorrectly() {
+ 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, ',');
+
+ assertEquals(8, parts.length);
+ }
}