Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.security.authentication.DigestAuthenticator;
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
Expand All @@ -58,6 +59,8 @@

public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
{
private LoginAuthenticator authenticator;

private String realm = "TestRealm";

public void startBasic(Scenario scenario, Handler handler) throws Exception
Expand All @@ -67,15 +70,27 @@ public void startBasic(Scenario scenario, Handler handler) throws Exception

public void startBasic(Scenario scenario, Handler handler, Charset charset) throws Exception
{
BasicAuthenticator authenticator = new BasicAuthenticator();
BasicAuthenticator basicAuthenticator = new BasicAuthenticator();
if (charset != null)
authenticator.setCharset(charset);
basicAuthenticator.setCharset(charset);
basicAuthenticator.setProxyMode(isProxyMode());
authenticator = basicAuthenticator;

start(scenario, authenticator, handler);
}

public void startDigest(Scenario scenario, Handler handler) throws Exception
{
start(scenario, new DigestAuthenticator(), handler);
DigestAuthenticator digestAuthenticator = new DigestAuthenticator();
digestAuthenticator.setProxyMode(isProxyMode());
authenticator = digestAuthenticator;

start(scenario, authenticator, handler);
}

protected boolean isProxyMode()
{
return false;
}

private void start(Scenario scenario, Authenticator authenticator, Handler handler) throws Exception
Expand Down Expand Up @@ -166,11 +181,11 @@ public void onSuccess(Request request)
};
client.getRequestListeners().addListener(requestListener);

// Request without Authentication causes a 401
// Request without authentication causes 401 / 407
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme()).path("/secure");
ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
assertNotNull(response);
assertEquals(401, response.getStatus());
assertEquals(authenticator.getUnauthorizedStatusCode(), response.getStatus());
assertTrue(requests.get().await(5, TimeUnit.SECONDS));
client.getRequestListeners().removeListener(requestListener);

Expand All @@ -187,7 +202,7 @@ public void onSuccess(Request request)
};
client.getRequestListeners().addListener(requestListener);

// Request with authentication causes a 401 (no previous successful authentication) + 200
// Request with authentication causes a 401 / 407 (no previous successful authentication) + 200
request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme()).path("/secure");
response = request.timeout(5, TimeUnit.SECONDS).send();
assertNotNull(response);
Expand All @@ -206,7 +221,7 @@ public void onSuccess(Request request)
};
client.getRequestListeners().addListener(requestListener);

// Further requests do not trigger 401 because there is a previous successful authentication
// Further requests do not trigger 401 / 407 because there is a previous successful authentication
// Remove existing header to be sure it's added by the implementation
request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme()).path("/secure");
response = request.timeout(5, TimeUnit.SECONDS).send();
Expand Down Expand Up @@ -358,7 +373,7 @@ public void onSuccess(Request request)
request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme()).path("/secure");
response = request.timeout(5, TimeUnit.SECONDS).send();
assertNotNull(response);
assertEquals(401, response.getStatus());
assertEquals(authenticator.getUnauthorizedStatusCode(), response.getStatus());
assertTrue(requests.get().await(5, TimeUnit.SECONDS));
}

Expand All @@ -376,7 +391,7 @@ public void testBasicAuthenticationWithWrongPassword(Scenario scenario) throws E
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme()).path("/secure");
ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
assertNotNull(response);
assertEquals(401, response.getStatus());
assertEquals(authenticator.getUnauthorizedStatusCode(), response.getStatus());

Authentication.Result authenticationResult = authenticationStore.findAuthenticationResult(uri);
assertNull(authenticationResult);
Expand Down Expand Up @@ -415,6 +430,7 @@ public Result authenticate(Request request, ContentResponse response, HeaderInfo
.send(result ->
{
assertTrue(result.isFailed());
assertEquals(authenticator.getUnauthorizedStatusCode(), result.getResponse().getStatus());
assertEquals(cause, result.getFailure().getMessage());
latch.countDown();
});
Expand All @@ -430,7 +446,7 @@ public void testPreemptedAuthentication(Scenario scenario) throws Exception

AuthenticationStore authenticationStore = client.getAuthenticationStore();
URI uri = URI.create(scenario.getScheme() + "://localhost:" + connector.getLocalPort());
authenticationStore.addAuthenticationResult(new BasicAuthentication.BasicResult(uri, "basic", "basic"));
authenticationStore.addAuthenticationResult(new BasicAuthentication.BasicResult(uri, authenticator.getAuthorizationHeader(), "basic", "basic"));

AtomicInteger requests = new AtomicInteger();
client.getRequestListeners().addListener(new Request.Listener()
Expand Down Expand Up @@ -471,7 +487,7 @@ public void testNonReproducibleContent(Scenario scenario) throws Exception
.body(content);
request.send(result ->
{
if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.UNAUTHORIZED_401)
if (result.isSucceeded() && result.getResponse().getStatus() == authenticator.getUnauthorizedStatusCode())
resultLatch.countDown();
});

Expand All @@ -495,29 +511,12 @@ public boolean handle(org.eclipse.jetty.server.Request request, org.eclipse.jett
});

CountDownLatch authLatch = new CountDownLatch(1);
client.getProtocolHandlers().remove(WWWAuthenticationProtocolHandler.NAME);
client.getProtocolHandlers().put(new WWWAuthenticationProtocolHandler(client)
{
@Override
public Response.Listener getResponseListener()
{
Response.Listener listener = super.getResponseListener();
return new Response.Listener()
{
@Override
public void onSuccess(Response response)
{
authLatch.countDown();
}

@Override
public void onComplete(Result result)
{
listener.onComplete(result);
}
};
}
});
client.getProtocolHandlers().remove(
isProxyMode()
? ProxyAuthenticationProtocolHandler.NAME
: WWWAuthenticationProtocolHandler.NAME
);
client.getProtocolHandlers().put(newAuthenticationProtocolHandler(authLatch));

AuthenticationStore authenticationStore = client.getAuthenticationStore();
URI uri = URI.create(scenario.getScheme() + "://localhost:" + connector.getLocalPort());
Expand Down Expand Up @@ -587,10 +586,19 @@ public void testInfiniteAuthentication(Scenario scenario) throws Exception
@Override
public boolean handle(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback)
{
// Always reply with a 401 to see if the client
// Always reply with a 401 / 407 to see if the client
// can handle an infinite authentication loop.
response.setStatus(HttpStatus.UNAUTHORIZED_401);
response.getHeaders().put(HttpHeader.WWW_AUTHENTICATE, authType);
response.setStatus(
isProxyMode()
? HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407
: HttpStatus.UNAUTHORIZED_401
);
response.getHeaders().put(
isProxyMode()
? HttpHeader.PROXY_AUTHENTICATE
: HttpHeader.WWW_AUTHENTICATE,
authType
);
callback.succeeded();
return true;
}
Expand Down Expand Up @@ -629,7 +637,10 @@ public void apply(Request request)
.scheme(scenario.getScheme())
.send();

assertEquals(HttpStatus.UNAUTHORIZED_401, response.getStatus());
assertEquals(
isProxyMode() ? HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407 : HttpStatus.UNAUTHORIZED_401,
response.getStatus()
);
}

@Test
Expand Down Expand Up @@ -810,6 +821,50 @@ public void testSingleChallengeLooksLikeMultipleChallenges()
assertEquals(headerInfo.getParameter("nonce"), "1523430383=");
}

private AuthenticationProtocolHandler newAuthenticationProtocolHandler(CountDownLatch authLatch)
{
if (isProxyMode())
{
return new ProxyAuthenticationProtocolHandler(client)
{
@Override
public Response.Listener getResponseListener()
{
return wrapAuthenticationSuccess(super.getResponseListener(), authLatch);
}
};
}
else
{
return new WWWAuthenticationProtocolHandler(client)
{
@Override
public Response.Listener getResponseListener()
{
return wrapAuthenticationSuccess(super.getResponseListener(), authLatch);
}
};
}
}

private Response.Listener wrapAuthenticationSuccess(Response.Listener base, CountDownLatch latch)
{
return new Response.Listener()
{
@Override
public void onSuccess(Response response)
{
latch.countDown();
}

@Override
public void onComplete(Result result)
{
base.onComplete(result);
}
};
}

private static class GeneratingRequestContent implements Request.Content
{
private final IntFunction<ByteBuffer> generator;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.client;

/**
* Tests authentication in proxy mode by subclassing {@link HttpClientAuthenticationTest}.
*/
public class HttpClientProxyAuthenticationTest extends HttpClientAuthenticationTest
{
@Override
protected boolean isProxyMode()
{
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
import java.nio.charset.StandardCharsets;
import java.util.Base64;

import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.security.AuthenticationState;
import org.eclipse.jetty.security.Authenticator;
import org.eclipse.jetty.security.ServerAuthException;
Expand Down Expand Up @@ -50,7 +48,7 @@ public String getAuthenticationType()
@Override
public AuthenticationState validateRequest(Request req, Response res, Callback callback) throws ServerAuthException
{
String credentials = req.getHeaders().get(HttpHeader.AUTHORIZATION);
String credentials = req.getHeaders().get(getAuthorizationHeader());

if (credentials != null)
{
Expand Down Expand Up @@ -86,10 +84,10 @@ public AuthenticationState validateRequest(Request req, Response res, Callback c
Charset charset = getCharset();
if (charset != null)
value += ", charset=\"" + charset.name() + "\"";
res.getHeaders().put(HttpHeader.WWW_AUTHENTICATE.asString(), value);
res.getHeaders().put(getChallengeHeader().asString(), value);

// Don't use AuthenticationState.writeError, to avoid possibility of doing a Servlet error dispatch.
Response.writeError(req, res, callback, HttpStatus.UNAUTHORIZED_401);
Response.writeError(req, res, callback, getUnauthorizedStatusCode());
return AuthenticationState.CHALLENGE;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;

import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.security.AuthenticationState;
import org.eclipse.jetty.security.Authenticator;
import org.eclipse.jetty.security.SecurityHandler;
Expand Down Expand Up @@ -112,7 +110,7 @@ public String getAuthenticationType()
@Override
public AuthenticationState validateRequest(Request req, Response res, Callback callback) throws ServerAuthException
{
String credentials = req.getHeaders().get(HttpHeader.AUTHORIZATION);
String credentials = req.getHeaders().get(getAuthorizationHeader());

boolean stale = false;
if (credentials != null)
Expand Down Expand Up @@ -185,15 +183,15 @@ else if (n == 0)
String domain = req.getContext().getContextPath();
if (domain == null)
domain = "/";
res.getHeaders().put(HttpHeader.WWW_AUTHENTICATE.asString(), "Digest realm=\"" + _loginService.getName() +
res.getHeaders().put(getChallengeHeader().asString(), "Digest realm=\"" + _loginService.getName() +
"\", domain=\"" + domain +
"\", nonce=\"" + newNonce(req) +
"\", algorithm=" + getAlgorithm() +
", qop=\"auth\"" +
", stale=" + stale);

// Don't use AuthenticationState.writeError, to avoid possibility of doing a Servlet error dispatch.
Response.writeError(req, res, callback, HttpStatus.UNAUTHORIZED_401);
Response.writeError(req, res, callback, getUnauthorizedStatusCode());
return AuthenticationState.CHALLENGE;
}

Expand Down
Loading