Skip to content

Commit

Permalink
Replace RedirectExec with modified chain handler.
Browse files Browse the repository at this point in the history
Fixes #5989
  • Loading branch information
corneil committed Oct 16, 2024
1 parent efd1708 commit 2e5b184
Show file tree
Hide file tree
Showing 3 changed files with 304 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@

import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.StandardCookieSpec;
import org.apache.hc.client5.http.impl.ChainElement;
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
Expand All @@ -44,12 +48,12 @@

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.dataflow.container.registry.authorization.DropAuthorizationHeaderRequestRedirectStrategy;
import org.springframework.cloud.dataflow.container.registry.authorization.SpecialRedirectExec;
import org.springframework.http.MediaType;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;


/**
* On demand creates a cacheable {@link RestTemplate} instances for the purpose of the Container Registry access.
* Created RestTemplates can be configured to use Http Proxy and/or bypassing the SSL verification.
Expand Down Expand Up @@ -197,23 +201,30 @@ private HttpClientBuilder httpClientBuilder(SSLContext sslContext) {
private RestTemplate initRestTemplate(HttpClientBuilder clientBuilder, boolean withHttpProxy, Map<String, String> extra) {

clientBuilder.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(StandardCookieSpec.RELAXED).build());

DefaultRoutePlanner routePlanner;
// Set the HTTP proxy if configured.
if (withHttpProxy) {
if (!properties.getHttpProxy().isEnabled()) {
throw new ContainerRegistryException("Registry Configuration uses a HttpProxy but non is configured!");
}
HttpHost proxy = new HttpHost(properties.getHttpProxy().getHost(), properties.getHttpProxy().getPort());
clientBuilder.setProxy(proxy);
routePlanner = new DefaultProxyRoutePlanner(proxy, DefaultSchemePortResolver.INSTANCE);
}
else {
routePlanner = new DefaultRoutePlanner(DefaultSchemePortResolver.INSTANCE);
}

HttpComponentsClientHttpRequestFactory customRequestFactory =
new HttpComponentsClientHttpRequestFactory(
clientBuilder
.setRedirectStrategy(new DropAuthorizationHeaderRequestRedirectStrategy(extra))
// Azure redirects may contain double slashes and on default those are normilised
.setDefaultRequestConfig(RequestConfig.custom().build())
.build());
DropAuthorizationHeaderRequestRedirectStrategy redirectStrategy = new DropAuthorizationHeaderRequestRedirectStrategy(
extra);
HttpComponentsClientHttpRequestFactory customRequestFactory = new HttpComponentsClientHttpRequestFactory(
clientBuilder.setRedirectStrategy(redirectStrategy)
.replaceExecInterceptor(ChainElement.REDIRECT.name(),
new SpecialRedirectExec(routePlanner, redirectStrategy))
// Azure redirects may contain double slashes and on default those are
// normilised
.setDefaultRequestConfig(RequestConfig.custom().build())
.build());

// DockerHub response's media-type is application/octet-stream although the content is in JSON.
// Similarly the Github CR response's media-type is always text/plain although the content is in JSON.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Map;

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpHead;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpException;
Expand Down Expand Up @@ -92,109 +90,82 @@ public URI getLocationURI(final HttpRequest request, final HttpResponse response
URI httpUriRequest = super.getLocationURI(request, response, context);
String query = httpUriRequest.getQuery();
String method = request.getMethod();

// Handle Amazon requests
if (StringUtils.hasText(query) && query.contains(AMZ_CREDENTIAL)) {
if (isHeadOrGetMethod(method)) {
try {
return new DropAuthorizationHeaderHttpRequestBase(httpUriRequest, method).getUri();
} catch (URISyntaxException e) {
throw new HttpException("Unable to get location URI", e);
}
}
removeAuthorizationHeader(request, response, false);
try {
if (isHeadMethod(method)) {
return new HttpHead(httpUriRequest).getUri();
}
else {
return new HttpGet(httpUriRequest).getUri();
}
}
catch (URISyntaxException e) {
throw new HttpException("Unable to get location URI", e);
}
}
}

// Handle Azure requests
try {
if (request.getUri().getRawPath().contains(AZURECR_URI_SUFFIX)) {
if (isHeadOrGetMethod(method)) {
return (new DropAuthorizationHeaderHttpRequestBase(httpUriRequest, method) {
// Drop headers only for the Basic Auth and leave unchanged for OAuth2
@Override
protected boolean isDropHeader(String name, Object value) {
return name.equalsIgnoreCase(AUTHORIZATION_HEADER) && StringUtils.hasText((String) value) && ((String)value).contains(BASIC_AUTH);
}
}).getUri();
}
}


// Handle Custom requests
if (extra.containsKey(CUSTOM_REGISTRY) && request.getUri().getRawPath().contains(extra.get(CUSTOM_REGISTRY))) {
if (isHeadOrGetMethod(method)) {
return new DropAuthorizationHeaderHttpRequestBase(httpUriRequest, method).getUri();
try {
if (request.getUri().getRawPath().contains(AZURECR_URI_SUFFIX)) {
if (isHeadOrGetMethod(method)) {
removeAuthorizationHeader(request, response, true);
if (isHeadMethod(method)) {
return new HttpHead(httpUriRequest).getUri();
}
else {
return new HttpGet(httpUriRequest).getUri();
}
}
}

// Handle Custom requests
if (extra.containsKey(CUSTOM_REGISTRY)
&& request.getUri().getRawPath().contains(extra.get(CUSTOM_REGISTRY))) {
if (isHeadOrGetMethod(method)) {
removeAuthorizationHeader(request, response, false);
if (isHeadMethod(method)) {
return new HttpHead(httpUriRequest).getUri();
}
else {
return new HttpGet(httpUriRequest).getUri();
}
}
}
}
} catch (URISyntaxException e) {
catch (URISyntaxException e) {
throw new HttpException("Unable to get Locaction URI", e);
}
return httpUriRequest;
}

private boolean isHeadOrGetMethod(String method) {
return StringUtils.hasText(method)
&& (method.equalsIgnoreCase(HttpHead.METHOD_NAME) || method.equalsIgnoreCase(HttpGet.METHOD_NAME));
}

/**
* Overrides all header setter methods to filter out the Authorization headers.
*/
static class DropAuthorizationHeaderHttpRequestBase extends HttpUriRequestBase {

private final String method;

DropAuthorizationHeaderHttpRequestBase(URI uri, String method) {
super(method, uri);
this.method = method;
}

@Override
public String getMethod() {
return this.method;
}

@Override
public void addHeader(Header header) {
if (!isDropHeader(header)) {
super.addHeader(header);
private static void removeAuthorizationHeader(HttpRequest request, HttpResponse response, boolean onlyBasicAuth) {
for (Header header : response.getHeaders()) {
if (header.getName().equalsIgnoreCase(AUTHORIZATION_HEADER)
&& (!onlyBasicAuth || (onlyBasicAuth && header.getValue().contains(BASIC_AUTH)))) {
response.removeHeaders(header.getName());
break;
}
}

@Override
public void addHeader(String name, Object value) {
if (!isDropHeader(name, value)) {
super.addHeader(name, value);
for (Header header : request.getHeaders()) {
if (header.getName().equalsIgnoreCase(AUTHORIZATION_HEADER)
&& (!onlyBasicAuth || (onlyBasicAuth && header.getValue().contains(BASIC_AUTH)))) {
request.removeHeaders(header.getName());
break;
}
}
}

@Override
public void setHeader(Header header) {
if (!isDropHeader(header)) {
super.setHeader(header);
}
}

@Override
public void setHeader(String name, Object value) {
if (!isDropHeader(name, value)) {
super.setHeader(name, value);
}
}

@Override
public void setHeaders(Header[] headers) {
Header[] filteredHeaders = Arrays.stream(headers)
.filter(header -> !isDropHeader(header))
.toArray(Header[]::new);
super.setHeaders(filteredHeaders);
}

protected boolean isDropHeader(Header header) {
return isDropHeader(header.getName(), header.getValue());
}
private boolean isHeadOrGetMethod(String method) {
return StringUtils.hasText(method)
&& (method.equalsIgnoreCase(HttpHead.METHOD_NAME) || method.equalsIgnoreCase(HttpGet.METHOD_NAME));
}

protected boolean isDropHeader(String name, Object value) {
return name.equalsIgnoreCase(AUTHORIZATION_HEADER);
}
private boolean isHeadMethod(String method) {
return StringUtils.hasText(method) && method.equalsIgnoreCase(HttpHead.METHOD_NAME);
}

}
Loading

0 comments on commit 2e5b184

Please sign in to comment.