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
This commit is contained in:
Oleg Kalnichevski 2011-08-16 15:13:36 +00:00
parent be07d74d16
commit 1b08395e72
7 changed files with 392 additions and 61 deletions

View File

@ -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");

View File

@ -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<String> qopset = new HashSet<String>(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 <CODE>null</CODE> 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++) {

View File

@ -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
* <http://www.apache.org/>.
*
*/
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;
}
}

View File

@ -52,13 +52,14 @@ public abstract class RFC2617Scheme extends AuthSchemeBase {
/**
* Authentication parameter map.
*/
private Map<String, String> params;
private final Map<String, String> params;
/**
* Default constructor for RFC2617 compliant authentication schemes.
*/
public RFC2617Scheme() {
super();
this.params = new HashMap<String, String>();
}
@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<String, String>(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<String, String> getParameters() {
if (this.params == null) {
this.params = new HashMap<String, String>();
}
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));

View File

@ -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")));

View File

@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> table1 = parseAuthResponse(authResponse1);
Assert.assertEquals("00000001", table1.get("nc"));
Header authResponse2 = authscheme.authenticate(cred, request);
Header authResponse2 = authscheme.authenticate(cred, request, context);
Map<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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);
}
}

View File

@ -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();