Skip to content

Commit

Permalink
further improvements to proxied digest auth
Browse files Browse the repository at this point in the history
- some unit tests still missing
- also fix tests after adding route
  • Loading branch information
rburgstaller committed Jan 13, 2017
1 parent 4c13311 commit 29ca520
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 14 deletions.
9 changes: 8 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,15 @@ android {
}
}


ext {
okhttpVersion = "3.5.0"
}

dependencies {
compile 'com.squareup.okhttp3:okhttp:3.5.0'
compile "com.squareup.okhttp3:okhttp:${okhttpVersion}"
testCompile "com.squareup.okhttp3:logging-interceptor:${okhttpVersion}"

testCompile 'org.hamcrest:hamcrest-junit:2.0.0.0'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:2.5.7'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import okhttp3.Route;
import okhttp3.internal.platform.Platform;

import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;

/**
Expand Down Expand Up @@ -42,7 +43,8 @@ public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(authRequest);

// Cached response was used, but it produced unauthorized response (cache expired).
if (authenticator != null && response != null && response.code() == HTTP_UNAUTHORIZED) {
int responseCode = response != null ? response.code() : 0;
if (authenticator != null && (responseCode == HTTP_UNAUTHORIZED || responseCode == HTTP_PROXY_AUTH)) {
// Remove cached authenticator and resend request
if (authCache.remove(key) != null) {
response.body().close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
import okhttp3.Route;
import okhttp3.internal.platform.Platform;

import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;

/**
* Standard HTTP basic authenticator.
*/
public class BasicAuthenticator implements CachingAuthenticator {
private final Credentials credentials;
private boolean proxy;

public BasicAuthenticator(Credentials credentials) {
this.credentials = credentials;
Expand All @@ -23,19 +26,22 @@ public BasicAuthenticator(Credentials credentials) {
@Override
public Request authenticate(Route route, Response response) throws IOException {
final Request request = response.request();
proxy = response.code() == HTTP_PROXY_AUTH;
return authFromRequest(request);
}

private Request authFromRequest(Request request) {
// prevent infinite loops when the password is wrong
final String authorizationHeader = request.header("Authorization");
String header = proxy ? "Proxy-Authorization" : "Authorization";

final String authorizationHeader = request.header(header);
if (authorizationHeader != null && authorizationHeader.startsWith("Basic")) {
Platform.get().log(Platform.WARN, "previous basic authentication failed, returning null", null);
return null;
}
String authValue = okhttp3.Credentials.basic(credentials.getUserName(), credentials.getPassword());
return request.newBuilder()
.header("Authorization", authValue)
.header(header, authValue)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package com.burgstaller.okhttp;

import com.burgstaller.okhttp.basic.BasicAuthenticator;
import com.burgstaller.okhttp.digest.Credentials;
import com.burgstaller.okhttp.digest.DigestAuthenticator;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;

import okhttp3.Authenticator;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import okhttp3.logging.HttpLoggingInterceptor.Level;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

/**
* Tests Proxy authentication. This test can only be run with a properly configured proxy server.
* To use this test set up a proxy server with the given credentials which can be provided via
* System properties or environment variables:
* <dl>
* <dt>PROXY_HOST</dt><dd>The hostname of the proxy server</dd>
* <dt>PROXY_PORT</dt><dd>The TCPIP port number of the proxy server</dd>
* <dt>PROXY_PASSWORD</dt><dd>The proxy server password for the users 'okhttp_basic' and
* 'okhttp_digest'</dd>
* </dl>
*/
@Ignore
public class ProxyAuthenticationManualTest {

private static final HttpLoggingInterceptor.Logger LOGGER = new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
System.out.println(message);
}
};
private static final HttpLoggingInterceptor LOGGING_INTERCEPTOR = new HttpLoggingInterceptor(LOGGER);
private static final String AUTH_BASIC_USERNAME = "okhttp_basic";
private static final String AUTH_DIGEST_USERNAME = "okhttp_digest";

static {
LOGGING_INTERCEPTOR.setLevel(Level.HEADERS);
}

private Proxy proxy;
private String authPass = "allCorrect@auth";

@Before
public void setupProxy() {

String proxyAddress = System.getenv("PROXY_HOST");
if (proxyAddress == null) {
proxyAddress = System.getProperty("PROXY_HOST", "localhost");
}
String proxyPortString = System.getenv("PROXY_PORT");
if (proxyPortString == null) {
proxyPortString = System.getProperty("PROXY_PORT", "8080");
}
authPass = System.getenv("PROXY_PASSWORD");
if (authPass == null) {
authPass = System.getProperty("PROXY_PASSWORD", "password");
}
int proxyPort = Integer.valueOf(proxyPortString);
proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyAddress, proxyPort));
}

@Test
public void testConnection_WithoutProxy_Expect200() throws IOException {
final OkHttpClient client = new OkHttpClient.Builder()
.build();
final Request request = new Request.Builder()
.url("https://www.google.com/favicon.ico")
.build();
Response response = client.newCall(request).execute();
assertEquals(200, response.code());
}

@Test
public void testConnection_WithProxyButNoAuth_ExpectAuthException() throws IOException {
final OkHttpClient client = givenHttpClientWithoutAuth();
final Request request = new Request.Builder()
.url("https://www.google.com/favicon.ico")
.build();
try {
client.newCall(request).execute();
fail("this call should fail with an exception");
} catch (IOException e) {
assertEquals("Failed to authenticate with proxy", e.getMessage());
}
}

@Test
public void testConnection_WithProxyBasicAuthWithoutTunnel_Expect200() throws IOException {
final BasicAuthenticator authenticator = new BasicAuthenticator(
new Credentials(AUTH_BASIC_USERNAME, authPass));

final OkHttpClient client = givenHttpClientWithProxyAuth(authenticator);
final Request request = new Request.Builder()
.url("http://edition.cnn.com")
.build();
Response response = client.newCall(request).execute();
assertEquals(200, response.code());
}

@Test
public void testConnection_WithProxyBasicAuthWithTunnel_Expect200() throws IOException {
final BasicAuthenticator authenticator = new BasicAuthenticator(
new Credentials(AUTH_BASIC_USERNAME, authPass));

final OkHttpClient client = givenHttpClientWithProxyAuth(authenticator);
final Request request = new Request.Builder()
.url("https://www.google.com/favicon.ico")
.build();
Response response = client.newCall(request).execute();
assertEquals(200, response.code());
}


@Test
public void testConnection_WithProxyBasicAuthWithNotAllowedSites_Expect403() throws IOException {
final BasicAuthenticator authenticator = new BasicAuthenticator(
new Credentials(AUTH_BASIC_USERNAME, authPass));

final OkHttpClient client = givenHttpClientWithProxyAuth(authenticator);
final Request request = new Request.Builder()
.url("http://www.youtube.com")
.build();
Response response = client.newCall(request).execute();
assertEquals(403, response.code());
}

@Test
public void testConnection_WithProxyDigestAuthWithoutTunnel_Expect200() throws IOException {
final DigestAuthenticator authenticator = new DigestAuthenticator(
new Credentials(AUTH_DIGEST_USERNAME, authPass));

final OkHttpClient client = givenHttpClientWithProxyAuth(authenticator);
final Request request = new Request.Builder()
.url("http://edition.cnn.com")
.build();
Response response = client.newCall(request).execute();
assertEquals(200, response.code());
}

@Test
public void testConnection_WithProxyDigestAuthWithTunnel_Expect200() throws IOException {
final DigestAuthenticator authenticator = new DigestAuthenticator(
new Credentials(AUTH_DIGEST_USERNAME, authPass));

final OkHttpClient client = givenHttpClientWithProxyAuth(authenticator);
final Request request = new Request.Builder()
.url("https://www.google.com/favicon.ico")
.build();
Response response = client.newCall(request).execute();
assertEquals(200, response.code());
}

@Test
public void testConnection_WithProxyDigestAuthWithNotAllowdSites_Expect403() throws IOException {
final DigestAuthenticator authenticator = new DigestAuthenticator(
new Credentials(AUTH_DIGEST_USERNAME, authPass));

final OkHttpClient client = givenHttpClientWithProxyAuth(authenticator);
final Request request = new Request.Builder()
.url("http://www.youtube.com")
.build();
Response response = client.newCall(request).execute();
assertEquals(403, response.code());
}


private OkHttpClient givenHttpClientWithProxyAuth(Authenticator authenticator) {
return new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator(authenticator)
.addNetworkInterceptor(LOGGING_INTERCEPTOR)
.build();
}

private OkHttpClient givenHttpClientWithoutAuth() {
return new OkHttpClient.Builder()
.proxy(proxy)
.addNetworkInterceptor(LOGGING_INTERCEPTOR)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,62 @@

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.util.Collections;

import javax.net.SocketFactory;

import okhttp3.Address;
import okhttp3.Authenticator;
import okhttp3.Connection;
import okhttp3.ConnectionSpec;
import okhttp3.Dns;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;
import okhttp3.Route;

import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.text.MatchesPattern.matchesPattern;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;

public class DigestAuthenticatorTest {

@Mock
private Connection mockConnection;
@Mock
private Dns mockDns;
@Mock
private SocketFactory socketFactory;
@Mock
private Authenticator proxyAuthenticator;
@Mock
private ProxySelector proxySelector;
@Mock
Proxy proxy;
private Route mockRoute;
private DigestAuthenticator authenticator;

@Before
public void setUp() throws Exception {
public void beforeMethod() {
MockitoAnnotations.initMocks(this);

// setup some dummy data so that we dont get NPEs
Address address = new Address("localhost", 8080, mockDns, socketFactory, null, null,
null, proxyAuthenticator, null, Collections.singletonList(Protocol.HTTP_1_1),
Collections.singletonList(ConnectionSpec.MODERN_TLS), proxySelector);
InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost", 8080);
mockRoute = new Route(address, proxy, inetSocketAddress);
given(mockConnection.route()).willReturn(mockRoute);

authenticator = new DigestAuthenticator(new Credentials("user1", "user1"));
}

Expand All @@ -35,7 +74,7 @@ public void testAuthenticate() throws Exception {
.header("WWW-Authenticate",
"Digest realm=\"myrealm\", nonce=\"NnjGCdMhBQA=8ede771f94b593e46e5d0dd10b68313226c133f4\", algorithm=MD5, qop=\"auth\"")
.build();
Request authenticated = authenticator.authenticate(null, response);
Request authenticated = authenticator.authenticate(mockRoute, response);

assertThat(authenticated.header("Authorization"),
matchesPattern("Digest username=\"user1\", realm=\"myrealm\", nonce=\"NnjGCdMhBQA=8ede771f94b593e46e5d0dd10b68313226c133f4\", uri=\"/\", response=\"[0-9a-f]+\", qop=auth, nc=00000001, cnonce=\"[0-9a-f]+\", algorithm=MD5"));
Expand All @@ -54,7 +93,7 @@ public void testAuthenticate__withProxy__shouldWork() throws Exception {
.header("Proxy-Authenticate",
"Digest realm=\"myrealm\", nonce=\"NnjGCdMhBQA=8ede771f94b593e46e5d0dd10b68313226c133f4\", algorithm=MD5, qop=\"auth\"")
.build();
Request authenticated = authenticator.authenticate(null, response);
Request authenticated = authenticator.authenticate(mockRoute, response);

assertThat(authenticated.header("Proxy-Authorization"),
matchesPattern("Digest username=\"user1\", realm=\"myrealm\", nonce=\"NnjGCdMhBQA=8ede771f94b593e46e5d0dd10b68313226c133f4\", uri=\"/\", response=\"[0-9a-f]+\", qop=auth, nc=00000001, cnonce=\"[0-9a-f]+\", algorithm=MD5"));
Expand Down Expand Up @@ -94,7 +133,7 @@ public void testAuthenticate__withUriPathAndParameters() throws Exception {
.header("WWW-Authenticate",
"Digest realm=\"myrealm\", nonce=\"NnjGCdMhBQA=8ede771f94b593e46e5d0dd10b68313226c133f4\", algorithm=MD5, qop=\"auth\"")
.build();
Request authenticated = authenticator.authenticate(null, response);
Request authenticated = authenticator.authenticate(mockRoute, response);

String authHeader = authenticated.header("Authorization");
assertThat(authHeader,
Expand All @@ -115,7 +154,7 @@ public void testAuthenticate_withMultipleAuthResponseHeaders_shouldWork() throws
"Digest realm=\"myrealm\", nonce=\"NnjGCdMhBQA=8ede771f94b593e46e5d0dd10b68313226c133f4\", algorithm=MD5, qop=\"auth\"")
.addHeader("WWW-Authenticate", "Basic realm=\"DVRNVRDVS\"")
.build();
Request authenticated = authenticator.authenticate(null, response);
Request authenticated = authenticator.authenticate(mockRoute, response);

assertThat(authenticated.header("Authorization"),
matchesPattern("Digest username=\"user1\", realm=\"myrealm\", nonce=\"NnjGCdMhBQA=8ede771f94b593e46e5d0dd10b68313226c133f4\", uri=\"/\", response=\"[0-9a-f]+\", qop=auth, nc=00000001, cnonce=\"[0-9a-f]+\", algorithm=MD5"));
Expand All @@ -139,7 +178,7 @@ public void testAuthenticate_withWrongPassword_shouldNotRepeat() throws IOExcept
.build();

// when
final Request result = authenticator.authenticate(null, response);
final Request result = authenticator.authenticate(mockRoute, response);

// then
assertThat(result, is(nullValue()));
Expand Down Expand Up @@ -187,7 +226,7 @@ public void testAuthenticate_withDifferentNonceAndStale_shouldRetry() throws IOE
.build();

// when
final Request authenticated = authenticator.authenticate(null, response);
final Request authenticated = authenticator.authenticate(mockRoute, response);

// then
assertThat(authenticated.header("Authorization"),
Expand All @@ -212,7 +251,7 @@ public void testAuthenticate_withDifferentNonceAndStaleAndQuotes_shouldRetry() t
.build();

// when
final Request authenticated = authenticator.authenticate(null, response);
final Request authenticated = authenticator.authenticate(mockRoute, response);

// then
assertThat(authenticated.header("Authorization"),
Expand Down

0 comments on commit 29ca520

Please sign in to comment.