Skip to content

Commit

Permalink
Erstatt token provider med forenklet versjon (#10)
Browse files Browse the repository at this point in the history
* Erstatt token provider med forenklet versjon

* Bruk bedre navn på exception

* Bruk korrekt ktlint-versjon i workflow

* Legg til tester

* Legg til dokumentasjon
  • Loading branch information
bjerga authored Mar 11, 2024
1 parent 0f8f6aa commit e9e822d
Show file tree
Hide file tree
Showing 20 changed files with 286 additions and 253 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ktlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ jobs:
- name: "Install ktlint"
uses: nbadal/action-ktlint-setup@v1
with:
ktlint_version: '1.0.0'
ktlint_version: '1.1.1'
- run: ktlint
shell: bash
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
# helsearbeidsgiver-tokenprovider
# helsearbeidsgiver-tokenprovider

Denne pakken inneholder hjelpemetoder for å hente OAuth2-token for tilgang mellom apper (med grant type "client_credentials").

Verdiene som er påkrevd i `OAuth2Environment` finnes typisk som systemvaribler.
Systemvariablene stammer fra en Kubernetes-secret som automatisk legges til Nais-apper som har aktivert Azure AD
([se hvordan her](https://doc.nav.cloud.nais.io/reference/application-spec/?h=azure#azureapplicationenabled)).
Kubernetes-secreten vil hete `azure-<appnavn>-<id>` og inneholde systemvariablene som leses i eksempelet nedenfor.

```kt
import no.nav.helsearbeidsgiver.tokenprovider.OAuth2Environment
import no.nav.helsearbeidsgiver.tokenprovider.oauth2ClientCredentialsTokenGetter

val oauth2Environment = OAuth2Environment(
scope = "api://dev-gcp.eksempel-scope/.default",
wellKnownUrl = "AZURE_APP_WELL_KNOWN_URL".let(System::getenv),
tokenEndpointUrl = "AZURE_OPENID_CONFIG_TOKEN_ENDPOINT".let(System::getenv),
clientId = "AZURE_APP_CLIENT_ID".let(System::getenv),
clientSecret = "AZURE_APP_CLIENT_SECRET".let(System::getenv),
clientJwk = "AZURE_APP_JWK".let(System::getenv)
)

val tokenGetter = oauth2ClientCredentialsTokenGetter(oauth2Environment)

val accessToken = tokenGetter()
```
26 changes: 11 additions & 15 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,37 +31,33 @@ repositories {
}

dependencies {
val hagUtilsVersion: String by project
val jacksonVersion: String by project
val kotestVersion: String by project
val ktorVersion: String by project
val logbackVersion: String by project
val mockkVersion: String by project
val kotlinCoroutinesVersion: String by project
val kotlinSerializationVersion: String by project
val junitJupiterVersion: String by project
val tokenClientCoreVersion: String by project
val nimbusJoseJwtVersion: String by project
val logbackVersion: String by project
val slf4jVersion: String by project
val jacksonVersion: String by project
val hagUtilsVersion: String by project
val tokenClientCoreVersion: String by project

runtimeOnly("ch.qos.logback:logback-classic:$logbackVersion")
api("io.ktor:ktor-client-core:$ktorVersion")

implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion")
implementation("com.nimbusds:nimbus-jose-jwt:$nimbusJoseJwtVersion")
implementation("io.ktor:ktor-client-apache5:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-serialization-jackson:$ktorVersion")
implementation("no.nav.helsearbeidsgiver:utils:$hagUtilsVersion")
implementation("no.nav.security:token-client-core:$tokenClientCoreVersion")
implementation("no.nav.security:token-validation-ktor-v2:$tokenClientCoreVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
implementation("org.slf4j:slf4j-api:$slf4jVersion")

runtimeOnly("ch.qos.logback:logback-classic:$logbackVersion")

testImplementation(testFixtures("no.nav.helsearbeidsgiver:utils:$hagUtilsVersion"))
testImplementation("io.ktor:ktor-client-mock:$ktorVersion")
testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
testImplementation("io.mockk:mockk:$mockkVersion")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion")
testImplementation("org.junit.jupiter:junit-jupiter:$junitJupiterVersion")
testImplementation("io.ktor:ktor-client-mock:$ktorVersion")
}

publishing {
Expand Down
21 changes: 9 additions & 12 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ kotlin.code.style=official

# Plugin versions
kotlinVersion=1.9.22
kotlinterVersion=4.0.0
kotlinterVersion=4.2.0

# Dependency versions
junitJupiterVersion=5.10.0
ktorVersion=2.3.5
mockkVersion=1.13.8
kotlinCoroutinesVersion=1.7.3
kotlinSerializationVersion=1.6.0
tokenClientCoreVersion=3.1.7
nimbusJoseJwtVersion=9.36
logbackVersion=1.4.11
slf4jVersion=2.0.9
jacksonVersion=2.15.2
hagUtilsVersion=0.7.0
hagUtilsVersion=0.8.0
jacksonVersion=2.16.1
kotestVersion=5.8.0
ktorVersion=2.3.8
logbackVersion=1.5.2
mockkVersion=1.13.10
nimbusJoseJwtVersion=9.37.3
tokenClientCoreVersion=4.1.3

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import io.ktor.client.engine.apache5.Apache5
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.jackson.jackson

internal fun createHttpClient(): HttpClient = HttpClient(Apache5) { configureClientConfig() }
internal fun createHttpClient(): HttpClient = HttpClient(Apache5) { customConfig() }

internal fun HttpClientConfig<*>.configureClientConfig() {
internal fun HttpClientConfig<*>.customConfig() {
expectSuccess = true
install(ContentNegotiation) {
jackson {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package no.nav.helsearbeidsgiver.tokenprovider

import io.ktor.client.HttpClient
import no.nav.security.token.support.client.core.oauth2.ClientCredentialsGrantRequest
import no.nav.security.token.support.client.core.oauth2.ClientCredentialsTokenClient

/** @param httpClient Dersom egendefinert klient brukes så kan gal konfigurasjon føre til feil. */
fun oauth2ClientCredentialsTokenGetter(
env: OAuth2Environment,
httpClient: HttpClient = createHttpClient(),
): () -> String {
val tokenClient = TokenClient(httpClient).let(::ClientCredentialsTokenClient)

val request = env.toClientCredentialsProperties().let(::ClientCredentialsGrantRequest)

return {
tokenClient.getTokenResponse(request).accessToken
?: throw MissingAccessTokenException()
}
}

class MissingAccessTokenException : Exception()
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package no.nav.helsearbeidsgiver.tokenprovider

import com.nimbusds.oauth2.sdk.GrantType
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod
import no.nav.security.token.support.client.core.ClientAuthenticationProperties
import no.nav.security.token.support.client.core.ClientProperties
import java.net.URI

data class OAuth2Environment(
val scope: String,
val wellKnownUrl: String,
val tokenEndpointUrl: String,
val clientId: String,
val clientSecret: String,
val clientJwk: String,
) {
internal fun toClientCredentialsProperties(): ClientProperties =
ClientProperties(
tokenEndpointUrl = tokenEndpointUrl.let(::URI),
wellKnownUrl = wellKnownUrl.let(::URI),
grantType = GrantType.CLIENT_CREDENTIALS,
scope = scope.split(","),
authentication =
ClientAuthenticationProperties(
clientId = clientId,
clientAuthMethod = ClientAuthenticationMethod.CLIENT_SECRET_POST,
clientSecret = clientSecret,
clientJwk = clientJwk,
),
resourceUrl = null,
tokenExchange = null,
)
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package no.nav.helsearbeidsgiver.tokenprovider

import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.request.forms.submitForm
import io.ktor.http.parametersOf
import kotlinx.coroutines.runBlocking
import no.nav.helsearbeidsgiver.utils.log.logger
import no.nav.helsearbeidsgiver.utils.log.sikkerLogger
import no.nav.security.token.support.client.core.http.OAuth2HttpClient
import no.nav.security.token.support.client.core.http.OAuth2HttpRequest
import no.nav.security.token.support.client.core.oauth2.OAuth2AccessTokenResponse

internal class TokenClient(
private val httpClient: HttpClient,
) : OAuth2HttpClient {
private val logger = logger()
private val sikkerLogger = sikkerLogger()

override fun post(req: OAuth2HttpRequest): OAuth2AccessTokenResponse =
runBlocking {
try {
httpClient.submitForm(
url = req.tokenEndpointUrl.toString(),
formParameters =
req.formParameters
.mapValues { listOf(it.value) }
.let(::parametersOf),
).body<OAuth2AccessTokenResponse>()
} catch (e: Exception) {
if (e is ClientRequestException) {
"Noe gikk galt under henting av av OAuth2-token.".also {
logger.error(it)
sikkerLogger.error("$it. Error response: ${e.response.body<String>()}")
}
}
throw e
}
}
}

This file was deleted.

Loading

0 comments on commit e9e822d

Please sign in to comment.