Index: httpclient-cache/src/test/java/org/apache/http/client/cache/impl/HttpTestUtils.java =================================================================== --- httpclient-cache/src/test/java/org/apache/http/client/cache/impl/HttpTestUtils.java (revision 943418) +++ httpclient-cache/src/test/java/org/apache/http/client/cache/impl/HttpTestUtils.java (working copy) @@ -79,6 +79,17 @@ } /* + * Determines whether a given header name may appear multiple times. + */ + public static boolean isMultiHeader(String name) { + for (String s : MULTI_HEADERS) { + if (s.equalsIgnoreCase(name)) + return true; + } + return false; + } + + /* * Determines whether a given header name may only appear once in a message. */ public static boolean isSingleHeader(String name) { @@ -88,7 +99,6 @@ } return false; } - /* * Assert.asserts that two request or response bodies are byte-equivalent. */ Index: httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntry.java =================================================================== --- httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntry.java (revision 943418) +++ httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntry.java (working copy) @@ -26,8 +26,6 @@ */ package org.apache.http.client.cache.impl; -import static junit.framework.Assert.assertFalse; - import java.util.Date; import java.util.Set; @@ -376,8 +374,7 @@ new BasicHeader("Cache-Control", "public") }; CacheEntry entry = getEntry(headers); - - assertFalse(entry.isRevalidatable()); + Assert.assertFalse(entry.isRevalidatable()); } Index: httpclient/src/test/java/org/apache/http/impl/auth/TestNegotiateScheme.java =================================================================== --- httpclient/src/test/java/org/apache/http/impl/auth/TestNegotiateScheme.java (revision 943418) +++ httpclient/src/test/java/org/apache/http/impl/auth/TestNegotiateScheme.java (working copy) @@ -26,17 +26,17 @@ */ package org.apache.http.impl.auth; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - import java.io.IOException; import java.security.Principal; +import junit.framework.Assert; + import org.apache.http.HttpEntity; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; import org.apache.http.auth.AuthScheme; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; @@ -44,8 +44,6 @@ import org.apache.http.client.params.AuthPolicy; import org.apache.http.client.params.ClientPNames; import org.apache.http.entity.StringEntity; -import org.apache.http.impl.auth.NegotiateScheme; -import org.apache.http.impl.auth.NegotiateSchemeFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.localserver.BasicServerTestBase; import org.apache.http.localserver.LocalTestServer; @@ -59,8 +57,9 @@ import org.ietf.jgss.GSSName; import org.ietf.jgss.Oid; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; +import org.mockito.Matchers; +import org.mockito.Mockito; /** * Tests for {@link NegotiateScheme}. @@ -79,13 +78,15 @@ * This service will continue to ask for authentication. */ private static class PleaseNegotiateService implements HttpRequestHandler { - public void handle(final HttpRequest request, + + public void handle( + final HttpRequest request, final HttpResponse response, final HttpContext context) throws HttpException, IOException { - response.setStatusCode(401); + response.setStatusCode(HttpStatus.SC_UNAUTHORIZED); response.addHeader(new BasicHeader("WWW-Authenticate", "Negotiate blablabla")); + response.addHeader(new BasicHeader("Connection", "Keep-Alive")); response.setEntity(new StringEntity("auth required ")); - response.addHeader(new BasicHeader("Connection", "Keep-Alive")); } } @@ -96,29 +97,34 @@ * */ private static class NegotiateSchemeWithMockGssManager extends NegotiateScheme { - GSSManager manager = mock(GSSManager.class); - GSSName name = mock(GSSName.class); - GSSContext context = mock(GSSContext.class); + + GSSManager manager = Mockito.mock(GSSManager.class); + GSSName name = Mockito.mock(GSSName.class); + GSSContext context = Mockito.mock(GSSContext.class); NegotiateSchemeWithMockGssManager() throws Exception { super(null, true); - - when(context.initSecContext(any(byte[].class), anyInt(), anyInt())) - .thenReturn("12345678".getBytes()); - when(manager.createName(any(String.class), any(Oid.class))) - .thenReturn(name); - when(manager.createContext(any(GSSName.class), any(Oid.class), any(GSSCredential.class), anyInt())) - .thenReturn(context); - + Mockito.when(context.initSecContext( + Matchers.any(byte[].class), Matchers.anyInt(), Matchers.anyInt())) + .thenReturn("12345678".getBytes()); + Mockito.when(manager.createName( + Matchers.any(String.class), Matchers.any(Oid.class))) + .thenReturn(name); + Mockito.when(manager.createContext( + Matchers.any(GSSName.class), Matchers.any(Oid.class), + Matchers.any(GSSCredential.class), Matchers.anyInt())) + .thenReturn(context); } @Override protected GSSManager getManager() { return manager; } + } private static class UseJaasCredentials implements Credentials { + public String getPassword() { return null; } @@ -126,17 +132,22 @@ public Principal getUserPrincipal() { return null; } + } private static class NegotiateSchemeFactoryWithMockGssManager extends NegotiateSchemeFactory { + NegotiateSchemeWithMockGssManager scheme; + NegotiateSchemeFactoryWithMockGssManager() throws Exception { scheme = new NegotiateSchemeWithMockGssManager(); } + @Override public AuthScheme newInstance(HttpParams params) { return scheme; } + } /** @@ -144,7 +155,6 @@ * the server still keep asking for a valid ticket. */ @Test - @Ignore public void testDontTryToAuthenticateEndlessly() throws Exception { int port = this.localServer.getServiceAddress().getPort(); this.localServer.register("*", new PleaseNegotiateService()); @@ -163,25 +173,26 @@ HttpGet httpget = new HttpGet(s); HttpResponse response = client.execute(httpget); HttpEntity e = response.getEntity(); - e.consumeContent(); + if (e != null) { + e.consumeContent(); + } + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusLine().getStatusCode()); } - /** * Javadoc specifies that {@link GSSContext#initSecContext(byte[], int, int)} can return null * if no token is generated. Client should be able to deal with this response. - * */ @Test - @Ignore - public void testNoTokenGeneratedGenerateAnError() throws Exception { + public void testNoTokenGeneratedError() throws Exception { int port = this.localServer.getServiceAddress().getPort(); this.localServer.register("*", new PleaseNegotiateService()); HttpHost target = new HttpHost("localhost", port); DefaultHttpClient client = new DefaultHttpClient(); NegotiateSchemeFactoryWithMockGssManager nsf = new NegotiateSchemeFactoryWithMockGssManager(); - when(nsf.scheme.context.initSecContext(any(byte[].class), anyInt(), anyInt())).thenReturn(null); + Mockito.when(nsf.scheme.context.initSecContext( + Matchers.any(byte[].class), Matchers.anyInt(), Matchers.anyInt())).thenReturn(null); client.getAuthSchemes().register(AuthPolicy.SPNEGO, nsf); Credentials use_jaas_creds = new UseJaasCredentials(); @@ -193,7 +204,10 @@ HttpGet httpget = new HttpGet(s); HttpResponse response = client.execute(httpget); HttpEntity e = response.getEntity(); - e.consumeContent(); + if (e != null) { + e.consumeContent(); + } + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusLine().getStatusCode()); } } Index: httpclient/src/main/java/org/apache/http/impl/auth/NTLMScheme.java =================================================================== --- httpclient/src/main/java/org/apache/http/impl/auth/NTLMScheme.java (revision 943418) +++ httpclient/src/main/java/org/apache/http/impl/auth/NTLMScheme.java (working copy) @@ -99,8 +99,9 @@ @Override protected void parseChallenge( - final CharArrayBuffer buffer, int pos, int len) throws MalformedChallengeException { - String challenge = buffer.substringTrimmed(pos, len); + final CharArrayBuffer buffer, + int beginIndex, int endIndex) throws MalformedChallengeException { + String challenge = buffer.substringTrimmed(beginIndex, endIndex); if (challenge.length() == 0) { if (this.state == State.UNINITIATED) { this.state = State.CHALLENGE_RECEIVED; Index: httpclient/src/main/java/org/apache/http/impl/auth/AuthSchemeBase.java =================================================================== --- httpclient/src/main/java/org/apache/http/impl/auth/AuthSchemeBase.java (revision 943418) +++ httpclient/src/main/java/org/apache/http/impl/auth/AuthSchemeBase.java (working copy) @@ -125,7 +125,7 @@ } protected abstract void parseChallenge( - CharArrayBuffer buffer, int pos, int len) throws MalformedChallengeException; + CharArrayBuffer buffer, int beginIndex, int endIndex) throws MalformedChallengeException; /** * Returns true if authenticating against a proxy, false Index: httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java =================================================================== --- httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java (revision 943418) +++ httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java (working copy) @@ -33,15 +33,14 @@ import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; -import org.apache.http.auth.AUTH; import org.apache.http.auth.AuthenticationException; -import org.apache.http.auth.ContextAwareAuthScheme; import org.apache.http.auth.Credentials; import org.apache.http.auth.InvalidCredentialsException; import org.apache.http.auth.MalformedChallengeException; import org.apache.http.message.BasicHeader; import org.apache.http.protocol.ExecutionContext; import org.apache.http.protocol.HttpContext; +import org.apache.http.util.CharArrayBuffer; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; @@ -54,15 +53,17 @@ * * @since 4.1 */ -public class NegotiateScheme implements ContextAwareAuthScheme { +public class NegotiateScheme extends AuthSchemeBase { - private static final int UNINITIATED = 0; - private static final int INITIATED = 1; - private static final int NEGOTIATING = 3; - private static final int ESTABLISHED = 4; - private static final int FAILED = Integer.MAX_VALUE; - private static final String SPNEGO_OID = "1.3.6.1.5.5.2"; - private static final String KERBEROS_OID = "1.2.840.113554.1.2.2"; + enum State { + UNINITIATED, + CHALLENGE_RECEIVED, + TOKEN_GENERATED, + FAILED, + } + + private static final String SPNEGO_OID = "1.3.6.1.5.5.2"; + private static final String KERBEROS_OID = "1.2.840.113554.1.2.2"; private final Log log = LogFactory.getLog(getClass()); @@ -70,12 +71,10 @@ private final boolean stripPort; - private boolean proxy; - private GSSContext gssContext = null; /** Authentication process state */ - private int state; + private State state; /** base64 decoded challenge **/ private byte[] token; @@ -88,7 +87,7 @@ */ public NegotiateScheme(final SpnegoTokenGenerator spengoGenerator, boolean stripPort) { super(); - this.state = UNINITIATED; + this.state = State.UNINITIATED; this.spengoGenerator = spengoGenerator; this.stripPort = stripPort; } @@ -109,7 +108,7 @@ * */ public boolean isComplete() { - return this.state == ESTABLISHED || this.state == FAILED; + return this.state == State.TOKEN_GENERATED || this.state == State.FAILED; } /** @@ -152,7 +151,7 @@ if (request == null) { throw new IllegalArgumentException("HTTP request may not be null"); } - if (state == UNINITIATED) { + if (state != State.CHALLENGE_RECEIVED) { throw new IllegalStateException( "Negotiation authentication process has not been initiated"); } @@ -227,19 +226,13 @@ gssContext.requestMutualAuth(true); gssContext.requestCredDeleg(true); } - state = INITIATED; - if (token == null) { token = new byte[0]; } - // HTTP 1.1 issue: - // Mutual auth will never complete to do 200 instead of 401 in - // return from server. "state" will never reach ESTABLISHED - // but it works anyway - token = gssContext.initSecContext(token, 0, token.length); if (token == null) { - throw new AuthenticationException("Failed to initialize security context"); + state = State.FAILED; + throw new AuthenticationException("GSS security context initialization failed"); } /* @@ -250,11 +243,14 @@ token = spengoGenerator.generateSpnegoDERObject(token); } + state = State.TOKEN_GENERATED; + String tokenstr = new String(Base64.encodeBase64(token, false)); if (log.isDebugEnabled()) { - log.debug("got token, sending " + token.length + " bytes to server"); + log.debug("Sending response '" + tokenstr + "' back to the auth server"); } + return new BasicHeader("Authorization", "Negotiate " + tokenstr); } catch (GSSException gsse) { - state = FAILED; + state = State.FAILED; if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) throw new InvalidCredentialsException(gsse.getMessage(), gsse); @@ -267,11 +263,9 @@ // other error throw new AuthenticationException(gsse.getMessage()); } catch (IOException ex){ - state = FAILED; + state = State.FAILED; throw new AuthenticationException(ex.getMessage()); } - return new BasicHeader("Authorization", "Negotiate " + - new String(Base64.encodeBase64(token, false)) ); } @@ -312,41 +306,21 @@ return true; } - /** - * Processes the Negotiate challenge. - * - */ - public void processChallenge(final Header header) throws MalformedChallengeException { + @Override + protected void parseChallenge( + final CharArrayBuffer buffer, + int beginIndex, int endIndex) throws MalformedChallengeException { + String challenge = buffer.substringTrimmed(beginIndex, endIndex); if (log.isDebugEnabled()) { - log.debug("Challenge header: " + header); + log.debug("Received challenge '" + challenge + "' from the auth server"); } - String authheader = header.getName(); - String challenge = header.getValue(); - if (authheader.equalsIgnoreCase(AUTH.WWW_AUTH)) { - this.proxy = false; - } else if (authheader.equalsIgnoreCase(AUTH.PROXY_AUTH)) { - this.proxy = true; + if (state == State.UNINITIATED) { + token = new Base64().decode(challenge.getBytes()); + state = State.CHALLENGE_RECEIVED; } else { - throw new MalformedChallengeException("Unexpected header name: " + authheader); + log.debug("Authentication already attempted"); + state = State.FAILED; } - - if (challenge.startsWith("Negotiate")) { - if(isComplete() == false) - state = NEGOTIATING; - - if (challenge.startsWith("Negotiate ")){ - token = new Base64().decode(challenge.substring(10).getBytes()); - if (log.isDebugEnabled()) { - log.debug("challenge = " + challenge.substring(10)); - } - } else { - token = new byte[0]; - } - } } - - public boolean isProxy() { - return this.proxy; - } - + }