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
@@ -0,0 +1,6 @@
{
"type": "bugfix",
"category": "Apache5 HTTP Client (Preview)",
"contributor": "",
"description": "Fix bug where Basic proxy authentication fails with credentials not found."
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
import static com.github.tomakehurst.wiremock.client.WireMock.any;
import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static org.assertj.core.api.Assertions.assertThat;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import java.net.URI;
import java.util.Base64;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -42,6 +43,13 @@
* the Proxy-Authorization header is sent with the first request to the proxy.
*/
public class ApacheHttpClientProxyAuthTest {
private static final String USERNAME = "testuser";
private static final String PASSWORD = "testpass";

// Header value is "Basic " + base64(<username> + ':' + <password>)
// https://datatracker.ietf.org/doc/html/rfc7617#section-2
private static final String BASIC_PROXY_AUTH_HEADER =
"Basic " + Base64.getEncoder().encodeToString((USERNAME + ":" + PASSWORD).getBytes());

private WireMockServer mockProxy;
private SdkHttpClient httpClient;
Expand All @@ -65,7 +73,7 @@ public void teardown() {
@Test
public void proxyAuthentication_whenPreemptiveAuthEnabled_shouldSendProxyAuthorizationHeader() throws Exception {
mockProxy.stubFor(any(anyUrl())
.withHeader("Proxy-Authorization", matching("Basic .+"))
.withHeader("Proxy-Authorization", equalTo(BASIC_PROXY_AUTH_HEADER))
.willReturn(aResponse()
.withStatus(200)
.withBody("Success")));
Expand All @@ -74,8 +82,8 @@ public void proxyAuthentication_whenPreemptiveAuthEnabled_shouldSendProxyAuthori
httpClient = ApacheHttpClient.builder()
.proxyConfiguration(ProxyConfiguration.builder()
.endpoint(URI.create("http://localhost:" + mockProxy.port()))
.username("testuser")
.password("testpass")
.username(USERNAME)
.password(PASSWORD)
.preemptiveBasicAuthenticationEnabled(true)
.build())
.build();
Expand All @@ -96,7 +104,7 @@ public void proxyAuthentication_whenPreemptiveAuthEnabled_shouldSendProxyAuthori

mockProxy.verify(1, anyRequestedFor(anyUrl()));
mockProxy.verify(WireMock.getRequestedFor(anyUrl())
.withHeader("Proxy-Authorization", matching("Basic .+")));
.withHeader("Proxy-Authorization", equalTo(BASIC_PROXY_AUTH_HEADER)));
}

@Test
Expand All @@ -109,7 +117,7 @@ public void proxyAuthentication_whenPreemptiveAuthDisabled_shouldUseChallengeRes

// Second request with auth header should succeed
mockProxy.stubFor(any(anyUrl())
.withHeader("Proxy-Authorization", matching("Basic .+"))
.withHeader("Proxy-Authorization", equalTo(BASIC_PROXY_AUTH_HEADER))
.willReturn(aResponse()
.withStatus(200)
.withBody("Success")));
Expand All @@ -118,8 +126,8 @@ public void proxyAuthentication_whenPreemptiveAuthDisabled_shouldUseChallengeRes
httpClient = ApacheHttpClient.builder()
.proxyConfiguration(ProxyConfiguration.builder()
.endpoint(URI.create("http://localhost:" + mockProxy.port()))
.username("testuser")
.password("testpass")
.username(USERNAME)
.password(PASSWORD)
.preemptiveBasicAuthenticationEnabled(false)
.build())
.build();
Expand All @@ -143,6 +151,6 @@ public void proxyAuthentication_whenPreemptiveAuthDisabled_shouldUseChallengeRes
// First request without auth header
mockProxy.verify(1, anyRequestedFor(anyUrl()).withoutHeader("Proxy-Authorization"));
// Second request with auth header
mockProxy.verify(1, anyRequestedFor(anyUrl()).withHeader("Proxy-Authorization", matching("Basic .+")));
mockProxy.verify(1, anyRequestedFor(anyUrl()).withHeader("Proxy-Authorization", equalTo(BASIC_PROXY_AUTH_HEADER)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.hc.client5.http.auth.Credentials;
import org.apache.hc.client5.http.auth.CredentialsProvider;
import org.apache.hc.client5.http.auth.NTCredentials;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
Expand All @@ -35,7 +36,6 @@

@SdkInternalApi
public final class Apache5Utils {

private Apache5Utils() {
}

Expand Down Expand Up @@ -71,14 +71,14 @@
*/
public static CredentialsProvider newProxyCredentialsProvider(ProxyConfiguration proxyConfiguration) {
BasicCredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(newAuthScope(proxyConfiguration), newNtCredentials(proxyConfiguration));
provider.setCredentials(newAuthScope(proxyConfiguration), proxyCredentials(proxyConfiguration));
return provider;
}

/**
* Returns a new instance of NTCredentials used for proxy authentication.
*/
private static Credentials newNtCredentials(ProxyConfiguration proxyConfiguration) {
private static NTCredentials ntCredentials(ProxyConfiguration proxyConfiguration) {

Check warning on line 81 in http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/utils/Apache5Utils.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this use of "NTCredentials"; it is deprecated.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZq8N5oVPx18q-Pw2UdK&open=AZq8N5oVPx18q-Pw2UdK&pullRequest=6584
// Deprecated NTCredentials is used to maintain backward compatibility with Apache4.
return new NTCredentials(
proxyConfiguration.username(),
Expand All @@ -88,6 +88,23 @@
);
}

/**
* Returns the credentials object used to authenticate with a proxy. This method returns either an {@link NTCredentials}
* object if either {@link ProxyConfiguration#ntlmDomain()} or {@link ProxyConfiguration#ntlmWorkstation()} are present,
* otherwise it returns a {@link UsernamePasswordCredentials}.
*/
private static Credentials proxyCredentials(ProxyConfiguration proxyConfiguration) {
if (proxyConfiguration.ntlmWorkstation() != null || proxyConfiguration.ntlmDomain() != null) {
return ntCredentials(proxyConfiguration);
}
return usernamePasswordCredentials(proxyConfiguration);
}

public static UsernamePasswordCredentials usernamePasswordCredentials(ProxyConfiguration proxyConfiguration) {
return new UsernamePasswordCredentials(proxyConfiguration.username(),
proxyConfiguration.password().toCharArray());
}

/**
* Returns a new instance of AuthScope used for proxy authentication.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.
*/

package software.amazon.awssdk.http.apache5;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.any;
import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static org.assertj.core.api.Assertions.assertThat;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import java.net.URI;
import java.util.Base64;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.http.HttpExecuteRequest;
import software.amazon.awssdk.http.HttpExecuteResponse;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;

public class Apache5HttpClientProxyAuthTest {

Check warning on line 38 in http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientProxyAuthTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZq8N5ohPx18q-Pw2UdO&open=AZq8N5ohPx18q-Pw2UdO&pullRequest=6584
private static final String USERNAME = "testuser";
private static final String PASSWORD = "testpass";

// Header value is "Basic " + base64(<username> + ':' + <password>)
// https://datatracker.ietf.org/doc/html/rfc7617#section-2
private static final String BASIC_PROXY_AUTH_HEADER =
"Basic " + Base64.getEncoder().encodeToString((USERNAME + ":" + PASSWORD).getBytes());

private WireMockServer mockProxy;
private SdkHttpClient httpClient;

@BeforeEach
public void setup() {

Check warning on line 51 in http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientProxyAuthTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZq8N5ohPx18q-Pw2UdL&open=AZq8N5ohPx18q-Pw2UdL&pullRequest=6584
mockProxy = new WireMockServer(WireMockConfiguration.options().dynamicPort());
mockProxy.start();
}

@AfterEach
public void teardown() {

Check warning on line 57 in http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientProxyAuthTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZq8N5ohPx18q-Pw2UdM&open=AZq8N5ohPx18q-Pw2UdM&pullRequest=6584
if (mockProxy != null) {
mockProxy.stop();
mockProxy = null;
}

if (httpClient != null) {
httpClient.close();
httpClient = null;
}
}

@Test
public void proxyAuthentication_whenPreemptiveAuthDisabled_shouldUseChallengeResponseAuth() throws Exception {

Check warning on line 70 in http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientProxyAuthTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZq8N5ohPx18q-Pw2UdN&open=AZq8N5ohPx18q-Pw2UdN&pullRequest=6584
// First request without auth header should get 407
mockProxy.stubFor(any(anyUrl())
.willReturn(aResponse()
.withStatus(407)
.withHeader("Proxy-Authenticate", "Basic realm=\"proxy\"")));

// Second request with auth header should succeed
mockProxy.stubFor(any(anyUrl())
.withHeader("Proxy-Authorization", matching(BASIC_PROXY_AUTH_HEADER))
.willReturn(aResponse()
.withStatus(200)
.withBody("Success")));

// Create HTTP client with preemptive proxy authentication disabled
httpClient = Apache5HttpClient.builder()
.proxyConfiguration(ProxyConfiguration.builder()
.endpoint(URI.create("http://localhost:" + mockProxy.port()))
.username("testuser")
.password("testpass")
.preemptiveBasicAuthenticationEnabled(false)
.build())
.build();

// Create a request
SdkHttpRequest request = SdkHttpRequest.builder()
.method(SdkHttpMethod.GET)
.uri(URI.create("http://example.com/test"))
.build();

HttpExecuteRequest executeRequest = HttpExecuteRequest.builder()
.request(request)
.build();

// Execute the request - should succeed after challenge-response
HttpExecuteResponse response = httpClient.prepareRequest(executeRequest).call();
assertThat(response.httpResponse().statusCode()).isEqualTo(200);

// Verify challenge-response flow - 2 requests total
mockProxy.verify(2, anyRequestedFor(anyUrl()));
// First request without auth header
mockProxy.verify(1, anyRequestedFor(anyUrl()).withoutHeader("Proxy-Authorization"));
// Second request with auth header
mockProxy.verify(1, anyRequestedFor(anyUrl()).withHeader("Proxy-Authorization", matching(BASIC_PROXY_AUTH_HEADER)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.
*/

package software.amazon.awssdk.http.apache5.internal.utils;

import static org.assertj.core.api.Assertions.assertThat;

import java.net.URI;
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.NTCredentials;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.http.apache5.ProxyConfiguration;

public class Apache5UtilsTest {

Check warning on line 27 in http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/utils/Apache5UtilsTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZq8N5otPx18q-Pw2UdS&open=AZq8N5otPx18q-Pw2UdS&pullRequest=6584
private static final AuthScope AUTH_SCOPE = new AuthScope("localhost", 8080);

@Test
public void proxyCredentials_ntlmDetailsNotPresent_usesUsernameAndPassword() {

Check warning on line 31 in http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/utils/Apache5UtilsTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZq8N5otPx18q-Pw2UdP&open=AZq8N5otPx18q-Pw2UdP&pullRequest=6584
ProxyConfiguration config =
ProxyConfiguration.builder().username("name").password("pass").endpoint(URI.create("localhost:8080")).build();

assertThat(Apache5Utils.newProxyCredentialsProvider(config).getCredentials(AUTH_SCOPE, null))
.isInstanceOf(UsernamePasswordCredentials.class);
}

@Test
public void proxyCredentials_ntlmWorkstationPresent_usesNtCredentials() {

Check warning on line 40 in http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/utils/Apache5UtilsTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZq8N5otPx18q-Pw2UdQ&open=AZq8N5otPx18q-Pw2UdQ&pullRequest=6584
ProxyConfiguration config = ProxyConfiguration.builder()
.username("name")
.password("pass")
.ntlmWorkstation("workstation")
.endpoint(URI.create("localhost:8080")).build();

assertThat(Apache5Utils.newProxyCredentialsProvider(config).getCredentials(AUTH_SCOPE, null))
.isInstanceOf(NTCredentials.class);

Check warning on line 48 in http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/utils/Apache5UtilsTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this use of "NTCredentials"; it is deprecated.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZq8N5otPx18q-Pw2UdT&open=AZq8N5otPx18q-Pw2UdT&pullRequest=6584
}

@Test
public void proxyCredentials_ntlmDomainPresent_usesNtCredentials() {

Check warning on line 52 in http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/utils/Apache5UtilsTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZq8N5otPx18q-Pw2UdR&open=AZq8N5otPx18q-Pw2UdR&pullRequest=6584
ProxyConfiguration config = ProxyConfiguration.builder()
.username("name")
.password("pass")
.ntlmDomain("domain")
.endpoint(URI.create("localhost:8080")).build();

assertThat(Apache5Utils.newProxyCredentialsProvider(config).getCredentials(AUTH_SCOPE, null))
.isInstanceOf(NTCredentials.class);

Check warning on line 60 in http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/utils/Apache5UtilsTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this use of "NTCredentials"; it is deprecated.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZq8N5otPx18q-Pw2UdU&open=AZq8N5otPx18q-Pw2UdU&pullRequest=6584
}
}
Loading