From 3d75160700d885662bfce0f176c3fb1f6cf8be89 Mon Sep 17 00:00:00 2001 From: David Holiday Date: Fri, 16 Aug 2024 14:05:27 -0600 Subject: [PATCH 1/3] asdF --- prime-router/build.gradle.kts | 1 + .../src/main/kotlin/cli/tests/AuthTests.kt | 11 +- .../src/main/kotlin/common/HttpClientUtils.kt | 109 ++++++++---------- 3 files changed, 56 insertions(+), 65 deletions(-) diff --git a/prime-router/build.gradle.kts b/prime-router/build.gradle.kts index f470edb4efa..c20ab4b6ede 100644 --- a/prime-router/build.gradle.kts +++ b/prime-router/build.gradle.kts @@ -878,6 +878,7 @@ dependencies { implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("io.ktor:ktor-client-apache:$ktorVersion") + implementation("io.ktor:ktor-client-apache5:$ktorVersion") implementation("io.ktor:ktor-client-logging:$ktorVersion") implementation("io.ktor:ktor-client-encoding:$ktorVersion") implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") diff --git a/prime-router/src/main/kotlin/cli/tests/AuthTests.kt b/prime-router/src/main/kotlin/cli/tests/AuthTests.kt index d679e57fbfd..c7c35d2c5d5 100644 --- a/prime-router/src/main/kotlin/cli/tests/AuthTests.kt +++ b/prime-router/src/main/kotlin/cli/tests/AuthTests.kt @@ -1172,23 +1172,20 @@ class Server2ServerAuthTests : CoolTest() { ) val orgEndpoint = "${environment.url}/api/settings/organizations" - val client = HttpClientUtils.createDefaultHttpClient( - userToken - ) + val client = HttpClientUtils.getDefaultHttpClient() - val clientAdmin = HttpClientUtils.createDefaultHttpClient( - adminToken - ) + val clientAdmin = HttpClientUtils.getDefaultHttpClient() // Case: GET All Org Settings (Admin-only endpoint) // Unhappy Path: user on admin-only endpoint val response = runBlocking { - client.get(orgEndpoint) { + client.get(orgEndpoint, userToken) { timeout { requestTimeoutMillis = 45000 // default timeout is 15s; raising higher due to slow Function startup issues } accept(ContentType.Application.Json) + } } diff --git a/prime-router/src/main/kotlin/common/HttpClientUtils.kt b/prime-router/src/main/kotlin/common/HttpClientUtils.kt index 4e80e8f64f2..bf4976f17f3 100644 --- a/prime-router/src/main/kotlin/common/HttpClientUtils.kt +++ b/prime-router/src/main/kotlin/common/HttpClientUtils.kt @@ -2,14 +2,12 @@ package gov.cdc.prime.router.common import io.ktor.client.HttpClient import io.ktor.client.call.body -import io.ktor.client.engine.apache.Apache +import io.ktor.client.engine.apache5.Apache5 import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.client.plugins.defaultRequest import io.ktor.client.plugins.timeout import io.ktor.client.request.accept import io.ktor.client.request.forms.submitForm -import io.ktor.client.request.header import io.ktor.client.request.headers import io.ktor.client.request.parameter import io.ktor.client.request.request @@ -32,10 +30,30 @@ class HttpClientUtils { const val REQUEST_TIMEOUT_MILLIS: Long = 130000 // need to be public to be used by inline const val SETTINGS_REQUEST_TIMEOUT_MILLIS = 30000 + private val httpClient: HttpClient = + HttpClient(Apache5) { + install(ContentNegotiation) { + json( + Json { + prettyPrint = true + isLenient = true + ignoreUnknownKeys = true + } + ) + } + install(HttpTimeout) + engine { + followRedirects = true + socketTimeout = TIMEOUT + connectTimeout = TIMEOUT.toLong() + connectionRequestTimeout = TIMEOUT.toLong() + } + } + /** * GET (query resource) operation to the given endpoint resource [url] * @param url: required, the url to the resource endpoint - * @param tokens: null default, the access token needed to call the endpoint + * @param accessToken: null default, the access token needed to call the endpoint * @param headers: null default, the headers of the request * @param acceptedContent: default application/json the accepted content type * @param timeout: default to a system base value in millis @@ -73,7 +91,7 @@ class HttpClientUtils { /** * GET (query resource) operation to the given endpoint resource [url] * @param url: required, the url to the resource endpoint - * @param tokens: null default, the access token needed to call the endpoint + * @param accessToken: null default, the access token needed to call the endpoint * @param headers: null default, the headers of the request * @param acceptedContent: default application/json the accepted content type * @param timeout: default to a system base value in millis @@ -106,7 +124,7 @@ class HttpClientUtils { /** * PUT (modify resource) operation to the given endpoint resource [url] * @param url: required, the url to the resource endpoint - * @param tokens: null default, the access token needed to call the endpoint + * @param accessToken: null default, the access token needed to call the endpoint * @param headers: null default, the headers of the request * @param acceptedContent: default application/json the accepted content type * @param timeout: default to a system base value in millis @@ -147,7 +165,7 @@ class HttpClientUtils { /** * PUT (modify resource) operation to the given endpoint resource [url] * @param url: required, the url to the resource endpoint - * @param tokens: null default, the access token needed to call the endpoint + * @param accessToken: null default, the access token needed to call the endpoint * @param headers: null default, the headers of the request * @param acceptedContent: default application/json the accepted content type * @param timeout: default to a system base value in millis @@ -183,7 +201,7 @@ class HttpClientUtils { /** * POST (create resource) operation to the given endpoint resource [url] * @param url: required, the url to the resource endpoint - * @param tokens: null default, the access token needed to call the endpoint + * @param accessToken: null default, the access token needed to call the endpoint * @param headers: null default, the headers of the request * @param acceptedContent: default application/json the accepted content type * @param timeout: default to a system base value in millis @@ -223,7 +241,7 @@ class HttpClientUtils { /** * POST (create resource) operation to the given endpoint resource [url] * @param url: required, the url to the resource endpoint - * @param tokens: null default, the access token needed to call the endpoint + * @param accessToken: null default, the access token needed to call the endpoint * @param headers: null default, the headers of the request * @param acceptedContent: default application/json the accepted content type * @param timeout: default to a system base value in millis @@ -260,7 +278,7 @@ class HttpClientUtils { * Submit form to the endpoint as indicated by [url] * * @param url: required, the url to the resource endpoint - * @param tokens: null default, the access token needed to call the endpoint + * @param accessToken: null default, the access token needed to call the endpoint * @param headers: null default, the headers of the request * @param acceptedContent: default application/json the accepted content type * @param timeout: default to a system base value in millis @@ -294,7 +312,7 @@ class HttpClientUtils { * Submit form to the endpoint as indicated by [url] * * @param url: required, the url to the resource endpoint - * @param tokens: null default, the access token needed to call the endpoint + * @param accessToken: null default, the access token needed to call the endpoint * @param headers: null default, the headers of the request * @param acceptedContent: default application/json the accepted content type * @param timeout: default to a system base value in millis @@ -312,7 +330,7 @@ class HttpClientUtils { httpClient: HttpClient? = null, ): HttpResponse { return runBlocking { - (httpClient ?: createDefaultHttpClient(accessToken)).submitForm( + (httpClient ?: getDefaultHttpClient()).submitForm( url, formParameters = Parameters.build { formParams?.forEach { param -> @@ -331,7 +349,11 @@ class HttpClientUtils { } } } - + accessToken?.let { + headers { + append("Authorization", "Bearer $accessToken") + } + } accept(acceptedContent) } } @@ -340,7 +362,7 @@ class HttpClientUtils { /** * HEAD operation to the given endpoint resource [url] * @param url: required, the url to the resource endpoint - * @param tokens: null default, the access token needed to call the endpoint + * @param accessToken: null default, the access token needed to call the endpoint * @param headers: null default, the headers of the request * @param acceptedContent: default application/json the accepted content type * @param timeout: default to a system base value in millis @@ -376,9 +398,9 @@ class HttpClientUtils { /** * HEAD operation to the given endpoint resource [url] * @param url: required, the url to the resource endpoint - * @param tokens: null default, the access token needed to call the endpoint + * @param accessToken: null default, the access token needed to call the endpoint * @param headers: null default, the headers of the request - * @param acceptContent: default application/json the accepted content type + * @param acceptedContent: default application/json the accepted content type * @param timeout: default to a system base value in millis * @param queryParameters: null default, query parameters of the request * @param httpClient: null default, a http client injected by caller @@ -411,7 +433,7 @@ class HttpClientUtils { * A thin wrapper on top of the underlying 3rd party http client, e.g. ktor http client * with: * @param url: required, the url to the resource endpoint - * @param tokens: null default, the access token needed to call the endpoint + * @param accessToken: null default, the access token needed to call the endpoint * @param headers: null default, the headers of the request * @param acceptedContent: default application/json the accepted content type * @param timeout: default to a system base value in millis @@ -450,7 +472,7 @@ class HttpClientUtils { * A thin wrapper on top of the underlying 3rd party http client, e.g. ktor http client * with: * @param url: required, the url to the resource endpoint - * @param tokens: null default, the access token needed to call the endpoint + * @param accessToken: null default, the access token needed to call the endpoint * @param headers: null default, the headers of the request * @param acceptedContent: default application/json the accepted content type * @param timeout: default to a system base value in millis @@ -496,17 +518,16 @@ class HttpClientUtils { httpClient: HttpClient? = null, ): HttpResponse { return runBlocking { - (httpClient ?: createDefaultHttpClient(accessToken)).request(url) { + (httpClient ?: getDefaultHttpClient()).request(url) { this.method = method timeout { requestTimeoutMillis = timeout } url { queryParameters?.forEach { - parameter(it.key, it.value.toString()) + parameter(it.key, it.value) } } - headers?.let { headers { headers.forEach { @@ -514,6 +535,11 @@ class HttpClientUtils { } } } + accessToken?.let { + headers { + append("Authorization", "Bearer $accessToken") + } + } acceptedContent?.let { accept(acceptedContent) contentType(acceptedContent) @@ -526,48 +552,15 @@ class HttpClientUtils { } /** - * Create a http client with sensible default settings + * Get a http client with sensible default settings * note: most configuration parameters are overridable * e.g. expectSuccess default to false because most of the time * the caller wants to handle the whole range of response status - * @param bearerTokens null default, the access token needed to call the endpoint + * * @return a HttpClient with all sensible defaults */ - fun createDefaultHttpClient(accessToken: String?): HttpClient { - return HttpClient(Apache) { - // installs logging into the call to post to the server - // commented out - not to override underlying default logger settings - // enable to trace http client internals when needed - // install(Logging) { - // logger = Logger.SIMPLE - // level = LogLevel.INFO - // } - // not using Bearer Auth handler due to refresh token behavior - accessToken?.let { - defaultRequest { - header("Authorization", "Bearer $it") - } - } - install(ContentNegotiation) { - json( - Json { - prettyPrint = true - isLenient = true - ignoreUnknownKeys = true - } - ) - } - - install(HttpTimeout) - engine { - followRedirects = true - socketTimeout = TIMEOUT - connectTimeout = TIMEOUT - connectionRequestTimeout = TIMEOUT - customizeClient { - } - } - } + fun getDefaultHttpClient(): HttpClient { + return httpClient } } } \ No newline at end of file From 2ec2481b260de1cfdc522325b958605efa4dac9e Mon Sep 17 00:00:00 2001 From: David Holiday Date: Fri, 16 Aug 2024 14:19:17 -0600 Subject: [PATCH 2/3] ports authtest changes --- .../src/main/kotlin/cli/tests/AuthTests.kt | 92 +++++++------------ 1 file changed, 35 insertions(+), 57 deletions(-) diff --git a/prime-router/src/main/kotlin/cli/tests/AuthTests.kt b/prime-router/src/main/kotlin/cli/tests/AuthTests.kt index c7c35d2c5d5..8a74452beee 100644 --- a/prime-router/src/main/kotlin/cli/tests/AuthTests.kt +++ b/prime-router/src/main/kotlin/cli/tests/AuthTests.kt @@ -26,11 +26,9 @@ import gov.cdc.prime.router.tokens.AuthUtils import gov.cdc.prime.router.tokens.DatabaseJtiCache import gov.cdc.prime.router.tokens.Scope import io.ktor.client.plugins.timeout -import io.ktor.client.request.accept import io.ktor.client.request.get import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode -import kotlinx.coroutines.runBlocking import java.io.File import java.io.IOException import java.net.URLEncoder @@ -1172,104 +1170,84 @@ class Server2ServerAuthTests : CoolTest() { ) val orgEndpoint = "${environment.url}/api/settings/organizations" - val client = HttpClientUtils.getDefaultHttpClient() - - val clientAdmin = HttpClientUtils.getDefaultHttpClient() - // Case: GET All Org Settings (Admin-only endpoint) // Unhappy Path: user on admin-only endpoint - val response = runBlocking { - client.get(orgEndpoint, userToken) { - timeout { - requestTimeoutMillis = 45000 - // default timeout is 15s; raising higher due to slow Function startup issues - } - accept(ContentType.Application.Json) - - } - } + val response = HttpClientUtils.get( + url = orgEndpoint, + accessToken = userToken, + timeout = 45000, // default timeout is 15s; raising higher due to slow Function startup issues + acceptedContent = ContentType.Application.Json + ) if (response.status != HttpStatusCode.Unauthorized) { bad( "***$name Test settings/organizations Unhappy Path (user-GET All Orgs) FAILED:" + - " Expected HttpStatus ${HttpStatusCode.Unauthorized}. Got ${response.status.value}" + " Expected HttpStatus ${HttpStatusCode.Unauthorized}. Got ${response.status.value}" ) return false } // Happy Path: admin on admin-only endpoint - val response2 = runBlocking { - clientAdmin.get(orgEndpoint) { - timeout { - requestTimeoutMillis = 45000 - // default timeout is 15s; raising higher due to slow Function startup issues - } - accept(ContentType.Application.Json) - } - } + val response2 = HttpClientUtils.get( + url = orgEndpoint, + accessToken = adminToken, + timeout = 45000, // default timeout is 15s; raising higher due to slow Function startup issues + acceptedContent = ContentType.Application.Json + ) if (response2.status != HttpStatusCode.OK) { bad( "***$name Test settings/organizations Happy Path (admin-GET All Orgs) FAILED:" + - " Expected HttpStatus ${HttpStatusCode.OK}. Got ${response2.status.value}" + " Expected HttpStatus ${HttpStatusCode.OK}. Got ${response2.status.value}" ) return false } // Case: GET Receivers for an Org (Endpoint allowed for admins and members of the org) // Happy Path: user on user-allowed endpoint - val response3 = runBlocking { - client.get("$orgEndpoint/${authorizedOrg.name}/receivers") { - timeout { - requestTimeoutMillis = 45000 - // default timeout is 15s; raising higher due to slow Function startup issues - } - accept(ContentType.Application.Json) - } - } + val response3 = HttpClientUtils.get( + url = "$orgEndpoint/${authorizedOrg.name}/receivers", + accessToken = userToken, + timeout = 45000, // default timeout is 15s; raising higher due to slow Function startup issues + acceptedContent = ContentType.Application.Json + ) if (response3.status != HttpStatusCode.OK) { bad( "***$name Test settings/organizations Happy Path (user-GET Org Receivers) FAILED:" + - " Expected HttpStatus ${HttpStatusCode.OK}. Got ${response3.status.value}" + " Expected HttpStatus ${HttpStatusCode.OK}. Got ${response3.status.value}" ) return false } // Happy Path: admin on user-allowed endpoint - val response4 = runBlocking { - clientAdmin.get("$orgEndpoint/${authorizedOrg.name}/receivers") { - timeout { - requestTimeoutMillis = 45000 - // default timeout is 15s; raising higher due to slow Function startup issues - } - accept(ContentType.Application.Json) - } - } + val response4 = HttpClientUtils.get( + url = "$orgEndpoint/${authorizedOrg.name}/receivers", + accessToken = adminToken, + timeout = 45000, // default timeout is 15s; raising higher due to slow Function startup issues + acceptedContent = ContentType.Application.Json + ) if (response4.status != HttpStatusCode.OK) { bad( "***$name Test settings/organizations Happy Path (admin-GET Org Receivers) FAILED:" + - " Expected HttpStatus ${HttpStatusCode.OK}. Got ${response4.status.value}" + " Expected HttpStatus ${HttpStatusCode.OK}. Got ${response4.status.value}" ) return false } // UnhappyPath: user on an unauthorized org name - val response5 = runBlocking { - client.get("$orgEndpoint/${unauthorizedOrg.name}/receivers") { - timeout { - requestTimeoutMillis = 45000 - // default timeout is 15s; raising higher due to slow Function startup issues - } - accept(ContentType.Application.Json) - } - } + val response5 = HttpClientUtils.get( + url = "$orgEndpoint/${unauthorizedOrg.name}/receivers", + accessToken = userToken, + timeout = 45000, // default timeout is 15s; raising higher due to slow Function startup issues + acceptedContent = ContentType.Application.Json + ) if (response5.status != HttpStatusCode.Unauthorized) { bad( "***$name Test settings/organizations Unhappy Path (user-GET Unauthorized Org Receivers) FAILED:" + - " Expected HttpStatus ${HttpStatusCode.Unauthorized}. Got ${response5.status.value}" + " Expected HttpStatus ${HttpStatusCode.Unauthorized}. Got ${response5.status.value}" ) return false } From a8ea2aa9ecea41a98f7da05fffa277d83e1d5cd5 Mon Sep 17 00:00:00 2001 From: David Holiday Date: Tue, 20 Aug 2024 17:04:00 -0600 Subject: [PATCH 3/3] removes Apache5 --- prime-router/build.gradle.kts | 1 - prime-router/src/main/kotlin/common/HttpClientUtils.kt | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/prime-router/build.gradle.kts b/prime-router/build.gradle.kts index c20ab4b6ede..f470edb4efa 100644 --- a/prime-router/build.gradle.kts +++ b/prime-router/build.gradle.kts @@ -878,7 +878,6 @@ dependencies { implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("io.ktor:ktor-client-apache:$ktorVersion") - implementation("io.ktor:ktor-client-apache5:$ktorVersion") implementation("io.ktor:ktor-client-logging:$ktorVersion") implementation("io.ktor:ktor-client-encoding:$ktorVersion") implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") diff --git a/prime-router/src/main/kotlin/common/HttpClientUtils.kt b/prime-router/src/main/kotlin/common/HttpClientUtils.kt index bf4976f17f3..b406ef0bcb3 100644 --- a/prime-router/src/main/kotlin/common/HttpClientUtils.kt +++ b/prime-router/src/main/kotlin/common/HttpClientUtils.kt @@ -2,7 +2,7 @@ package gov.cdc.prime.router.common import io.ktor.client.HttpClient import io.ktor.client.call.body -import io.ktor.client.engine.apache5.Apache5 +import io.ktor.client.engine.apache.Apache import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.timeout @@ -31,7 +31,7 @@ class HttpClientUtils { const val SETTINGS_REQUEST_TIMEOUT_MILLIS = 30000 private val httpClient: HttpClient = - HttpClient(Apache5) { + HttpClient(Apache) { install(ContentNegotiation) { json( Json { @@ -45,8 +45,8 @@ class HttpClientUtils { engine { followRedirects = true socketTimeout = TIMEOUT - connectTimeout = TIMEOUT.toLong() - connectionRequestTimeout = TIMEOUT.toLong() + connectTimeout = TIMEOUT + connectionRequestTimeout = TIMEOUT } }