diff --git a/lib/http-client-utils/build.gradle.kts b/lib/http-client-utils/build.gradle.kts new file mode 100644 index 00000000..bc089622 --- /dev/null +++ b/lib/http-client-utils/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + kotlin("jvm") +} + +val jvmMajorVersion: String by project +val jvmVersion = JavaVersion.valueOf("VERSION_$jvmMajorVersion") + +dependencies { + implementation(project(":lib:hoplite-config")) + implementation(libs.ktor.serialization.jackson) + implementation(libs.jackson.datatypeJsr310) + implementation(libs.jackson.kotlin) + implementation(libs.ktor.client.contentNegotiation) + implementation(libs.ktor.client.cio) + api(libs.nav.common.tokenClient) +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(jvmVersion.majorVersion) + } +} diff --git a/lib/http-client-utils/src/main/kotlin/no/nav/paw/client/config/AzureAdM2MConfig.kt b/lib/http-client-utils/src/main/kotlin/no/nav/paw/client/config/AzureAdM2MConfig.kt new file mode 100644 index 00000000..5e39ba9d --- /dev/null +++ b/lib/http-client-utils/src/main/kotlin/no/nav/paw/client/config/AzureAdM2MConfig.kt @@ -0,0 +1,8 @@ +package no.nav.paw.client.config + +const val AZURE_M2M_CONFIG = "azure_m2m_config.toml" + +data class AzureAdM2MConfig( + val tokenEndpointUrl: String, + val clientId: String +) \ No newline at end of file diff --git a/lib/http-client-utils/src/main/kotlin/no/nav/paw/client/factory/AzureAdM2MTokenClientFactory.kt b/lib/http-client-utils/src/main/kotlin/no/nav/paw/client/factory/AzureAdM2MTokenClientFactory.kt new file mode 100644 index 00000000..bb30a064 --- /dev/null +++ b/lib/http-client-utils/src/main/kotlin/no/nav/paw/client/factory/AzureAdM2MTokenClientFactory.kt @@ -0,0 +1,44 @@ +package no.nav.paw.client.factory + +import com.nimbusds.jose.jwk.KeyUse +import com.nimbusds.jose.jwk.RSAKey +import no.nav.common.token_client.builder.AzureAdTokenClientBuilder +import no.nav.common.token_client.cache.CaffeineTokenCache +import no.nav.common.token_client.client.AzureAdMachineToMachineTokenClient +import no.nav.paw.client.config.AzureAdM2MConfig +import no.nav.paw.config.env.Local +import no.nav.paw.config.env.RuntimeEnvironment +import no.nav.paw.config.env.currentRuntimeEnvironment +import java.security.KeyPairGenerator +import java.security.interfaces.RSAPrivateKey +import java.security.interfaces.RSAPublicKey + +fun createAzureAdM2MTokenClient( + runtimeEnvironment: RuntimeEnvironment = currentRuntimeEnvironment, + azureProviderConfig: AzureAdM2MConfig +): AzureAdMachineToMachineTokenClient = + when (runtimeEnvironment) { + is Local -> AzureAdTokenClientBuilder.builder() + .withClientId(azureProviderConfig.clientId) + .withPrivateJwk(createMockRSAKey("azure")) + .withTokenEndpointUrl(azureProviderConfig.tokenEndpointUrl) + .buildMachineToMachineTokenClient() + + else -> AzureAdTokenClientBuilder.builder() + .withNaisDefaults() + .withCache(CaffeineTokenCache()) + .buildMachineToMachineTokenClient() + } + +private fun createMockRSAKey(keyID: String): String? = KeyPairGenerator + .getInstance("RSA").let { + it.initialize(2048) + it.generateKeyPair() + }.let { + RSAKey.Builder(it.public as RSAPublicKey) + .privateKey(it.private as RSAPrivateKey) + .keyUse(KeyUse.SIGNATURE) + .keyID(keyID) + .build() + .toJSONString() + } \ No newline at end of file diff --git a/lib/http-client-utils/src/main/kotlin/no/nav/paw/client/factory/HttpClientFactory.kt b/lib/http-client-utils/src/main/kotlin/no/nav/paw/client/factory/HttpClientFactory.kt new file mode 100644 index 00000000..b6cfb950 --- /dev/null +++ b/lib/http-client-utils/src/main/kotlin/no/nav/paw/client/factory/HttpClientFactory.kt @@ -0,0 +1,36 @@ +package no.nav.paw.client.factory + +import io.ktor.client.HttpClient +import io.ktor.client.HttpClientConfig +import io.ktor.client.engine.HttpClientEngine +import io.ktor.client.engine.HttpClientEngineConfig +import io.ktor.client.engine.HttpClientEngineFactory +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.serialization.jackson.jackson + +fun createHttpClient( + engineFactory: HttpClientEngineFactory = CIO, + block: HttpClientConfig<*>.() -> Unit = { + install(ContentNegotiation) { + jackson { + configureJackson() + } + } + } +): HttpClient { + return HttpClient(engineFactory, block) +} + +fun createHttpClient( + engine: HttpClientEngine, + block: HttpClientConfig<*>.() -> Unit = { + install(ContentNegotiation) { + jackson { + configureJackson() + } + } + } +): HttpClient { + return HttpClient(engine, block) +} diff --git a/lib/http-client-utils/src/main/kotlin/no/nav/paw/client/factory/JacksonFactory.kt b/lib/http-client-utils/src/main/kotlin/no/nav/paw/client/factory/JacksonFactory.kt new file mode 100644 index 00000000..150d5720 --- /dev/null +++ b/lib/http-client-utils/src/main/kotlin/no/nav/paw/client/factory/JacksonFactory.kt @@ -0,0 +1,32 @@ +package no.nav.paw.client.factory + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.module.kotlin.KotlinFeature +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.kotlinModule + +fun createObjectMapper(): ObjectMapper { + return jacksonObjectMapper().apply { + configureJackson() + } +} + +fun ObjectMapper.configureJackson() { + setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL) + disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS) + registerModule(JavaTimeModule()) + kotlinModule { + withReflectionCacheSize(512) + disable(KotlinFeature.NullIsSameAsDefault) + disable(KotlinFeature.SingletonSupport) + disable(KotlinFeature.StrictNullChecks) + enable(KotlinFeature.NullToEmptyCollection) + enable(KotlinFeature.NullToEmptyMap) + } +} diff --git a/lib/http-client-utils/src/main/resources/nais/azure_m2m_config.toml b/lib/http-client-utils/src/main/resources/nais/azure_m2m_config.toml new file mode 100644 index 00000000..4f92e5d3 --- /dev/null +++ b/lib/http-client-utils/src/main/resources/nais/azure_m2m_config.toml @@ -0,0 +1,2 @@ +clientId = "${AZURE_APP_CLIENT_ID}" +tokenEndpointUrl = "${AZURE_OPENID_CONFIG_TOKEN_ENDPOINT}" diff --git a/settings.gradle.kts b/settings.gradle.kts index 9a0280fd..1c35ab4a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,6 +14,7 @@ include( "lib:hoplite-config", "lib:error-handling", "lib:security", + "lib:http-client-utils", "lib:kafka", "lib:kafka-streams", "lib:kafka-key-generator-client",