From 298506eb0e3653fab209c91a2ddc751f3351acda Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Tue, 6 Dec 2022 21:05:23 +0100 Subject: [PATCH] New Authenticator interface method to return an auth result with additional challenge parameters --- .../async/AuthenticatingAsyncDecorator.java | 21 +++-- .../auth/AbstractAuthenticationHandler.java | 2 +- .../hc/client5/testing/auth/AuthResult.java | 92 +++++++++++++++++++ .../client5/testing/auth/Authenticator.java | 7 ++ .../auth/BasicAuthenticationHandler.java | 21 ++++- .../classic/AuthenticatingDecorator.java | 22 +++-- 6 files changed, 151 insertions(+), 14 deletions(-) create mode 100644 httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AuthResult.java diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AuthenticatingAsyncDecorator.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AuthenticatingAsyncDecorator.java index f0bff871d..1f621aa92 100644 --- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AuthenticatingAsyncDecorator.java +++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AuthenticatingAsyncDecorator.java @@ -28,10 +28,12 @@ package org.apache.hc.client5.testing.async; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.Collections; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import org.apache.hc.client5.testing.auth.AuthResult; import org.apache.hc.client5.testing.auth.AuthenticationHandler; import org.apache.hc.client5.testing.auth.Authenticator; import org.apache.hc.client5.testing.auth.BasicAuthenticationHandler; @@ -43,6 +45,7 @@ import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.message.BasicNameValuePair; @@ -77,7 +80,7 @@ public class AuthenticatingAsyncDecorator implements AsyncServerExchangeHandler } public AuthenticatingAsyncDecorator(final AsyncServerExchangeHandler exchangeHandler, final Authenticator authenticator) { - this(exchangeHandler, new BasicAuthenticationHandler(), authenticator); + this(exchangeHandler, new BasicAuthenticationHandler(StandardCharsets.US_ASCII), authenticator); } protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) { @@ -95,20 +98,26 @@ public class AuthenticatingAsyncDecorator implements AsyncServerExchangeHandler final URIAuthority authority = request.getAuthority(); final String requestUri = request.getRequestUri(); - final boolean authenticated = authenticator.authenticate(authority, requestUri, challengeResponse); + final AuthResult authResult = authenticator.perform(authority, requestUri, challengeResponse); final Header expect = request.getFirstHeader(HttpHeaders.EXPECT); final boolean expectContinue = expect != null && "100-continue".equalsIgnoreCase(expect.getValue()); - if (authenticated) { + if (authResult.isSuccess()) { if (expectContinue) { responseChannel.sendInformation(new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE), context); } exchangeHandler.handleRequest(request, entityDetails, responseChannel, context); } else { final HttpResponse unauthorized = new BasicHttpResponse(HttpStatus.SC_UNAUTHORIZED); + final List challengeParams = new ArrayList<>(); final String realm = authenticator.getRealm(authority, requestUri); - final String challenge = authenticationHandler.challenge( - realm != null ? Collections.singletonList(new BasicNameValuePair("realm", realm)) : null); + if (realm != null) { + challengeParams.add(new BasicNameValuePair("realm", realm)); + } + if (authResult.hasParams()) { + challengeParams.addAll(authResult.getParams()); + } + final String challenge = authenticationHandler.challenge(challengeParams); unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, challenge); customizeUnauthorizedResponse(unauthorized); diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AbstractAuthenticationHandler.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AbstractAuthenticationHandler.java index 7ba77f3b4..5c2420bb2 100644 --- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AbstractAuthenticationHandler.java +++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/AbstractAuthenticationHandler.java @@ -54,7 +54,7 @@ abstract class AbstractAuthenticationHandler implements AuthenticationHandler. + * + */ + +package org.apache.hc.client5.testing.auth; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.util.LangUtils; + +public final class AuthResult { + + private final boolean success; + private final List params; + + public AuthResult(final boolean success, final List params) { + this.success = success; + this.params = params != null ? Collections.unmodifiableList(params) : Collections.emptyList(); + } + + public AuthResult(final boolean success, final NameValuePair... params) { + this(success, Arrays.asList(params)); + } + + public boolean isSuccess() { + return success; + } + + public boolean hasParams() { + return !params.isEmpty(); + } + + public List getParams() { + return params; + } + + @Override + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, this.success); + return hash; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o instanceof AuthResult) { + final AuthResult that = (AuthResult) o; + return this.success == that.success; + } + return false; + } + + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + buf.append(success); + if (!params.isEmpty()) { + buf.append(" ").append(params); + } + return buf.toString(); + } + +} diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/Authenticator.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/Authenticator.java index 911b3c455..0b6dda974 100644 --- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/Authenticator.java +++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/Authenticator.java @@ -31,6 +31,13 @@ import org.apache.hc.core5.net.URIAuthority; public interface Authenticator { + /** + * @since 5.3 + */ + default AuthResult perform(URIAuthority authority, String requestUri, String credentials) { + return new AuthResult(authenticate(authority, requestUri, credentials)); + } + boolean authenticate(URIAuthority authority, String requestUri, String credentials); String getRealm(URIAuthority authority, String requestUri); diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthenticationHandler.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthenticationHandler.java index c77e83349..3bbe32b07 100644 --- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthenticationHandler.java +++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthenticationHandler.java @@ -27,13 +27,32 @@ package org.apache.hc.client5.testing.auth; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import org.apache.hc.client5.http.auth.StandardAuthScheme; import org.apache.hc.client5.http.utils.Base64; +import org.apache.hc.core5.util.Args; public class BasicAuthenticationHandler extends AbstractAuthenticationHandler { + private final Charset charset; + + /** + * @since 5.3 + */ + public BasicAuthenticationHandler(final Charset charset) { + this.charset = Args.notNull(charset, "Charset"); + } + + /** + * @deprecated Use {@link #BasicAuthenticationHandler(Charset)} + */ + @Deprecated + public BasicAuthenticationHandler() { + this(StandardCharsets.US_ASCII); + } + @Override String getSchemeName() { return StandardAuthScheme.BASIC; @@ -43,7 +62,7 @@ public class BasicAuthenticationHandler extends AbstractAuthenticationHandler { String decodeChallenge(final String challenge) throws IllegalArgumentException { final byte[] bytes = challenge.getBytes(StandardCharsets.US_ASCII); final Base64 codec = new Base64(); - return new String(codec.decode(bytes), StandardCharsets.US_ASCII); + return new String(codec.decode(bytes), charset); } } diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/AuthenticatingDecorator.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/AuthenticatingDecorator.java index e0598043e..cc116e4e3 100644 --- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/AuthenticatingDecorator.java +++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/AuthenticatingDecorator.java @@ -28,8 +28,11 @@ package org.apache.hc.client5.testing.classic; import java.io.IOException; -import java.util.Collections; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import org.apache.hc.client5.testing.auth.AuthResult; import org.apache.hc.client5.testing.auth.AuthenticationHandler; import org.apache.hc.client5.testing.auth.Authenticator; import org.apache.hc.client5.testing.auth.BasicAuthenticationHandler; @@ -39,6 +42,7 @@ import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.io.HttpServerRequestHandler; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; @@ -67,7 +71,7 @@ public class AuthenticatingDecorator implements HttpServerRequestHandler { public AuthenticatingDecorator(final HttpServerRequestHandler requestHandler, final Authenticator authenticator) { - this(requestHandler, new BasicAuthenticationHandler(), authenticator); + this(requestHandler, new BasicAuthenticationHandler(StandardCharsets.US_ASCII), authenticator); } protected void customizeUnauthorizedResponse(final ClassicHttpResponse unauthorized) { @@ -84,20 +88,26 @@ public class AuthenticatingDecorator implements HttpServerRequestHandler { final URIAuthority authority = request.getAuthority(); final String requestUri = request.getRequestUri(); - final boolean authenticated = authenticator.authenticate(authority, requestUri, challengeResponse); + final AuthResult authResult = authenticator.perform(authority, requestUri, challengeResponse); final Header expect = request.getFirstHeader(HttpHeaders.EXPECT); final boolean expectContinue = expect != null && "100-continue".equalsIgnoreCase(expect.getValue()); - if (authenticated) { + if (authResult.isSuccess()) { if (expectContinue) { responseTrigger.sendInformation(new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE)); } requestHandler.handle(request, responseTrigger, context); } else { final ClassicHttpResponse unauthorized = new BasicClassicHttpResponse(HttpStatus.SC_UNAUTHORIZED); + final List challengeParams = new ArrayList<>(); final String realm = authenticator.getRealm(authority, requestUri); - final String challenge = authenticationHandler.challenge( - realm != null ? Collections.singletonList(new BasicNameValuePair("realm", realm)) : null); + if (realm != null) { + challengeParams.add(new BasicNameValuePair("realm", realm)); + } + if (authResult.hasParams()) { + challengeParams.addAll(authResult.getParams()); + } + final String challenge = authenticationHandler.challenge(challengeParams); unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, challenge); customizeUnauthorizedResponse(unauthorized); if (unauthorized.getEntity() == null) {