From fe3d9d799560553a9fce55f6741531ae0b30578e Mon Sep 17 00:00:00 2001 From: OTT SATHNGAM Date: Mon, 16 Sep 2024 07:52:58 -0500 Subject: [PATCH] Fixed CheckFunction to allow check report URL endpoint alive (#15850) * Fixed CheckFunction to allow check report URL endpoint alive * Cleanup * Moved common code to getAccessToken function * Moved common code to getAccessToken function --- .environment/gitleaks/gitleaks-config.toml | 1 + .../src/main/kotlin/azure/CheckFunction.kt | 75 +++++++++++++++---- .../main/kotlin/transport/RESTTransport.kt | 65 +++++++++++----- 3 files changed, 106 insertions(+), 35 deletions(-) diff --git a/.environment/gitleaks/gitleaks-config.toml b/.environment/gitleaks/gitleaks-config.toml index da0f618980d..883ce714aff 100644 --- a/.environment/gitleaks/gitleaks-config.toml +++ b/.environment/gitleaks/gitleaks-config.toml @@ -203,6 +203,7 @@ title = "PRIME ReportStream Gitleaks Configuration" 'authority\", \"extension\"', # FHIR extension URL also shows up in normal FHIR test data 'ApiKeyCredential\(\"flexion\"', 'authType: \"two-legged\"', + 'authType == "two-legged"', '\"apiKey\"', 'api-key\" to \"oracle123\"', 'Authorization-Type: \"username/password\"', diff --git a/prime-router/src/main/kotlin/azure/CheckFunction.kt b/prime-router/src/main/kotlin/azure/CheckFunction.kt index 740478ef8f4..9b11303c524 100644 --- a/prime-router/src/main/kotlin/azure/CheckFunction.kt +++ b/prime-router/src/main/kotlin/azure/CheckFunction.kt @@ -19,7 +19,13 @@ import gov.cdc.prime.router.common.JacksonMapperUtilities import gov.cdc.prime.router.tokens.AuthenticatedClaims import gov.cdc.prime.router.tokens.authenticationFailure import gov.cdc.prime.router.transport.RESTTransport +import gov.cdc.prime.router.transport.RESTTransport.Companion.buildHeaders import gov.cdc.prime.router.transport.SftpTransport +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.client.statement.HttpResponse +import io.ktor.http.HttpStatusCode import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import net.schmizz.sshj.sftp.RemoteResourceFilter @@ -342,6 +348,8 @@ class CheckFunction : Logging { ): Boolean { logger.info("REST Transport $restTransportType") responseBody.add("${receiver.fullName}: REST Transport") + var msg = "${receiver.fullName}: Success: received connection is alive" + var retVal = true try { val theRESTTransport = RESTTransport() val reportId = UUID.randomUUID().toString() @@ -354,28 +362,65 @@ class CheckFunction : Logging { runBlocking { launch { val httpHeaders = theRESTTransport.getHeaders(restTransportType, reportId) - val accessToken: String = - theRESTTransport.getOAuthToken( - restTransportType, - jksCredential, - credential, - aLogger + + val accessToken = theRESTTransport.getAccessToken( + restTransportType, + jksCredential, + credential, + httpHeaders, + aLogger + ) + + // Try to GET something from the endpoint + val response = getFromUrl( + restTransportType.reportUrl, + httpHeaders, + RESTTransport.createDefaultHttpClient( + jksCredential, accessToken, + restTransportType ) + ) - val msg = when { - accessToken.isNotEmpty() -> "${receiver.fullName}: Success: received OAuth token" - httpHeaders.isNotEmpty() -> "${receiver.fullName}: Success: received Authentication header" - else -> error("${receiver.fullName}: Failure: no valid response from RESTTransport") + if (response.status == HttpStatusCode.InternalServerError) { + msg = "${receiver.fullName}: Failure: 500 Internal Error Occured" + retVal = false } - logger.info(msg) - responseBody.add(msg) } } } catch (t: Throwable) { - trackException(t, responseBody, receiver) - return false + if (t.message!!.contains("connect_timeout") || + t.message!!.contains("Unable to find credentials") + ) { + // Fail if there is timeout or Unable to find credential from Vault + trackException(t, responseBody, receiver) + return false + } + } + + responseBody.add(msg) + return retVal + } + + /** + * getFromURL extracts something from provided URL. + * + * @param url - Url to extract + * @param headers - headers + * @param httpClient - given http client engine + * + */ + suspend fun getFromUrl( + url: String, + headers: Map, + httpClient: HttpClient, + ): HttpResponse { + httpClient.use { client -> + return client.get(url) { + buildHeaders( + headers.map { (key, value) -> Pair(key, value) }.toMap() + ) + } } - return true } /** diff --git a/prime-router/src/main/kotlin/transport/RESTTransport.kt b/prime-router/src/main/kotlin/transport/RESTTransport.kt index 7f7f0c8e448..6b0758c9ccb 100644 --- a/prime-router/src/main/kotlin/transport/RESTTransport.kt +++ b/prime-router/src/main/kotlin/transport/RESTTransport.kt @@ -109,26 +109,14 @@ class RESTTransport(private val httpClient: HttpClient? = null) : ITransport { launch { try { val httpHeaders = getHeaders(restTransportInfo, reportId) - var accessToken: String? = null - - if (restTransportInfo.authType == "apiKey") { - val apiKeyCredential = credential as UserApiKeyCredential - httpHeaders["shared-api-key"] = apiKeyCredential.apiKey - httpHeaders["System_ID"] = apiKeyCredential.user - httpHeaders["Key"] = apiKeyCredential.apiKey - accessToken = apiKeyCredential.apiKey - } - if (restTransportInfo.authType == "two-legged" || restTransportInfo.authType == null) { - // parse headers for any dynamic values, OK needs the report ID - accessToken = getOAuthToken( - restTransportInfo, - jksCredential, - credential, - logger - ) - logger.info("Token successfully added!") - } + val accessToken = getAccessToken( + restTransportInfo, + jksCredential, + credential, + httpHeaders, + logger + ) // If encryption is needed. if (restTransportInfo.encryptionKeyUrl.isNotEmpty()) { @@ -322,6 +310,43 @@ class RESTTransport(private val httpClient: HttpClient? = null) : ITransport { }.toMutableMap() } + /** + * Get the Accesstoken based on authType given in Restransport header + * + * @param restTransportInfo - Transport setting + * @param jksCredential The jks credential + */ + suspend fun getAccessToken( + restTransportInfo: RESTTransportType, + jksCredential: UserJksCredential?, + credential: RestCredential, + httpHeaders: MutableMap, + logger: Logger, + ): String? { + var accessToken: String? = null + + if (restTransportInfo.authType == "apiKey") { + val apiKeyCredential = credential as UserApiKeyCredential + httpHeaders["shared-api-key"] = apiKeyCredential.apiKey + httpHeaders["System_ID"] = apiKeyCredential.user + httpHeaders["Key"] = apiKeyCredential.apiKey + accessToken = apiKeyCredential.apiKey + } + + if (restTransportInfo.authType == "two-legged" || restTransportInfo.authType == null) { + // parse headers for any dynamic values, OK needs the report ID + accessToken = getOAuthToken( + restTransportInfo, + jksCredential, + credential, + logger + ) + logger.info("Token successfully added!") + } + + return accessToken + } + /** * Get the OAuth token based on credential type * @@ -621,7 +646,7 @@ class RESTTransport(private val httpClient: HttpClient? = null) : ITransport { } /** Our default Http Client, with an optional SSL context, and optional auth token */ - private fun createDefaultHttpClient( + fun createDefaultHttpClient( jks: UserJksCredential?, accessToken: String?, restTransportInfo: RESTTransportType?,