Skip to content

Commit

Permalink
ProxyServer should have its own ntlmHost and scheme, close #797
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephane Landelle committed Jan 9, 2015
1 parent 71536e5 commit 577615a
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 121 deletions.
31 changes: 30 additions & 1 deletion api/src/main/java/org/asynchttpclient/ProxyServer.java
Expand Up @@ -16,13 +16,15 @@
*/ */
package org.asynchttpclient; package org.asynchttpclient;


import static java.nio.charset.StandardCharsets.*; import static java.nio.charset.StandardCharsets.UTF_8;


import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;


import org.asynchttpclient.Realm.AuthScheme;

/** /**
* Represents a proxy server. * Represents a proxy server.
*/ */
Expand Down Expand Up @@ -56,6 +58,8 @@ public String toString() {
private final String url; private final String url;
private Charset charset = UTF_8; private Charset charset = UTF_8;
private String ntlmDomain = System.getProperty("http.auth.ntlm.domain", ""); private String ntlmDomain = System.getProperty("http.auth.ntlm.domain", "");
private String ntlmHost;
private AuthScheme scheme = AuthScheme.BASIC;


public ProxyServer(final Protocol protocol, final String host, final int port, String principal, String password) { public ProxyServer(final Protocol protocol, final String host, final int port, String principal, String password) {
this.protocol = protocol; this.protocol = protocol;
Expand All @@ -78,6 +82,15 @@ public ProxyServer(final String host, final int port) {
this(Protocol.HTTP, host, port, null, null); this(Protocol.HTTP, host, port, null, null);
} }


public Realm.RealmBuilder realmBuilder() {
return new Realm.RealmBuilder()//
.setNtlmDomain(ntlmDomain)
.setNtlmHost(ntlmHost)
.setPrincipal(principal)
.setPassword(password)
.setScheme(scheme);
}

public Protocol getProtocol() { public Protocol getProtocol() {
return protocol; return protocol;
} }
Expand Down Expand Up @@ -134,6 +147,22 @@ public String getNtlmDomain() {
return ntlmDomain; return ntlmDomain;
} }


public AuthScheme getScheme() {
return scheme;
}

public void setScheme(AuthScheme scheme) {
this.scheme = scheme;
}

public String getNtlmHost() {
return ntlmHost;
}

public void setNtlmHost(String ntlmHost) {
this.ntlmHost = ntlmHost;
}

@Override @Override
public String toString() { public String toString() {
return url; return url;
Expand Down
18 changes: 7 additions & 11 deletions api/src/main/java/org/asynchttpclient/Realm.java
Expand Up @@ -50,7 +50,7 @@ public class Realm {
private final String methodName; private final String methodName;
private final boolean usePreemptiveAuth; private final boolean usePreemptiveAuth;
private final Charset charset; private final Charset charset;
private final String host; private final String ntlmHost;
private final String ntlmDomain; private final String ntlmDomain;
private final boolean useAbsoluteURI; private final boolean useAbsoluteURI;
private final boolean omitQuery; private final boolean omitQuery;
Expand Down Expand Up @@ -79,7 +79,7 @@ private Realm(AuthScheme scheme, String principal, String password, String realm
this.methodName = method; this.methodName = method;
this.usePreemptiveAuth = usePreemptiveAuth; this.usePreemptiveAuth = usePreemptiveAuth;
this.ntlmDomain = ntlmDomain; this.ntlmDomain = ntlmDomain;
this.host = host; this.ntlmHost = host;
this.charset = charset; this.charset = charset;
this.useAbsoluteURI = useAbsoluteURI; this.useAbsoluteURI = useAbsoluteURI;
this.omitQuery = omitQuery; this.omitQuery = omitQuery;
Expand All @@ -94,10 +94,6 @@ public String getPassword() {
return password; return password;
} }


public AuthScheme getAuthScheme() {
return scheme;
}

public AuthScheme getScheme() { public AuthScheme getScheme() {


return scheme; return scheme;
Expand Down Expand Up @@ -171,7 +167,7 @@ public String getNtlmDomain() {
* @return the NTLM host * @return the NTLM host
*/ */
public String getNtlmHost() { public String getNtlmHost() {
return host; return ntlmHost;
} }


public boolean isUseAbsoluteURI() { public boolean isUseAbsoluteURI() {
Expand Down Expand Up @@ -271,7 +267,7 @@ public static class RealmBuilder {
private boolean usePreemptive; private boolean usePreemptive;
private String ntlmDomain = System.getProperty("http.auth.ntlm.domain", ""); private String ntlmDomain = System.getProperty("http.auth.ntlm.domain", "");
private Charset charset = UTF_8; private Charset charset = UTF_8;
private String host = "localhost"; private String ntlmHost = "localhost";
private boolean useAbsoluteURI = true; private boolean useAbsoluteURI = true;
private boolean omitQuery; private boolean omitQuery;
private boolean targetProxy; private boolean targetProxy;
Expand All @@ -297,11 +293,11 @@ public RealmBuilder setNtlmDomain(String ntlmDomain) {
} }


public String getNtlmHost() { public String getNtlmHost() {
return host; return ntlmHost;
} }


public RealmBuilder setNtlmHost(String host) { public RealmBuilder setNtlmHost(String host) {
this.host = host; this.ntlmHost = host;
return this; return this;
} }


Expand Down Expand Up @@ -624,7 +620,7 @@ public Realm build() {
} }


return new Realm(scheme, principal, password, realmName, nonce, algorithm, response, qop, nc, cnonce, uri, methodName, return new Realm(scheme, principal, password, realmName, nonce, algorithm, response, qop, nc, cnonce, uri, methodName,
usePreemptive, ntlmDomain, charset, host, opaque, useAbsoluteURI, omitQuery, targetProxy); usePreemptive, ntlmDomain, charset, ntlmHost, opaque, useAbsoluteURI, omitQuery, targetProxy);
} }
} }
} }
2 changes: 1 addition & 1 deletion api/src/test/java/org/asynchttpclient/RealmTest.java
Expand Up @@ -40,7 +40,7 @@ public void testClone() {
assertEquals(clone.getUsePreemptiveAuth(), orig.getUsePreemptiveAuth()); assertEquals(clone.getUsePreemptiveAuth(), orig.getUsePreemptiveAuth());
assertEquals(clone.getRealmName(), orig.getRealmName()); assertEquals(clone.getRealmName(), orig.getRealmName());
assertEquals(clone.getAlgorithm(), orig.getAlgorithm()); assertEquals(clone.getAlgorithm(), orig.getAlgorithm());
assertEquals(clone.getAuthScheme(), orig.getAuthScheme()); assertEquals(clone.getScheme(), orig.getScheme());
} }


@Test(groups = "fast") @Test(groups = "fast")
Expand Down
Expand Up @@ -451,7 +451,7 @@ private void addAuthorizationHeader(final Request request, final HttpRequestPack


private String generateAuthHeader(final Realm realm) { private String generateAuthHeader(final Realm realm) {
try { try {
switch (realm.getAuthScheme()) { switch (realm.getScheme()) {
case BASIC: case BASIC:
return computeBasicAuthentication(realm); return computeBasicAuthentication(realm);
case DIGEST: case DIGEST:
Expand Down
Expand Up @@ -96,7 +96,7 @@ private Realm getRealm(final Request request) {


private String generateAuthHeader(final Realm realm) { private String generateAuthHeader(final Realm realm) {
try { try {
switch (realm.getAuthScheme()) { switch (realm.getScheme()) {
case BASIC: case BASIC:
return computeBasicAuthentication(realm); return computeBasicAuthentication(realm);
case DIGEST: case DIGEST:
Expand Down
Expand Up @@ -66,7 +66,7 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpT
responsePacket.setSkipRemainder(true); // ignore the remainder of the response responsePacket.setSkipRemainder(true); // ignore the remainder of the response


final Request req = httpTransactionContext.getRequest(); final Request req = httpTransactionContext.getRequest();
realm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(req.getUri()) realm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getScheme()).setUri(req.getUri())
.setMethodName(req.getMethod()).setUsePreemptiveAuth(true).parseWWWAuthenticateHeader(auth).build(); .setMethodName(req.getMethod()).setUsePreemptiveAuth(true).parseWWWAuthenticateHeader(auth).build();
String lowerCaseAuth = auth.toLowerCase(Locale.ENGLISH); String lowerCaseAuth = auth.toLowerCase(Locale.ENGLISH);
if (lowerCaseAuth.startsWith("basic")) { if (lowerCaseAuth.startsWith("basic")) {
Expand Down
Expand Up @@ -30,6 +30,7 @@
import org.asynchttpclient.FluentCaseInsensitiveStringsMap; import org.asynchttpclient.FluentCaseInsensitiveStringsMap;
import org.asynchttpclient.ProxyServer; import org.asynchttpclient.ProxyServer;
import org.asynchttpclient.Realm; import org.asynchttpclient.Realm;
import org.asynchttpclient.Realm.AuthScheme;
import org.asynchttpclient.Request; import org.asynchttpclient.Request;
import org.asynchttpclient.RequestBuilder; import org.asynchttpclient.RequestBuilder;
import org.asynchttpclient.ntlm.NTLMEngine; import org.asynchttpclient.ntlm.NTLMEngine;
Expand Down Expand Up @@ -63,28 +64,51 @@ public HttpProtocol(ChannelManager channelManager, AsyncHttpClientConfig config,
connectionStrategy = nettyConfig.getConnectionStrategy(); connectionStrategy = nettyConfig.getConnectionStrategy();
} }


private Realm.RealmBuilder newRealmBuilder(Realm realm) { private Realm kerberosChallenge(Channel channel,//
return realm != null ? new Realm.RealmBuilder().clone(realm) : new Realm.RealmBuilder(); List<String> authHeaders,//
Request request,//
FluentCaseInsensitiveStringsMap headers,//
Realm realm,//
NettyResponseFuture<?> future) throws NTLMEngineException {

Uri uri = request.getUri();
String host = request.getVirtualHost() == null ? uri.getHost() : request.getVirtualHost();
try {
String challengeHeader = SpnegoEngine.instance().generateToken(host);
headers.remove(HttpHeaders.Names.AUTHORIZATION);
headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader);

return new Realm.RealmBuilder().clone(realm)//
.setUri(uri)//
.setMethodName(request.getMethod())//
.setScheme(Realm.AuthScheme.KERBEROS)//
.build();


} catch (Throwable throwable) {
String ntlmAuthenticate = getNTLM(authHeaders);
if (ntlmAuthenticate != null) {
return ntlmChallenge(ntlmAuthenticate, request, headers, realm, future);
}
requestSender.abort(channel, future, throwable);
return null;
}
} }


private Realm kerberosChallenge(Channel channel,// private Realm kerberosProxyChallenge(Channel channel,//
List<String> proxyAuth,// List<String> proxyAuth,//
Request request,// Request request,//
ProxyServer proxyServer,// ProxyServer proxyServer,//
FluentCaseInsensitiveStringsMap headers,// FluentCaseInsensitiveStringsMap headers,//
Realm realm,// NettyResponseFuture<?> future) throws NTLMEngineException {
NettyResponseFuture<?> future,//
boolean proxyInd) throws NTLMEngineException {


Uri uri = request.getUri(); Uri uri = request.getUri();
String host = request.getVirtualHost() == null ? uri.getHost() : request.getVirtualHost();
String server = proxyServer == null ? host : proxyServer.getHost();
try { try {
String challengeHeader = SpnegoEngine.instance().generateToken(server); String challengeHeader = SpnegoEngine.instance().generateToken(proxyServer.getHost());
headers.remove(HttpHeaders.Names.AUTHORIZATION); headers.remove(HttpHeaders.Names.AUTHORIZATION);
headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader);


return newRealmBuilder(realm)// return proxyServer.realmBuilder()//
.setUri(uri)// .setUri(uri)//
.setMethodName(request.getMethod())// .setMethodName(request.getMethod())//
.setScheme(Realm.AuthScheme.KERBEROS)// .setScheme(Realm.AuthScheme.KERBEROS)//
Expand All @@ -93,13 +117,13 @@ private Realm kerberosChallenge(Channel channel,//
} catch (Throwable throwable) { } catch (Throwable throwable) {
String ntlmAuthenticate = getNTLM(proxyAuth); String ntlmAuthenticate = getNTLM(proxyAuth);
if (ntlmAuthenticate != null) { if (ntlmAuthenticate != null) {
return ntlmChallenge(ntlmAuthenticate, request, proxyServer, headers, realm, future, proxyInd); return ntlmProxyChallenge(ntlmAuthenticate, request, proxyServer, headers, future);
} }
requestSender.abort(channel, future, throwable); requestSender.abort(channel, future, throwable);
return null; return null;
} }
} }

private String authorizationHeaderName(boolean proxyInd) { private String authorizationHeaderName(boolean proxyInd) {
return proxyInd ? HttpHeaders.Names.PROXY_AUTHORIZATION : HttpHeaders.Names.AUTHORIZATION; return proxyInd ? HttpHeaders.Names.PROXY_AUTHORIZATION : HttpHeaders.Names.AUTHORIZATION;
} }
Expand All @@ -110,71 +134,55 @@ private void addNTLMAuthorizationHeader(FluentCaseInsensitiveStringsMap headers,


private Realm ntlmChallenge(String authenticateHeader,// private Realm ntlmChallenge(String authenticateHeader,//
Request request,// Request request,//
ProxyServer proxyServer,//
FluentCaseInsensitiveStringsMap headers,// FluentCaseInsensitiveStringsMap headers,//
Realm realm,// Realm realm,//
NettyResponseFuture<?> future,// NettyResponseFuture<?> future) throws NTLMEngineException {
boolean proxyInd) throws NTLMEngineException {

boolean useRealm = proxyServer == null && realm != null;


String ntlmDomain = useRealm ? realm.getNtlmDomain() : proxyServer.getNtlmDomain();
String ntlmHost = useRealm ? realm.getNtlmHost() : proxyServer.getHost();
String principal = useRealm ? realm.getPrincipal() : proxyServer.getPrincipal();
String password = useRealm ? realm.getPassword() : proxyServer.getPassword();
Uri uri = request.getUri(); Uri uri = request.getUri();


if (authenticateHeader.equals("NTLM")) { if (authenticateHeader.equals("NTLM")) {
// server replied bare NTLM => we didn't preemptively sent Type1Msg // server replied bare NTLM => we didn't preemptively sent Type1Msg
String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg(); String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg();


addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); addNTLMAuthorizationHeader(headers, challengeHeader, false);
future.getAndSetAuth(false); future.getAndSetAuth(false);
return newRealmBuilder(realm)//
.setScheme(realm.getAuthScheme())//
.setUri(uri)//
.setMethodName(request.getMethod())//
.build();


} else { } else {
// probably receiving Type2Msg, so we issue Type3Msg // probably receiving Type2Msg, so we issue Type3Msg
addType3NTLMAuthorizationHeader(authenticateHeader, headers, principal, password, ntlmDomain, ntlmHost, proxyInd); addType3NTLMAuthorizationHeader(authenticateHeader, headers, realm, false);
Realm.AuthScheme authScheme = realm != null ? realm.getAuthScheme() : Realm.AuthScheme.NTLM;
return newRealmBuilder(realm)//
.setScheme(authScheme)//
.setUri(uri)//
.setMethodName(request.getMethod())//
.build();
} }

return new Realm.RealmBuilder().clone(realm)//
.setUri(uri)//
.setMethodName(request.getMethod())//
.build();
} }


private Realm ntlmProxyChallenge(String authenticateHeader,// private Realm ntlmProxyChallenge(String authenticateHeader,//
Request request,// Request request,//
ProxyServer proxyServer,// ProxyServer proxyServer,//
FluentCaseInsensitiveStringsMap headers,// FluentCaseInsensitiveStringsMap headers,//
Realm realm,// NettyResponseFuture<?> future) throws NTLMEngineException {
NettyResponseFuture<?> future,//
boolean proxyInd) throws NTLMEngineException {


future.getAndSetAuth(false); future.getAndSetAuth(false);
headers.remove(HttpHeaders.Names.PROXY_AUTHORIZATION); headers.remove(HttpHeaders.Names.PROXY_AUTHORIZATION);


addType3NTLMAuthorizationHeader(authenticateHeader, headers, proxyServer.getPrincipal(), proxyServer.getPassword(), Realm realm = proxyServer.realmBuilder()//
proxyServer.getNtlmDomain(), proxyServer.getHost(), proxyInd); .setScheme(AuthScheme.NTLM)//

return newRealmBuilder(realm)//
// .setScheme(realm.getAuthScheme())
.setUri(request.getUri())// .setUri(request.getUri())//
.setMethodName(request.getMethod()).build(); .setMethodName(request.getMethod()).build();

addType3NTLMAuthorizationHeader(authenticateHeader, headers, realm, true);

return realm;
} }


private void addType3NTLMAuthorizationHeader(String auth, FluentCaseInsensitiveStringsMap headers, String username, private void addType3NTLMAuthorizationHeader(String auth, FluentCaseInsensitiveStringsMap headers, Realm realm, boolean proxyInd) throws NTLMEngineException {
String password, String domain, String workstation, boolean proxyInd) throws NTLMEngineException {
headers.remove(authorizationHeaderName(proxyInd)); headers.remove(authorizationHeaderName(proxyInd));


if (isNonEmpty(auth) && auth.startsWith("NTLM ")) { if (isNonEmpty(auth) && auth.startsWith("NTLM ")) {
String serverChallenge = auth.substring("NTLM ".length()).trim(); String serverChallenge = auth.substring("NTLM ".length()).trim();
String challengeHeader = NTLMEngine.INSTANCE.generateType3Msg(username, password, domain, workstation, serverChallenge); String challengeHeader = NTLMEngine.INSTANCE.generateType3Msg(realm.getPrincipal(), realm.getPassword(), realm.getNtlmDomain(), realm.getNtlmHost(), serverChallenge);
addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd);
} }
} }
Expand Down Expand Up @@ -233,18 +241,17 @@ private boolean exitAfterHandling401(//
String ntlmAuthenticate = getNTLM(wwwAuthHeaders); String ntlmAuthenticate = getNTLM(wwwAuthHeaders);
if (!wwwAuthHeaders.contains("Kerberos") && ntlmAuthenticate != null) { if (!wwwAuthHeaders.contains("Kerberos") && ntlmAuthenticate != null) {
// NTLM // NTLM
newRealm = ntlmChallenge(ntlmAuthenticate, request, proxyServer, request.getHeaders(), realm, future, false); newRealm = ntlmChallenge(ntlmAuthenticate, request, request.getHeaders(), realm, future);


} else if (negociate) { } else if (negociate) {
// SPNEGO KERBEROS // SPNEGO KERBEROS
newRealm = kerberosChallenge(channel, wwwAuthHeaders, request, proxyServer, request.getHeaders(), realm, future, false); newRealm = kerberosChallenge(channel, wwwAuthHeaders, request, request.getHeaders(), realm, future);
if (newRealm == null) if (newRealm == null)
return true; return true;


} else { } else {
newRealm = new Realm.RealmBuilder()// newRealm = new Realm.RealmBuilder()//
.clone(realm)// .clone(realm)//
.setScheme(realm.getAuthScheme())//
.setUri(request.getUri())// .setUri(request.getUri())//
.setMethodName(request.getMethod())// .setMethodName(request.getMethod())//
.setUsePreemptiveAuth(true)// .setUsePreemptiveAuth(true)//
Expand Down Expand Up @@ -304,17 +311,16 @@ private boolean exitAfterHandling407(//
boolean negociate = proxyAuthHeaders.contains("Negotiate"); boolean negociate = proxyAuthHeaders.contains("Negotiate");
String ntlmAuthenticate = getNTLM(proxyAuthHeaders); String ntlmAuthenticate = getNTLM(proxyAuthHeaders);
if (!proxyAuthHeaders.contains("Kerberos") && ntlmAuthenticate != null) { if (!proxyAuthHeaders.contains("Kerberos") && ntlmAuthenticate != null) {
newRealm = ntlmProxyChallenge(ntlmAuthenticate, request, proxyServer, requestHeaders, realm, future, true); newRealm = ntlmProxyChallenge(ntlmAuthenticate, request, proxyServer, requestHeaders, future);
// SPNEGO KERBEROS // SPNEGO KERBEROS


} else if (negociate) { } else if (negociate) {
newRealm = kerberosChallenge(channel, proxyAuthHeaders, request, proxyServer, requestHeaders, realm, future, true); newRealm = kerberosProxyChallenge(channel, proxyAuthHeaders, request, proxyServer, requestHeaders, future);
if (newRealm == null) if (newRealm == null)
return true; return true;


} else { } else {
newRealm = new Realm.RealmBuilder().clone(realm)// newRealm = new Realm.RealmBuilder().clone(realm)//
.setScheme(realm.getAuthScheme())//
.setUri(request.getUri())// .setUri(request.getUri())//
.setOmitQuery(true)// .setOmitQuery(true)//
.setMethodName(HttpMethod.CONNECT.getName())// .setMethodName(HttpMethod.CONNECT.getName())//
Expand Down

0 comments on commit 577615a

Please sign in to comment.