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}.

*/ public class DigestProcessingFilter implements Filter, InitializingBean, MessageSourceAware { @@ -105,10 +105,11 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message Assert.notNull(authenticationEntryPoint, "A DigestProcessingFilterEntryPoint is required"); } - public void destroy() {} + public void destroy() { + } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { if (!(request instanceof HttpServletRequest)) { throw new ServletException("Can only process HttpServletRequest"); } @@ -128,7 +129,7 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message if ((header != null) && header.startsWith("Digest ")) { String section212response = header.substring(7); - String[] headerEntries = StringUtils.commaDelimitedListToStringArray(section212response); + String[] headerEntries = StringSplitUtils.splitIgnoringQuotes(section212response, ','); Map headerMap = StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\""); String username = (String) headerMap.get("username"); @@ -144,12 +145,12 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message if ((username == null) || (realm == null) || (nonce == null) || (uri == null) || (response == null)) { if (logger.isDebugEnabled()) { logger.debug("extracted username: '" + username + "'; realm: '" + username + "'; nonce: '" - + username + "'; uri: '" + username + "'; response: '" + username + "'"); + + username + "'; uri: '" + username + "'; response: '" + username + "'"); } fail(request, response, - new BadCredentialsException(messages.getMessage("DigestProcessingFilter.missingMandatory", - new Object[] {section212response}, "Missing mandatory digest value; received header {0}"))); + new BadCredentialsException(messages.getMessage("DigestProcessingFilter.missingMandatory", + new Object[]{section212response}, "Missing mandatory digest value; received header {0}"))); return; } @@ -162,8 +163,8 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message } fail(request, response, - new BadCredentialsException(messages.getMessage("DigestProcessingFilter.missingAuth", - new Object[] {section212response}, "Missing mandatory digest value; received header {0}"))); + new BadCredentialsException(messages.getMessage("DigestProcessingFilter.missingAuth", + new Object[]{section212response}, "Missing mandatory digest value; received header {0}"))); return; } @@ -172,9 +173,9 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message // Check realm name equals what we expected if (!this.getAuthenticationEntryPoint().getRealmName().equals(realm)) { fail(request, response, - new BadCredentialsException(messages.getMessage("DigestProcessingFilter.incorrectRealm", - new Object[] {realm, this.getAuthenticationEntryPoint().getRealmName()}, - "Response realm name '{0}' does not match system realm name of '{1}'"))); + new BadCredentialsException(messages.getMessage("DigestProcessingFilter.incorrectRealm", + new Object[]{realm, this.getAuthenticationEntryPoint().getRealmName()}, + "Response realm name '{0}' does not match system realm name of '{1}'"))); return; } @@ -182,22 +183,22 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message // Check nonce was a Base64 encoded (as sent by DigestProcessingFilterEntryPoint) if (!Base64.isArrayByteBase64(nonce.getBytes())) { fail(request, response, - new BadCredentialsException(messages.getMessage("DigestProcessingFilter.nonceEncoding", - new Object[] {nonce}, "Nonce is not encoded in Base64; received nonce {0}"))); + new BadCredentialsException(messages.getMessage("DigestProcessingFilter.nonceEncoding", + new Object[]{nonce}, "Nonce is not encoded in Base64; received nonce {0}"))); return; } // Decode nonce from Base64 - // format of nonce is: + // format of nonce is: // base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key)) String nonceAsPlainText = new String(Base64.decodeBase64(nonce.getBytes())); String[] nonceTokens = StringUtils.delimitedListToStringArray(nonceAsPlainText, ":"); if (nonceTokens.length != 2) { fail(request, response, - new BadCredentialsException(messages.getMessage("DigestProcessingFilter.nonceNotTwoTokens", - new Object[] {nonceAsPlainText}, "Nonce should have yielded two tokens but was {0}"))); + new BadCredentialsException(messages.getMessage("DigestProcessingFilter.nonceNotTwoTokens", + new Object[]{nonceAsPlainText}, "Nonce should have yielded two tokens but was {0}"))); return; } @@ -209,9 +210,9 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message nonceExpiryTime = new Long(nonceTokens[0]).longValue(); } catch (NumberFormatException nfe) { fail(request, response, - new BadCredentialsException(messages.getMessage("DigestProcessingFilter.nonceNotNumeric", - new Object[] {nonceAsPlainText}, - "Nonce token should have yielded a numeric first token, but was {0}"))); + new BadCredentialsException(messages.getMessage("DigestProcessingFilter.nonceNotNumeric", + new Object[]{nonceAsPlainText}, + "Nonce token should have yielded a numeric first token, but was {0}"))); return; } @@ -222,8 +223,8 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message if (!expectedNonceSignature.equals(nonceTokens[1])) { fail(request, response, - new BadCredentialsException(messages.getMessage("DigestProcessingFilter.nonceCompromised", - new Object[] {nonceAsPlainText}, "Nonce token compromised {0}"))); + new BadCredentialsException(messages.getMessage("DigestProcessingFilter.nonceCompromised", + new Object[]{nonceAsPlainText}, "Nonce token compromised {0}"))); return; } @@ -241,15 +242,15 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message user = userDetailsService.loadUserByUsername(username); } catch (UsernameNotFoundException notFound) { fail(request, response, - new BadCredentialsException(messages.getMessage("DigestProcessingFilter.usernameNotFound", - new Object[] {username}, "Username {0} not found"))); + new BadCredentialsException(messages.getMessage("DigestProcessingFilter.usernameNotFound", + new Object[]{username}, "Username {0} not found"))); return; } if (user == null) { throw new AuthenticationServiceException( - "AuthenticationDao returned null, which is an interface contract violation"); + "AuthenticationDao returned null, which is an interface contract violation"); } userCache.putUserInCache(user); @@ -266,7 +267,7 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message if (!serverDigestMd5.equals(responseDigest) && !loadedFromDao) { if (logger.isDebugEnabled()) { logger.debug( - "Digest comparison failure; trying to refresh user from DAO in case password had changed"); + "Digest comparison failure; trying to refresh user from DAO in case password had changed"); } try { @@ -274,8 +275,8 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message } catch (UsernameNotFoundException notFound) { // Would very rarely happen, as user existed earlier fail(request, response, - new BadCredentialsException(messages.getMessage("DigestProcessingFilter.usernameNotFound", - new Object[] {username}, "Username {0} not found"))); + new BadCredentialsException(messages.getMessage("DigestProcessingFilter.usernameNotFound", + new Object[]{username}, "Username {0} not found"))); } userCache.putUserInCache(user); @@ -289,12 +290,12 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message if (!serverDigestMd5.equals(responseDigest)) { if (logger.isDebugEnabled()) { logger.debug("Expected response: '" + serverDigestMd5 + "' but received: '" + responseDigest - + "'; is AuthenticationDao returning clear text passwords?"); + + "'; is AuthenticationDao returning clear text passwords?"); } fail(request, response, - new BadCredentialsException(messages.getMessage("DigestProcessingFilter.incorrectResponse", - "Incorrect response"))); + new BadCredentialsException(messages.getMessage("DigestProcessingFilter.incorrectResponse", + "Incorrect response"))); return; } @@ -305,15 +306,15 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message // but the request was otherwise appearing to be valid if (nonceExpiryTime < System.currentTimeMillis()) { fail(request, response, - new NonceExpiredException(messages.getMessage("DigestProcessingFilter.nonceExpired", - "Nonce has expired/timed out"))); + new NonceExpiredException(messages.getMessage("DigestProcessingFilter.nonceExpired", + "Nonce has expired/timed out"))); return; } if (logger.isDebugEnabled()) { logger.debug("Authentication success for user: '" + username + "' with response: '" + responseDigest - + "'"); + + "'"); } UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(user, @@ -335,7 +336,7 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message } private void fail(ServletRequest request, ServletResponse response, AuthenticationException failed) - throws IOException, ServletException { + throws IOException, ServletException { SecurityContextHolder.getContext().setAuthentication(null); if (logger.isDebugEnabled()) { @@ -351,24 +352,22 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message * 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. - * + * 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 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)); @@ -408,7 +407,8 @@ public class DigestProcessingFilter implements Filter, InitializingBean, Message return userDetailsService; } - public void init(FilterConfig ignored) throws ServletException {} + public void init(FilterConfig ignored) throws ServletException { + } public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) { Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required"); diff --git a/core/src/main/java/org/acegisecurity/util/StringSplitUtils.java b/core/src/main/java/org/acegisecurity/util/StringSplitUtils.java index 841cadeb57..9d344f3d1c 100644 --- a/core/src/main/java/org/acegisecurity/util/StringSplitUtils.java +++ b/core/src/main/java/org/acegisecurity/util/StringSplitUtils.java @@ -20,6 +20,8 @@ import org.springframework.util.StringUtils; import java.util.HashMap; import java.util.Map; +import java.util.ArrayList; +import java.util.List; /** @@ -29,6 +31,9 @@ import java.util.Map; * @version $Id$ */ public final class StringSplitUtils { + //~ Static fields/initializers ===================================================================================== + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + //~ Constructors =================================================================================================== private StringSplitUtils() { @@ -40,12 +45,10 @@ public final class StringSplitUtils { * Splits a 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.

* - * @param array the array to process - * @param delimiter to split each element using (typically the equals symbol) + * @param array the array to process + * @param delimiter to split each element using (typically the equals symbol) * @param removeCharacters one or more characters to remove from each element prior to attempting the split - * operation (typically the quotation mark symbol) or 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); + } }