New Authenticator interface method to return an auth result with additional challenge parameters

This commit is contained in:
Oleg Kalnichevski 2022-12-06 21:05:23 +01:00
parent a4784916cc
commit 298506eb0e
6 changed files with 151 additions and 14 deletions

View File

@ -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<NameValuePair> 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);

View File

@ -54,7 +54,7 @@ abstract class AbstractAuthenticationHandler implements AuthenticationHandler<St
return buf.toString();
}
abstract String decodeChallenge(String challenge) throws IllegalArgumentException;
abstract String decodeChallenge(String challenge);
public final String extractAuthToken(final String challengeResponse) throws HttpException {
final int i = challengeResponse.indexOf(' ');

View File

@ -0,0 +1,92 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
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<NameValuePair> params;
public AuthResult(final boolean success, final List<NameValuePair> 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<NameValuePair> 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();
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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<NameValuePair> 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) {