Skip to content

Commit

Permalink
[ALS-6880] HttpClient with proxy
Browse files Browse the repository at this point in the history
- Add proxy config to http client
- Refactor rest template to use client
- Fix SLF4J duplicate impls
  • Loading branch information
Luke Sikina committed Oct 4, 2024
1 parent 0061638 commit a563728
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 33 deletions.
12 changes: 12 additions & 0 deletions pic-sure-auth-services/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@
<xml.bind.version>2.3.0</xml.bind.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
<!-- Web version has been explicitly set to fix security finding in spring-boot-starter-web -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;

/**
Expand Down Expand Up @@ -156,7 +154,7 @@ public JsonNode retrieveUserInfo(String accessToken) throws IOException {
ResponseEntity<String> response = this.restClientUtil.retrieveGetResponseWithRequestConfiguration(
auth0UserInfoURI,
headers,
RestClientUtil.createRequestConfigWithCustomTimeout(2000)
2000
);

auth0Response = objectMapper.readTree(response.getBody());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ private JsonNode getFENCEUserProfile(String access_token) {
ResponseEntity<String> fence_user_profile_response = this.restClientUtil.retrieveGetResponseWithRequestConfiguration(
this.idp_provider_uri + "/user/user",
headers,
RestClientUtil.createRequestConfigWithCustomTimeout(10000)
10000
);

// Map the response to a JsonNode object
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package edu.harvard.hms.dbmi.avillach.auth.utils;


import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpHost;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestClientConfig {

private static final Logger LOG = LoggerFactory.getLogger(RestClientConfig.class);

@Value("${http.proxyHost:}")
private String proxyHost;

@Value("${http.proxyPort:}")
private int proxyPort;

@Value("${http.proxyUser:}")
private String proxyUser;

@Value("${http.proxyPassword:}")
private String proxyPassword;

@Bean
public HttpClient getHttpClient() {
if (!StringUtils.hasLength(proxyHost)) {
return HttpClients.createDefault();
} else if (!StringUtils.hasLength(proxyUser)) {
LOG.info("Utilizing unauthenticated proxy: host={}", proxyHost);
return HttpClients.custom()
.setProxy(new HttpHost(proxyHost, proxyPort))
.build();
} else {
LOG.info("Utilizing authenticated proxy: host={}, user={}", proxyHost, proxyUser);

BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
new AuthScope(proxyHost, proxyPort),
new UsernamePasswordCredentials(proxyUser, proxyPassword.toCharArray()));

return HttpClients.custom()
.setDefaultCredentialsProvider(credentialsProvider)
.setProxy(new HttpHost(proxyHost, proxyPort))
.build();
}
}

@Bean
public RestTemplate getRestTemplate(@Autowired HttpClient client) {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(client);
return new RestTemplate(factory);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

@Component
public class RestClientUtil {

private static final Logger logger = LoggerFactory.getLogger(RestClientUtil.class);
private final RestTemplate restTemplate = new RestTemplate();
private final RestTemplate restTemplate;

@Autowired
public RestClientUtil(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

public ResponseEntity<String> retrieveGetResponse(String uri, HttpHeaders headers) {
try {
Expand All @@ -29,16 +33,14 @@ public ResponseEntity<String> retrieveGetResponse(String uri, HttpHeaders header
}

// Implement: The ability to set the timeout on the rest template for a given request.
public ResponseEntity<String> retrieveGetResponseWithRequestConfiguration(String uri, HttpHeaders headers, ClientHttpRequestFactory requestFactory) {
if (requestFactory == null) {
return retrieveGetResponse(uri, headers);
}
RestTemplate localRestTemplate = new RestTemplate(requestFactory);

public ResponseEntity<String> retrieveGetResponseWithRequestConfiguration(String uri, HttpHeaders headers, int timeoutMs) {
try {
HttpEntity<String> entity = new HttpEntity<>("parameters", headers);
// Set timeout settings
((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory()).setConnectTimeout(timeoutMs);
((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory()).setConnectionRequestTimeout(timeoutMs);
// Pass custom configuration to the RestTemplate
return localRestTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
return restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
} catch (HttpClientErrorException ex) {
logger.error("HttpClientErrorException: {}", ex.getMessage());
throw ex;
Expand All @@ -55,12 +57,4 @@ public ResponseEntity<String> retrievePostResponse(String uri, HttpHeaders heade
public ResponseEntity<String> retrievePostResponse(String uri, HttpEntity<MultiValueMap<String, String>> requestEntity) throws HttpClientErrorException {
return restTemplate.postForEntity(uri, requestEntity, String.class);
}

public static ClientHttpRequestFactory createRequestConfigWithCustomTimeout(int timeoutMs) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(timeoutMs);
requestFactory.setReadTimeout(timeoutMs);
return requestFactory;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;

import java.io.IOException;
import java.util.HashMap;
Expand All @@ -25,6 +24,7 @@

import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

Expand Down Expand Up @@ -70,15 +70,15 @@ public void testGetToken_MissingParameters() throws IOException {
// Tests the failure in retrieving user information, expecting an IOException to be converted into a NotAuthorizedException
@Test(expected = NotAuthorizedException.class)
public void testGetToken_UserInfoRetrievalFails() throws IOException {
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), any(ClientHttpRequestFactory.class)))
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), anyInt()))
.thenThrow(new NotAuthorizedException("Failed to retrieve user info"));
authenticationService.authenticate(authRequest, "localhost");
}

// Tests the scenario where the user ID is not found in the user info retrieved
@Test(expected = NotAuthorizedException.class)
public void testGetToken_NoUserIdInUserInfo() throws IOException {
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), any()))
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), anyInt()))
.thenReturn(new ResponseEntity<>("{}", HttpStatus.OK));
authenticationService.authenticate(authRequest, "localhost");
}
Expand All @@ -98,7 +98,7 @@ public void testGetToken_Successful() throws Exception {
// Additional test to handle retries in user info retrieval
@Test
public void testRetrieveUserInfo_WithRetries() throws Exception {
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), any()))
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), anyInt()))
.thenThrow(new RuntimeException("Network error"))
.thenReturn(new ResponseEntity<>("{}", HttpStatus.OK));
// Assuming retrieveUserInfo is accessible, or using reflection if it is private
Expand Down Expand Up @@ -142,7 +142,7 @@ private void setupSuccessfulTokenRetrievalScenario() throws IOException {
+ "]"
+ "}";

when(restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), any(ClientHttpRequestFactory.class)))
when(restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), anyInt()))
.thenReturn(new ResponseEntity<>(validJson, HttpStatus.OK));

// Create a test user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;

import java.io.IOException;
import java.util.HashMap;
Expand All @@ -27,6 +26,7 @@

import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

Expand Down Expand Up @@ -72,15 +72,15 @@ public void testGetToken_MissingParameters() throws IOException {
// Tests the failure in retrieving user information, expecting an IOException to be converted into a NotAuthorizedException
@Test(expected = NotAuthorizedException.class)
public void testGetToken_UserInfoRetrievalFails() throws IOException {
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), any(ClientHttpRequestFactory.class)))
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), anyInt()))
.thenThrow(new NotAuthorizedException("Failed to retrieve user info"));
authenticationService.authenticate(authRequest, "localhost");
}

// Tests the scenario where the user ID is not found in the user info retrieved
@Test(expected = NotAuthorizedException.class)
public void testGetToken_NoUserIdInUserInfo() throws IOException {
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), any()))
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), anyInt()))
.thenReturn(new ResponseEntity<>("{}", HttpStatus.OK));
authenticationService.authenticate(authRequest, "localhost");
}
Expand All @@ -100,7 +100,7 @@ public void testGetToken_Successful() throws Exception {
// Additional test to handle retries in user info retrieval
@Test
public void testRetrieveUserInfo_WithRetries() throws Exception {
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), any()))
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), anyInt()))
.thenThrow(new RuntimeException("Network error"))
.thenReturn(new ResponseEntity<>("{}", HttpStatus.OK));
// Assuming retrieveUserInfo is accessible, or using reflection if it is private
Expand Down Expand Up @@ -144,7 +144,7 @@ private void setupSuccessfulTokenRetrievalScenario() throws IOException {
+ "]"
+ "}";

when(restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), any(ClientHttpRequestFactory.class)))
when(restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), anyInt()))
.thenReturn(new ResponseEntity<>(validJson, HttpStatus.OK));

// Create a test user
Expand Down

0 comments on commit a563728

Please sign in to comment.