From 1b08395e7268c5a85baaa8371cbfacaf6d93fe81 Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Tue, 16 Aug 2011 15:13:36 +0000 Subject: [PATCH] Support for auth-int qop (quality of protection) option in Digest auth scheme git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1158320 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/http/impl/auth/BasicScheme.java | 12 +- .../apache/http/impl/auth/DigestScheme.java | 87 ++++--- .../http/impl/auth/HttpEntityDigester.java | 75 ++++++ .../apache/http/impl/auth/RFC2617Scheme.java | 12 +- .../http/impl/auth/TestBasicScheme.java | 8 +- .../http/impl/auth/TestDigestScheme.java | 239 ++++++++++++++++-- .../http/impl/auth/TestRFC2617Scheme.java | 20 ++ 7 files changed, 392 insertions(+), 61 deletions(-) create mode 100644 httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java b/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java index 91e02cf3c..ff12a39f7 100644 --- a/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java +++ b/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java @@ -38,6 +38,8 @@ import org.apache.http.auth.InvalidCredentialsException; import org.apache.http.auth.MalformedChallengeException; import org.apache.http.auth.params.AuthParams; import org.apache.http.message.BufferedHeader; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; import org.apache.http.util.CharArrayBuffer; import org.apache.http.util.EncodingUtils; @@ -109,6 +111,12 @@ public class BasicScheme extends RFC2617Scheme { return false; } + @Deprecated + public Header authenticate( + final Credentials credentials, final HttpRequest request) throws AuthenticationException { + return authenticate(credentials, request, new BasicHttpContext()); + } + /** * Produces basic authorization header for the given set of {@link Credentials}. * @@ -121,9 +129,11 @@ public class BasicScheme extends RFC2617Scheme { * * @return a basic authorization string */ + @Override public Header authenticate( final Credentials credentials, - final HttpRequest request) throws AuthenticationException { + final HttpRequest request, + final HttpContext context) throws AuthenticationException { if (credentials == null) { throw new IllegalArgumentException("Credentials may not be null"); diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java b/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java index e8463ed50..05610286c 100644 --- a/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java +++ b/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java @@ -26,17 +26,22 @@ package org.apache.http.impl.auth; +import java.io.IOException; import java.security.MessageDigest; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Formatter; +import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.StringTokenizer; import org.apache.http.annotation.NotThreadSafe; import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpRequest; import org.apache.http.auth.AuthenticationException; import org.apache.http.auth.Credentials; @@ -46,6 +51,8 @@ import org.apache.http.auth.params.AuthParams; import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicHeaderValueFormatter; import org.apache.http.message.BufferedHeader; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; import org.apache.http.util.CharArrayBuffer; import org.apache.http.util.EncodingUtils; @@ -122,13 +129,6 @@ public class DigestScheme extends RFC2617Scheme { public void processChallenge( final Header header) throws MalformedChallengeException { super.processChallenge(header); - - if (getParameter("realm") == null) { - throw new MalformedChallengeException("missing realm in challenge"); - } - if (getParameter("nonce") == null) { - throw new MalformedChallengeException("missing nonce in challenge"); - } this.complete = true; } @@ -169,6 +169,12 @@ public class DigestScheme extends RFC2617Scheme { getParameters().put(name, value); } + @Deprecated + public Header authenticate( + final Credentials credentials, final HttpRequest request) throws AuthenticationException { + return authenticate(credentials, request, new BasicHttpContext()); + } + /** * Produces a digest authorization string for the given set of * {@link Credentials}, method name and URI. @@ -183,9 +189,11 @@ public class DigestScheme extends RFC2617Scheme { * * @return a digest authorization string */ + @Override public Header authenticate( final Credentials credentials, - final HttpRequest request) throws AuthenticationException { + final HttpRequest request, + final HttpContext context) throws AuthenticationException { if (credentials == null) { throw new IllegalArgumentException("Credentials may not be null"); @@ -193,7 +201,12 @@ public class DigestScheme extends RFC2617Scheme { if (request == null) { throw new IllegalArgumentException("HTTP request may not be null"); } - + if (getParameter("realm") == null) { + throw new AuthenticationException("missing realm in challenge"); + } + if (getParameter("nonce") == null) { + throw new AuthenticationException("missing nonce in challenge"); + } // Add method name and request-URI to the parameter map getParameters().put("methodname", request.getRequestLine().getMethod()); getParameters().put("uri", request.getRequestLine().getUri()); @@ -202,7 +215,7 @@ public class DigestScheme extends RFC2617Scheme { charset = AuthParams.getCredentialCharset(request.getParams()); getParameters().put("charset", charset); } - return createDigestHeader(credentials); + return createDigestHeader(credentials, request); } private static MessageDigest createMessageDigest( @@ -224,34 +237,28 @@ public class DigestScheme extends RFC2617Scheme { * @return The digest-response as String. */ private Header createDigestHeader( - final Credentials credentials) throws AuthenticationException { + final Credentials credentials, + final HttpRequest request) throws AuthenticationException { String uri = getParameter("uri"); String realm = getParameter("realm"); String nonce = getParameter("nonce"); String opaque = getParameter("opaque"); String method = getParameter("methodname"); String algorithm = getParameter("algorithm"); - if (uri == null) { - throw new IllegalStateException("URI may not be null"); - } - if (realm == null) { - throw new IllegalStateException("Realm may not be null"); - } - if (nonce == null) { - throw new IllegalStateException("Nonce may not be null"); - } - //TODO: add support for QOP_INT + Set qopset = new HashSet(8); int qop = QOP_UNKNOWN; String qoplist = getParameter("qop"); if (qoplist != null) { StringTokenizer tok = new StringTokenizer(qoplist, ","); while (tok.hasMoreTokens()) { String variant = tok.nextToken().trim(); - if (variant.equals("auth")) { - qop = QOP_AUTH; - break; - } + qopset.add(variant.toLowerCase(Locale.US)); + } + if (request instanceof HttpEntityEnclosingRequest && qopset.contains("auth-int")) { + qop = QOP_AUTH_INT; + } else if (qopset.contains("auth")) { + qop = QOP_AUTH; } } else { qop = QOP_MISSING; @@ -265,7 +272,6 @@ public class DigestScheme extends RFC2617Scheme { if (algorithm == null) { algorithm = "MD5"; } - // If an charset is not specified, default to ISO-8859-1. String charset = getParameter("charset"); if (charset == null) { charset = "ISO-8859-1"; @@ -331,8 +337,31 @@ public class DigestScheme extends RFC2617Scheme { a2 = method + ':' + uri; } else if (qop == QOP_AUTH_INT) { // Method ":" digest-uri-value ":" H(entity-body) - //TODO: calculate entity hash if entity is repeatable - throw new AuthenticationException("qop-int method is not suppported"); + HttpEntity entity = null; + if (request instanceof HttpEntityEnclosingRequest) { + entity = ((HttpEntityEnclosingRequest) request).getEntity(); + } + if (entity != null && !entity.isRepeatable()) { + // If the entity is not repeatable, try falling back onto QOP_AUTH + if (qopset.contains("auth")) { + qop = QOP_AUTH; + a2 = method + ':' + uri; + } else { + throw new AuthenticationException("Qop auth-int cannot be used with " + + "a non-repeatable entity"); + } + } else { + HttpEntityDigester entityDigester = new HttpEntityDigester(digester); + try { + if (entity != null) { + entity.writeTo(entityDigester); + } + entityDigester.close(); + } catch (IOException ex) { + throw new AuthenticationException("I/O error reading entity content", ex); + } + a2 = method + ':' + uri + ':' + encode(entityDigester.getDigest()); + } } else { a2 = method + ':' + uri; } @@ -413,7 +442,7 @@ public class DigestScheme extends RFC2617Scheme { * @param binaryData array containing the digest * @return encoded MD5, or null if encoding failed */ - private static String encode(byte[] binaryData) { + static String encode(byte[] binaryData) { int n = binaryData.length; char[] buffer = new char[n * 2]; for (int i = 0; i < n; i++) { diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java b/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java new file mode 100644 index 000000000..75fe561b3 --- /dev/null +++ b/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java @@ -0,0 +1,75 @@ +/* + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.http.impl.auth; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.MessageDigest; + +class HttpEntityDigester extends OutputStream { + + private final MessageDigest digester; + private boolean closed; + private byte[] digest; + + HttpEntityDigester(final MessageDigest digester) { + super(); + this.digester = digester; + this.digester.reset(); + } + + @Override + public void write(int b) throws IOException { + if (this.closed) { + throw new IOException("Stream has been already closed"); + } + this.digester.update((byte) b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (this.closed) { + throw new IOException("Stream has been already closed"); + } + this.digester.update(b, off, len); + } + + @Override + public void close() throws IOException { + if (this.closed) { + return; + } + this.closed = true; + this.digest = this.digester.digest(); + super.close(); + } + + public byte[] getDigest() { + return this.digest; + } + +} diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java b/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java index 021c39d33..87fe20059 100644 --- a/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java +++ b/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java @@ -52,13 +52,14 @@ public abstract class RFC2617Scheme extends AuthSchemeBase { /** * Authentication parameter map. */ - private Map params; + private final Map params; /** * Default constructor for RFC2617 compliant authentication schemes. */ public RFC2617Scheme() { super(); + this.params = new HashMap(); } @Override @@ -70,8 +71,7 @@ public abstract class RFC2617Scheme extends AuthSchemeBase { if (elements.length == 0) { throw new MalformedChallengeException("Authentication challenge is empty"); } - - this.params = new HashMap(elements.length); + this.params.clear(); for (HeaderElement element : elements) { this.params.put(element.getName(), element.getValue()); } @@ -83,9 +83,6 @@ public abstract class RFC2617Scheme extends AuthSchemeBase { * @return the map of authentication parameters */ protected Map getParameters() { - if (this.params == null) { - this.params = new HashMap(); - } return this.params; } @@ -98,9 +95,6 @@ public abstract class RFC2617Scheme extends AuthSchemeBase { */ public String getParameter(final String name) { if (name == null) { - throw new IllegalArgumentException("Parameter name may not be null"); - } - if (this.params == null) { return null; } return this.params.get(name.toLowerCase(Locale.ENGLISH)); diff --git a/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java b/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java index dbfe18286..96f1029f3 100644 --- a/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java +++ b/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java @@ -35,6 +35,8 @@ import org.apache.http.auth.MalformedChallengeException; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpRequest; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; import org.apache.http.util.EncodingUtils; import org.junit.Assert; import org.junit.Test; @@ -76,7 +78,8 @@ public class TestBasicScheme { authscheme.processChallenge(challenge); HttpRequest request = new BasicHttpRequest("GET", "/"); - Header authResponse = authscheme.authenticate(creds, request); + HttpContext context = new BasicHttpContext(); + Header authResponse = authscheme.authenticate(creds, request, context); String expected = "Basic " + EncodingUtils.getAsciiString( Base64.encodeBase64(EncodingUtils.getAsciiBytes("testuser:testpass"))); @@ -98,7 +101,8 @@ public class TestBasicScheme { authscheme.processChallenge(challenge); HttpRequest request = new BasicHttpRequest("GET", "/"); - Header authResponse = authscheme.authenticate(creds, request); + HttpContext context = new BasicHttpContext(); + Header authResponse = authscheme.authenticate(creds, request, context); String expected = "Basic " + EncodingUtils.getAsciiString( Base64.encodeBase64(EncodingUtils.getAsciiBytes("testuser:testpass"))); diff --git a/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java b/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java index 19bbec3c5..e6ae62394 100644 --- a/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java +++ b/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java @@ -26,11 +26,15 @@ package org.apache.http.impl.auth; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.MessageDigest; import java.util.HashMap; import java.util.Map; import org.apache.http.Header; import org.apache.http.HeaderElement; +import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpRequest; import org.apache.http.auth.AuthScheme; import org.apache.http.auth.AuthenticationException; @@ -38,9 +42,15 @@ import org.apache.http.auth.Credentials; import org.apache.http.auth.AUTH; import org.apache.http.auth.MalformedChallengeException; import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.entity.InputStreamEntity; +import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHeaderValueParser; +import org.apache.http.message.BasicHttpEntityEnclosingRequest; import org.apache.http.message.BasicHttpRequest; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; import org.junit.Assert; import org.junit.Test; @@ -50,14 +60,14 @@ import org.junit.Test; public class TestDigestScheme { @Test(expected=MalformedChallengeException.class) - public void testDigestAuthenticationWithNoRealm() throws Exception { + public void testDigestAuthenticationEmptyChallenge1() throws Exception { Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, "Digest"); AuthScheme authscheme = new DigestScheme(); authscheme.processChallenge(authChallenge); } @Test(expected=MalformedChallengeException.class) - public void testDigestAuthenticationWithNoRealm2() throws Exception { + public void testDigestAuthenticationEmptyChallenge2() throws Exception { Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, "Digest "); AuthScheme authscheme = new DigestScheme(); authscheme.processChallenge(authChallenge); @@ -70,8 +80,11 @@ public class TestDigestScheme { HttpRequest request = new BasicHttpRequest("Simple", "/"); Credentials cred = new UsernamePasswordCredentials("username","password"); DigestScheme authscheme = new DigestScheme(); + HttpContext context = new BasicHttpContext(); authscheme.processChallenge(authChallenge); - Header authResponse = authscheme.authenticate(cred, request); + Header authResponse = authscheme.authenticate(cred, request, context); + Assert.assertTrue(authscheme.isComplete()); + Assert.assertFalse(authscheme.isConnectionBased()); Map table = parseAuthResponse(authResponse); Assert.assertEquals("username", table.get("username")); @@ -88,8 +101,9 @@ public class TestDigestScheme { HttpRequest request = new BasicHttpRequest("Simple", "/"); Credentials cred = new UsernamePasswordCredentials("username","password"); DigestScheme authscheme = new DigestScheme(); + HttpContext context = new BasicHttpContext(); authscheme.processChallenge(authChallenge); - Header authResponse = authscheme.authenticate(cred, request); + Header authResponse = authscheme.authenticate(cred, request, context); Map table = parseAuthResponse(authResponse); Assert.assertEquals("username", table.get("username")); @@ -99,6 +113,47 @@ public class TestDigestScheme { Assert.assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response")); } + @Test + public void testDigestAuthenticationInvalidInput() throws Exception { + String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\""; + Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge); + HttpRequest request = new BasicHttpRequest("Simple", "/"); + Credentials cred = new UsernamePasswordCredentials("username","password"); + DigestScheme authscheme = new DigestScheme(); + HttpContext context = new BasicHttpContext(); + authscheme.processChallenge(authChallenge); + try { + authscheme.authenticate(null, request, context); + Assert.fail("IllegalArgumentException should have been thrown"); + } catch (IllegalArgumentException ex) { + } + try { + authscheme.authenticate(cred, null, context); + Assert.fail("IllegalArgumentException should have been thrown"); + } catch (IllegalArgumentException ex) { + } + } + + @Test + public void testDigestAuthenticationOverrideParameter() throws Exception { + String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\""; + Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge); + HttpRequest request = new BasicHttpRequest("Simple", "/"); + Credentials cred = new UsernamePasswordCredentials("username","password"); + DigestScheme authscheme = new DigestScheme(); + HttpContext context = new BasicHttpContext(); + authscheme.processChallenge(authChallenge); + authscheme.overrideParamter("realm", "other realm"); + Header authResponse = authscheme.authenticate(cred, request, context); + + Map table = parseAuthResponse(authResponse); + Assert.assertEquals("username", table.get("username")); + Assert.assertEquals("other realm", table.get("realm")); + Assert.assertEquals("/", table.get("uri")); + Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); + Assert.assertEquals("3f211de10463cbd055ab4cd9c5158eac", table.get("response")); + } + @Test public void testDigestAuthenticationWithSHA() throws Exception { String challenge = "Digest realm=\"realm1\", " + @@ -107,9 +162,10 @@ public class TestDigestScheme { Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge); HttpRequest request = new BasicHttpRequest("Simple", "/"); Credentials cred = new UsernamePasswordCredentials("username","password"); + HttpContext context = new BasicHttpContext(); DigestScheme authscheme = new DigestScheme(); authscheme.processChallenge(authChallenge); - Header authResponse = authscheme.authenticate(cred, request); + Header authResponse = authscheme.authenticate(cred, request, context); Map table = parseAuthResponse(authResponse); Assert.assertEquals("username", table.get("username")); @@ -125,9 +181,10 @@ public class TestDigestScheme { Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge); HttpRequest request = new BasicHttpRequest("Simple", "/?param=value"); Credentials cred = new UsernamePasswordCredentials("username","password"); + HttpContext context = new BasicHttpContext(); DigestScheme authscheme = new DigestScheme(); authscheme.processChallenge(authChallenge); - Header authResponse = authscheme.authenticate(cred, request); + Header authResponse = authscheme.authenticate(cred, request, context); Map table = parseAuthResponse(authResponse); Assert.assertEquals("username", table.get("username")); @@ -146,9 +203,10 @@ public class TestDigestScheme { Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge1); HttpRequest request = new BasicHttpRequest("Simple", "/"); + HttpContext context = new BasicHttpContext(); DigestScheme authscheme = new DigestScheme(); authscheme.processChallenge(authChallenge); - Header authResponse = authscheme.authenticate(cred, request); + Header authResponse = authscheme.authenticate(cred, request, context); Map table = parseAuthResponse(authResponse); Assert.assertEquals("username", table.get("username")); @@ -160,7 +218,7 @@ public class TestDigestScheme { authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge2); DigestScheme authscheme2 = new DigestScheme(); authscheme2.processChallenge(authChallenge); - authResponse = authscheme2.authenticate(cred2, request); + authResponse = authscheme2.authenticate(cred2, request, context); table = parseAuthResponse(authResponse); Assert.assertEquals("uname2", table.get("username")); @@ -170,6 +228,32 @@ public class TestDigestScheme { Assert.assertEquals("0283edd9ef06a38b378b3b74661391e9", table.get("response")); } + @Test(expected=AuthenticationException.class) + public void testDigestAuthenticationNoRealm() throws Exception { + String challenge = "Digest no-realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\""; + Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge); + HttpContext context = new BasicHttpContext(); + DigestScheme authscheme = new DigestScheme(); + authscheme.processChallenge(authChallenge); + + Credentials cred = new UsernamePasswordCredentials("username","password"); + HttpRequest request = new BasicHttpRequest("Simple", "/"); + authscheme.authenticate(cred, request, context); + } + + @Test(expected=AuthenticationException.class) + public void testDigestAuthenticationNoNonce() throws Exception { + String challenge = "Digest realm=\"realm1\", no-nonce=\"f2a3f18799759d4f1a1c068b92b573cb\""; + Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge); + HttpContext context = new BasicHttpContext(); + DigestScheme authscheme = new DigestScheme(); + authscheme.processChallenge(authChallenge); + + Credentials cred = new UsernamePasswordCredentials("username","password"); + HttpRequest request = new BasicHttpRequest("Simple", "/"); + authscheme.authenticate(cred, request, context); + } + /** * Test digest authentication using the MD5-sess algorithm. */ @@ -193,10 +277,11 @@ public class TestDigestScheme { Credentials cred = new UsernamePasswordCredentials(username, password); HttpRequest request = new BasicHttpRequest("Simple", "/"); + HttpContext context = new BasicHttpContext(); DigestScheme authscheme = new DigestScheme(); authscheme.processChallenge(authChallenge); - Header authResponse = authscheme.authenticate(cred, request); + Header authResponse = authscheme.authenticate(cred, request, context); String response = authResponse.getValue(); Assert.assertTrue(response.indexOf("nc=00000001") > 0); // test for quotes @@ -239,10 +324,11 @@ public class TestDigestScheme { Credentials cred = new UsernamePasswordCredentials(username, password); HttpRequest request = new BasicHttpRequest("Simple", "/"); + HttpContext context = new BasicHttpContext(); DigestScheme authscheme = new DigestScheme(); authscheme.processChallenge(authChallenge); - Header authResponse = authscheme.authenticate(cred, request); + Header authResponse = authscheme.authenticate(cred, request, context); Map table = parseAuthResponse(authResponse); Assert.assertEquals(username, table.get("username")); @@ -283,7 +369,8 @@ public class TestDigestScheme { Credentials cred = new UsernamePasswordCredentials(username, password); HttpRequest request = new BasicHttpRequest("Simple", "/"); - authscheme.authenticate(cred, request); + HttpContext context = new BasicHttpContext(); + authscheme.authenticate(cred, request, context); } /** @@ -312,7 +399,8 @@ public class TestDigestScheme { Credentials cred = new UsernamePasswordCredentials(username, password); HttpRequest request = new BasicHttpRequest("Simple", "/"); - authscheme.authenticate(cred, request); + HttpContext context = new BasicHttpContext(); + authscheme.authenticate(cred, request, context); } @Test @@ -346,24 +434,25 @@ public class TestDigestScheme { Header authChallenge1 = new BasicHeader(AUTH.WWW_AUTH, challenge1); HttpRequest request = new BasicHttpRequest("GET", "/"); Credentials cred = new UsernamePasswordCredentials("username","password"); + HttpContext context = new BasicHttpContext(); DigestScheme authscheme = new DigestScheme(); authscheme.processChallenge(authChallenge1); - Header authResponse1 = authscheme.authenticate(cred, request); + Header authResponse1 = authscheme.authenticate(cred, request, context); Map table1 = parseAuthResponse(authResponse1); Assert.assertEquals("00000001", table1.get("nc")); - Header authResponse2 = authscheme.authenticate(cred, request); + Header authResponse2 = authscheme.authenticate(cred, request, context); Map table2 = parseAuthResponse(authResponse2); Assert.assertEquals("00000002", table2.get("nc")); String challenge2 = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth"; Header authChallenge2 = new BasicHeader(AUTH.WWW_AUTH, challenge2); authscheme.processChallenge(authChallenge2); - Header authResponse3 = authscheme.authenticate(cred, request); + Header authResponse3 = authscheme.authenticate(cred, request, context); Map table3 = parseAuthResponse(authResponse3); Assert.assertEquals("00000003", table3.get("nc")); String challenge3 = "Digest realm=\"realm1\", nonce=\"e273f1776275974f1a120d8b92c5b3cb\", qop=auth"; Header authChallenge3 = new BasicHeader(AUTH.WWW_AUTH, challenge3); authscheme.processChallenge(authChallenge3); - Header authResponse4 = authscheme.authenticate(cred, request); + Header authResponse4 = authscheme.authenticate(cred, request, context); Map table4 = parseAuthResponse(authResponse4); Assert.assertEquals("00000001", table4.get("nc")); } @@ -375,15 +464,16 @@ public class TestDigestScheme { Header authChallenge1 = new BasicHeader(AUTH.WWW_AUTH, challenge1); HttpRequest request = new BasicHttpRequest("GET", "/"); Credentials cred = new UsernamePasswordCredentials("username","password"); + HttpContext context = new BasicHttpContext(); DigestScheme authscheme = new DigestScheme(); authscheme.processChallenge(authChallenge1); - Header authResponse1 = authscheme.authenticate(cred, request); + Header authResponse1 = authscheme.authenticate(cred, request, context); Map table1 = parseAuthResponse(authResponse1); Assert.assertEquals("00000001", table1.get("nc")); String cnonce1 = authscheme.getCnonce(); String sessionKey1 = authscheme.getA1(); - Header authResponse2 = authscheme.authenticate(cred, request); + Header authResponse2 = authscheme.authenticate(cred, request, context); Map table2 = parseAuthResponse(authResponse2); Assert.assertEquals("00000002", table2.get("nc")); String cnonce2 = authscheme.getCnonce(); @@ -396,7 +486,7 @@ public class TestDigestScheme { "charset=utf-8, realm=\"subnet.domain.com\""; Header authChallenge2 = new BasicHeader(AUTH.WWW_AUTH, challenge2); authscheme.processChallenge(authChallenge2); - Header authResponse3 = authscheme.authenticate(cred, request); + Header authResponse3 = authscheme.authenticate(cred, request, context); Map table3 = parseAuthResponse(authResponse3); Assert.assertEquals("00000003", table3.get("nc")); @@ -410,7 +500,7 @@ public class TestDigestScheme { "charset=utf-8, realm=\"subnet.domain.com\""; Header authChallenge3 = new BasicHeader(AUTH.WWW_AUTH, challenge3); authscheme.processChallenge(authChallenge3); - Header authResponse4 = authscheme.authenticate(cred, request); + Header authResponse4 = authscheme.authenticate(cred, request, context); Map table4 = parseAuthResponse(authResponse4); Assert.assertEquals("00000001", table4.get("nc")); @@ -420,4 +510,113 @@ public class TestDigestScheme { Assert.assertFalse(cnonce1.equals(cnonce4)); Assert.assertFalse(sessionKey1.equals(sessionKey4)); } + + @Test + public void testHttpEntityDigest() throws Exception { + HttpEntityDigester digester = new HttpEntityDigester(MessageDigest.getInstance("MD5")); + Assert.assertNull(digester.getDigest()); + digester.write('a'); + digester.write('b'); + digester.write('c'); + digester.write(0xe4); + digester.write(0xf6); + digester.write(0xfc); + digester.write(new byte[] { 'a', 'b', 'c'}); + Assert.assertNull(digester.getDigest()); + digester.close(); + Assert.assertEquals("acd2b59cd01c7737d8069015584c6cac", DigestScheme.encode(digester.getDigest())); + try { + digester.write('a'); + Assert.fail("IOException should have been thrown"); + } catch (IOException ex) { + } + try { + digester.write(new byte[] { 'a', 'b', 'c'}); + Assert.fail("IOException should have been thrown"); + } catch (IOException ex) { + } + } + + @Test + public void testDigestAuthenticationQopAuthInt() throws Exception { + String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " + + "qop=\"auth,auth-int\""; + Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge); + HttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("Post", "/"); + request.setEntity(new StringEntity("abc\u00e4\u00f6\u00fcabc", HTTP.DEFAULT_CONTENT_CHARSET)); + Credentials cred = new UsernamePasswordCredentials("username","password"); + DigestScheme authscheme = new DigestScheme(); + HttpContext context = new BasicHttpContext(); + authscheme.processChallenge(authChallenge); + Header authResponse = authscheme.authenticate(cred, request, context); + + Assert.assertEquals("Post:/:acd2b59cd01c7737d8069015584c6cac", authscheme.getA2()); + + Map table = parseAuthResponse(authResponse); + Assert.assertEquals("username", table.get("username")); + Assert.assertEquals("realm1", table.get("realm")); + Assert.assertEquals("/", table.get("uri")); + Assert.assertEquals("auth-int", table.get("qop")); + Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); + } + + @Test + public void testDigestAuthenticationQopAuthIntNullEntity() throws Exception { + String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " + + "qop=\"auth,auth-int\""; + Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge); + HttpRequest request = new BasicHttpEntityEnclosingRequest("Post", "/"); + Credentials cred = new UsernamePasswordCredentials("username","password"); + DigestScheme authscheme = new DigestScheme(); + HttpContext context = new BasicHttpContext(); + authscheme.processChallenge(authChallenge); + Header authResponse = authscheme.authenticate(cred, request, context); + + Assert.assertEquals("Post:/:d41d8cd98f00b204e9800998ecf8427e", authscheme.getA2()); + + Map table = parseAuthResponse(authResponse); + Assert.assertEquals("username", table.get("username")); + Assert.assertEquals("realm1", table.get("realm")); + Assert.assertEquals("/", table.get("uri")); + Assert.assertEquals("auth-int", table.get("qop")); + Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); + } + + @Test + public void testDigestAuthenticationQopAuthOrAuthIntNonRepeatableEntity() throws Exception { + String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " + + "qop=\"auth,auth-int\""; + Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge); + HttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("Post", "/"); + request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[] {'a'}), -1)); + Credentials cred = new UsernamePasswordCredentials("username","password"); + DigestScheme authscheme = new DigestScheme(); + HttpContext context = new BasicHttpContext(); + authscheme.processChallenge(authChallenge); + Header authResponse = authscheme.authenticate(cred, request, context); + + Assert.assertEquals("Post:/", authscheme.getA2()); + + Map table = parseAuthResponse(authResponse); + Assert.assertEquals("username", table.get("username")); + Assert.assertEquals("realm1", table.get("realm")); + Assert.assertEquals("/", table.get("uri")); + Assert.assertEquals("auth", table.get("qop")); + Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); + } + + @Test(expected=AuthenticationException.class) + public void testDigestAuthenticationQopIntOnlyNonRepeatableEntity() throws Exception { + String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " + + "qop=\"auth-int\""; + Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge); + HttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("Post", "/"); + request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[] {'a'}), -1)); + Credentials cred = new UsernamePasswordCredentials("username","password"); + DigestScheme authscheme = new DigestScheme(); + HttpContext context = new BasicHttpContext(); + authscheme.processChallenge(authChallenge); + authscheme.authenticate(cred, request, context); + } + } diff --git a/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java b/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java index 895362874..78d07ac83 100644 --- a/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java +++ b/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java @@ -73,11 +73,13 @@ public class TestRFC2617Scheme { authscheme.processChallenge(header); Assert.assertEquals("test", authscheme.getSchemeName()); + Assert.assertEquals("test", authscheme.toString()); Assert.assertEquals("realm1", authscheme.getParameter("realm")); Assert.assertEquals(null, authscheme.getParameter("test")); Assert.assertEquals("stuff", authscheme.getParameter("test1")); Assert.assertEquals("stuff, stuff", authscheme.getParameter("test2")); Assert.assertEquals("\"crap", authscheme.getParameter("test3")); + Assert.assertEquals(null, authscheme.getParameter(null)); } @Test @@ -87,12 +89,23 @@ public class TestRFC2617Scheme { buffer.append(" WWW-Authenticate: Test realm=\"realm1\""); Header header = new BufferedHeader(buffer); + authscheme.processChallenge(header); Assert.assertEquals("test", authscheme.getSchemeName()); Assert.assertEquals("realm1", authscheme.getParameter("realm")); } + @Test + public void testNullHeader() throws Exception { + TestAuthScheme authscheme = new TestAuthScheme(); + try { + authscheme.processChallenge(null); + Assert.fail("IllegalArgumentException should have been thrown"); + } catch (IllegalArgumentException ex) { + } + } + @Test(expected=MalformedChallengeException.class) public void testInvalidHeader() throws Exception { TestAuthScheme authscheme = new TestAuthScheme(); @@ -100,6 +113,13 @@ public class TestRFC2617Scheme { authscheme.processChallenge(header); } + @Test(expected=MalformedChallengeException.class) + public void testInvalidSchemeName() throws Exception { + TestAuthScheme authscheme = new TestAuthScheme(); + Header header = new BasicHeader(AUTH.WWW_AUTH, "Not-a-Test realm=\"realm1\""); + authscheme.processChallenge(header); + } + @Test(expected=MalformedChallengeException.class) public void testEmptyHeader() throws Exception { TestAuthScheme authscheme = new TestAuthScheme();