Skip to content

Commit

Permalink
test ny altinn-tilganger tjeneste i dev
Browse files Browse the repository at this point in the history
  • Loading branch information
kenglxn committed Sep 5, 2024
1 parent fa1e860 commit 3779724
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 56 deletions.
1 change: 1 addition & 0 deletions app/nais/dev-gcp-bruker-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ spec:
rules:
- application: altinn-rettigheter-proxy
namespace: arbeidsgiver
- application: arbeidsgiver-altinn-tilganger
external:
- host: fakedings.intern.dev.nav.no # fakedings login
- host: api-gw-q1.oera.no # fallback for altinn-rettigheter-proxy-client
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package no.nav.arbeidsgiver.notifikasjon.bruker

import io.micrometer.core.instrument.search.MeterNotFoundException
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.*
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.Database.Companion.openDatabaseAsync
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.altinn.Altinn
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.altinn.AltinnCachedImpl
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.altinn.AltinnTilgangerClient
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.altinn.AltinnTilgangerImpl
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.altinn.SuspendingAltinnClient
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.http.HttpAuthProviders
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.http.JWTAuthentication
Expand Down Expand Up @@ -41,7 +45,8 @@ object Bruker {
suspendingAltinnClient: SuspendingAltinnClient = SuspendingAltinnClient(
observer = virksomhetsinfoService::altinnObserver
),
altinn: Altinn = AltinnCachedImpl(suspendingAltinnClient),
//altinn: Altinn = AltinnCachedImpl(suspendingAltinnClient),
altinn: Altinn = AltinnTilgangerImpl(AltinnTilgangerClient()),
httpPort: Int = 8080
) {
runBlocking(Dispatchers.Default) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ interface Altinn {
): Tilganger
}

class AltinnTilgangerImpl(
private val altinnTilgangerClient: AltinnTilgangerClient
): Altinn {
override suspend fun hentTilganger(
fnr: String,
selvbetjeningsToken: String,
tjenester: Iterable<ServicecodeDefinisjon>
): Tilganger = altinnTilgangerClient.hentTilganger()
}

class AltinnImpl(
private val klient: SuspendingAltinnClient,
) : Altinn {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package no.nav.arbeidsgiver.notifikasjon.infrastruktur.altinn

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.*
import io.ktor.client.engine.apache.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.network.sockets.*
import io.ktor.serialization.jackson.*
import no.nav.arbeidsgiver.notifikasjon.bruker.BrukerModel
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.HttpClientMetricsFeature
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.Metrics
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.NaisEnvironment
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.PropagateFromMDCPlugin
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.tokenx.TokenXClient
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.tokenx.TokenXClientImpl
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.tokenx.TokenXPlugin
import org.apache.http.ConnectionClosedException
import javax.net.ssl.SSLHandshakeException

class AltinnTilgangerClient(
private val baseUrl: String? = null,
private val tokenXClient: TokenXClient = TokenXClientImpl(),
engine: HttpClientEngine = Apache.create(),
) {

private val httpClient = HttpClient(engine) {
defaultRequest {
url(baseUrl ?: "http://arbeidsgiver-altinn-tilganger")
}
install(ContentNegotiation) {
jackson()
}
install(PropagateFromMDCPlugin) {
propagate("x_correlation_id")
}
install(HttpClientMetricsFeature) {
registry = Metrics.meterRegistry
}
install(HttpRequestRetry) {
maxRetries = 3
retryOnExceptionIf { _, cause ->
cause is ConnectionClosedException ||
cause is SocketTimeoutException ||
cause is SSLHandshakeException
}
delayMillis { 250L }
}
install(TokenXPlugin) {
audience = "${NaisEnvironment.clusterName}:fager:arbeidsgiver-altinn-tilganger"
tokenXClient = this@AltinnTilgangerClient.tokenXClient
}
}

// TODO: ikke bruk BrukerModel typen her, lag egne DTOer for denne klienten og konverter til BrukerModel i tjenesten
suspend fun hentTilganger(): BrukerModel.Tilganger {
val response = httpClient.post {
url {
path("/tilganger")
}
accept(ContentType.Application.Json)
}
val dto = response.body<AltinnTilgangerResponse>()


return BrukerModel.Tilganger(
harFeil = dto.isError,
tjenestetilganger = dto.orgNrTilTilganger.flatMap {
(orgNr, tilganger) -> tilganger.map { tilgang ->
val (code, edition) = tilgang.split(":").let {
it.first() to it.getOrElse(1) { "" }
}
BrukerModel.Tilgang.Altinn(orgNr, code, edition)
}
}
)
}

}

@JsonIgnoreProperties(ignoreUnknown = true)
data class AltinnTilgangerResponse(
val isError: Boolean,
val orgNrTilTilganger: Map<String, List<String>>,
)
Original file line number Diff line number Diff line change
@@ -1,69 +1,38 @@
package no.nav.arbeidsgiver.notifikasjon.infrastruktur.tokenx

import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.api.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.util.*
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.logger

private const val BEARER_PREFIX = "Bearer "

class TokenXPlugin internal constructor(
val audience: String,
val tokenXClient: TokenXClient,
) {
class TokenXPluginConfig {
var audience: String? = null
var tokenXClient: TokenXClient? = null
}

class Config {
var audience: String? = null
set(value) {
require(audience == null) {
"audience already set"
}
field = value
}
var tokenXClient: TokenXClient? = null
set(value) {
require(tokenXClient == null) {
"tokenXClient already set"
}
field = value
}
}

companion object Plugin : HttpClientPlugin<Config, TokenXPlugin> {
private val log = logger()

override val key: AttributeKey<TokenXPlugin> = AttributeKey("TokenXPlugin")
val TokenXPlugin = createClientPlugin("TokenXPlugin", ::TokenXPluginConfig) {
val log = logger()

override fun prepare(block: Config.() -> Unit): TokenXPlugin {
val config = Config().apply(block)
return TokenXPlugin(
audience = requireNotNull(config.audience) {
"TokenXPlugin missing audience. Please configure audience."
},
tokenXClient = config.tokenXClient ?: TokenXClientImpl()
)
}

override fun install(plugin: TokenXPlugin, scope: HttpClient) {
scope.requestPipeline.intercept(HttpRequestPipeline.Phases.State) {
val subjectToken = context.headers[HttpHeaders.Authorization]?.removePrefix(BEARER_PREFIX)
val tokenXClient = requireNotNull(pluginConfig.tokenXClient) {
"TokenXPlugin: property 'tokenXClient' must be set in configuration when installing plugin"
}
val audience = requireNotNull(pluginConfig.audience) {
"TokenXPlugin: property 'audience' must be set in configuration when installing plugin"
}

if (subjectToken == null) {
log.warn("subjectToken not present. skipping exchange for audience {}", plugin.audience)
} else {
try {
val accessToken = plugin.tokenXClient.exchange(subjectToken, plugin.audience)
context.bearerAuth(accessToken)
} catch (e: RuntimeException) {
log.warn("failed to set bearer auth header due to exception in exchange ${e.message}", e)
}
}
onRequest { request, _ ->
val subjectToken = request.headers[HttpHeaders.Authorization]?.removePrefix(BEARER_PREFIX)

proceed()
if (subjectToken == null) {
log.warn("subjectToken not present. skipping exchange for audience {}", audience)
} else {
try {
request.bearerAuth(tokenXClient.exchange(subjectToken, audience))
} catch (e: RuntimeException) {
log.warn("failed to set bearer auth header due to exception in exchange ${e.message}", e)
}

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package no.nav.arbeidsgiver.notifikasjon.infrastruktur.altinn

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
import io.kotest.matchers.shouldBe
import io.ktor.client.engine.mock.*
import io.ktor.http.*
import io.ktor.utils.io.*
import no.nav.arbeidsgiver.notifikasjon.bruker.BrukerModel
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.tokenx.TokenXClientStub

class AltinnTilgangerClientTest : DescribeSpec({
describe("AltinnTilgangerClient") {

val client = AltinnTilgangerClient(
tokenXClient = TokenXClientStub(),
engine = MockEngine { _ ->
respond(
content = ByteReadChannel(altinnTilgangerResponse),
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
)

it("returns all tilganger") {
client.hentTilganger().also {
it.harFeil shouldBe true
it.tjenestetilganger shouldContainExactlyInAnyOrder listOf(
BrukerModel.Tilgang.Altinn("910825496", "test-fager", ""),
BrukerModel.Tilgang.Altinn("910825496", "4936", "1"),
)
}
}
}

})

private val altinnTilgangerResponse = """
{
"isError": true,
"hierarki": [
{
"orgNr": "810825472",
"altinn3Tilganger": [],
"altinn2Tilganger": [],
"underenheter": [
{
"orgNr": "910825496",
"altinn3Tilganger": [
"test-fager"
],
"altinn2Tilganger": [
"4936:1"
],
"underenheter": []
}
]
}
],
"orgNrTilTilganger": {
"910825496": [
"test-fager",
"4936:1"
]
},
"tilgangTilOrgNr": {
"test-fager": [
"910825496"
],
"4936:1": [
"910825496"
]
}
}
""".trimIndent()

0 comments on commit 3779724

Please sign in to comment.