Skip to content

Commit

Permalink
La til integrasjon mot poao
Browse files Browse the repository at this point in the history
  • Loading branch information
nilsmsa committed Dec 12, 2024
1 parent ef08b42 commit 4e1286d
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 22 deletions.
2 changes: 1 addition & 1 deletion apps/tilgangskontroll/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ val image: String? by project
dependencies {
implementation(project(":lib:hoplite-config"))
implementation(project(":lib:http-client-utils-ktorv3"))

implementation(project(":lib:error-handling"))

implementation(libs.arrow.core.core)
implementation(libs.bundles.ktor3ServerWithNettyAndMicrometer)
Expand Down
5 changes: 5 additions & 0 deletions apps/tilgangskontroll/nais/nais-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ spec:
prometheus:
enabled: true
path: /internal/metrics
accessPolicy:
outbound:
rules:
- application: poao-tilgang
namespace: poao
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
package no.nav.paw.tilgangskontroll

import io.ktor.server.application.install
import io.ktor.server.auth.Authentication
import io.ktor.server.auth.authenticate
import io.ktor.server.engine.embeddedServer
import io.ktor.server.metrics.micrometer.MicrometerMetrics
import io.ktor.server.netty.Netty
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.routing
import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics
import io.micrometer.core.instrument.binder.jvm.JvmInfoMetrics
Expand All @@ -22,16 +18,14 @@ import no.nav.paw.client.config.AzureAdM2MConfig
import no.nav.paw.client.factory.createAzureAdM2MTokenClient
import no.nav.paw.client.factory.createHttpClient
import no.nav.paw.config.hoplite.loadNaisOrLocalConfiguration
import no.nav.paw.tilgangskontroll.api.models.TilgangskontrollRequestV1
import no.nav.paw.tilgangskontroll.api.models.TilgangskontrollResponseV1
import no.nav.paw.tilgangskontroll.api.validering.valider
import no.nav.paw.tilgangskontroll.ktorserver.AuthProvider
import no.nav.paw.tilgangskontroll.ktorserver.authProvidersOf
import no.nav.paw.tilgangskontroll.ktorserver.configureAuthentication
import no.nav.paw.tilgangskontroll.ktorserver.installContentNegotiation
import no.nav.paw.tilgangskontroll.ktorserver.installStatusPage
import no.nav.paw.tilgangskontroll.poaotilgang.PoaoConfig
import no.nav.paw.tilgangskontroll.poaotilgang.initPoaobackend
import no.nav.security.token.support.v3.RequiredClaims
import no.nav.security.token.support.v3.tokenValidationSupport
import no.nav.paw.tilgangskontroll.routes.apiV1Tilgang
import org.slf4j.LoggerFactory

private val logger = LoggerFactory.getLogger("tilgangskontroll")
Expand All @@ -56,6 +50,8 @@ fun main() {
JvmInfoMetrics()
)
}
installContentNegotiation()
installStatusPage()
configureAuthentication(authProviders)
routing {
get("/internal/isAlive") {
Expand All @@ -68,16 +64,7 @@ fun main() {
call.respondText(prometheusMeterRegistry.scrape())
}
authenticate(AuthProvider.EntraId.name) {
post("/api/v1/tilgang") {
val request: TilgangskontrollRequestV1 = call.receive<TilgangskontrollRequestV1>()
val validertTilgangskontrollRequest = request.valider()
val harTilgang = service.harAnsattTilgangTilPerson(
navIdent = validertTilgangskontrollRequest.navAnsatt,
identitetsnummer = validertTilgangskontrollRequest.person,
tilgang = validertTilgangskontrollRequest.tilgang
)
call.respond(TilgangskontrollResponseV1(harTilgang = harTilgang))
}
apiV1Tilgang(service)
}
}
}.start(wait = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
package no.nav.paw.tilgangskontroll.api.validering

import io.ktor.http.HttpStatusCode
import no.nav.paw.error.exception.ServerResponseException
import no.nav.paw.error.model.ErrorType
import no.nav.paw.tilgangskontroll.api.models.TilgangskontrollRequestV1
import no.nav.paw.tilgangskontroll.vo.Identitetsnummer
import no.nav.paw.tilgangskontroll.vo.EntraId
import no.nav.paw.tilgangskontroll.vo.Tilgang
import java.net.URI

fun TilgangskontrollRequestV1?.valider(): ValidertTilgangskontrollRequest {
requireNotNull(this) { "Request er 'null'" }
require(identitetsnummer.matches(Regex("\\d{11}"))) { "Ugyldig identitetsnummer" }
try {
requireNotNull(this) { "Request er 'null'" }
require(identitetsnummer.matches(Regex("\\d{11}"))) { "Ugyldig identitetsnummer" }
} catch (ex: IllegalArgumentException) {
throw ServerResponseException(
status = HttpStatusCode.BadRequest,
type = ErrorType.domain("http").error("ugyldig-foresporsel").build(),
message = ex.message ?: "Ugyldig forespørsel",
cause = ex
)
}
return ValidertTilgangskontrollRequest(
person = Identitetsnummer(identitetsnummer),
navAnsatt = EntraId(navAnsattId),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package no.nav.paw.tilgangskontroll.ktorserver

import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import io.ktor.serialization.jackson.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*

fun Application.installContentNegotiation() {
install(ContentNegotiation) {
jackson {
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
registerModule(JavaTimeModule())
registerKotlinModule()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package no.nav.paw.tilgangskontroll.ktorserver

import io.ktor.server.application.*
import io.ktor.server.plugins.statuspages.*
import no.nav.paw.error.handler.handleException

fun Application.installStatusPage() {
install(StatusPages) {
exception<Throwable> { call: ApplicationCall, cause: Throwable ->
call.handleException(cause)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package no.nav.paw.tilgangskontroll.routes

import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import no.nav.paw.tilgangskontroll.TilgangsTjenesteForAnsatte
import no.nav.paw.tilgangskontroll.api.models.TilgangskontrollRequestV1
import no.nav.paw.tilgangskontroll.api.models.TilgangskontrollResponseV1
import no.nav.paw.tilgangskontroll.api.validering.valider

fun Route.apiV1Tilgang(service: TilgangsTjenesteForAnsatte) {
post("/api/v1/tilgang") {
val request: TilgangskontrollRequestV1 = call.receive<TilgangskontrollRequestV1>()
val validertTilgangskontrollRequest = request.valider()
val harTilgang = service.harAnsattTilgangTilPerson(
navIdent = validertTilgangskontrollRequest.navAnsatt,
identitetsnummer = validertTilgangskontrollRequest.person,
tilgang = validertTilgangskontrollRequest.tilgang
)
call.respond(TilgangskontrollResponseV1(harTilgang = harTilgang))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package no.nav.paw.tilgangskontroll

import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.nimbusds.jwt.SignedJWT
import io.kotest.common.runBlocking
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.request.bearerAuth
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.jackson.jackson
import io.ktor.server.application.Application
import io.ktor.server.auth.authenticate
import io.ktor.server.routing.routing
import io.ktor.server.testing.testApplication
import no.nav.paw.config.hoplite.loadNaisOrLocalConfiguration
import no.nav.paw.tilgangskontroll.api.models.TilgangskontrollRequestV1
import no.nav.paw.tilgangskontroll.api.models.TilgangskontrollResponseV1
import no.nav.paw.tilgangskontroll.ktorserver.AuthProvider
import no.nav.paw.tilgangskontroll.ktorserver.AuthProviderConfig
import no.nav.paw.tilgangskontroll.ktorserver.AuthProviders
import no.nav.paw.tilgangskontroll.ktorserver.configureAuthentication
import no.nav.paw.tilgangskontroll.ktorserver.installContentNegotiation
import no.nav.paw.tilgangskontroll.ktorserver.installStatusPage
import no.nav.paw.tilgangskontroll.routes.apiV1Tilgang
import no.nav.paw.tilgangskontroll.vo.EntraId
import no.nav.paw.tilgangskontroll.vo.Identitetsnummer
import no.nav.paw.tilgangskontroll.vo.Tilgang
import no.nav.security.mock.oauth2.MockOAuth2Server
import java.util.*
import java.util.concurrent.ConcurrentHashMap

class TilgangskontrollTest: FreeSpec({
val mockOAuthServer = MockOAuth2Server()
beforeSpec {
mockOAuthServer.start()
}
afterSpec {
mockOAuthServer.shutdown()
}
val map = ConcurrentHashMap<Triple<EntraId, Identitetsnummer, Tilgang>, Boolean>()
val service = object: TilgangsTjenesteForAnsatte {
override suspend fun harAnsattTilgangTilPerson(
navIdent: EntraId,
identitetsnummer: Identitetsnummer,
tilgang: Tilgang
): Boolean {
return map[Triple(navIdent, identitetsnummer, tilgang)] ?: false
}
}

"Verifiser applikasjonsflyt".config(enabled = false) {
val ansatt = NavAnsatt(UUID.randomUUID(), "Z123")
val person = Identitetsnummer("12345678901")
val token = mockOAuthServer.ansattToken(ansatt)
map[Triple(EntraId(ansatt.azureId), person, Tilgang.LESE)] = true
map[Triple(EntraId(ansatt.azureId), person, Tilgang.SKRIVE)] = true
testApplication {
application {
configureAuthentication(mockOAuthServer, AuthProvider.EntraId)
installStatusPage()
installContentNegotiation()
routing {
authenticate(AuthProvider.EntraId.name) {
apiV1Tilgang(service)
}
}
}
val client = createClient {
defaultRequest {
bearerAuth(token.serialize())
}
install(io.ktor.client.plugins.contentnegotiation.ContentNegotiation) {
jackson {
registerKotlinModule()
registerModule(JavaTimeModule())
}
}
}
client.post("/api/v1/tilgang") {
setBody(TilgangskontrollRequestV1(
identitetsnummer = person.value,
navAnsattId = ansatt.azureId,
tilgang = TilgangskontrollRequestV1.Tilgang.LESE
))
} should { response ->
response.status shouldBe HttpStatusCode.OK
val body = runBlocking { response.body<TilgangskontrollResponseV1>() }
body.harTilgang shouldBe true
}
client.post("/api/v1/tilgang") {
setBody(TilgangskontrollRequestV1(
identitetsnummer = person.value,
navAnsattId = ansatt.azureId,
tilgang = TilgangskontrollRequestV1.Tilgang.SKRIVE
))
} should { response ->
response.status shouldBe HttpStatusCode.OK
val body = runBlocking { response.body<TilgangskontrollResponseV1>() }
body.harTilgang shouldBe false
}
}
}

})


fun Application.configureAuthentication(
oAuth2Server: MockOAuth2Server,
vararg authProvider: AuthProvider
) {
val authProviders = authProvider.map { provider ->
loadNaisOrLocalConfiguration<AuthProviderConfig>(provider.config)
.copy(
discoveryUrl = oAuth2Server.wellKnownUrl("default").toString(),
clientId = "default"
)
} .let(::AuthProviders)
configureAuthentication(authProviders)
}

fun MockOAuth2Server.ansattToken(navAnsatt: NavAnsatt): SignedJWT = issueToken(
claims = mapOf(
"oid" to navAnsatt.azureId,
"NAVident" to navAnsatt.ident
)
)

data class NavAnsatt(
val azureId: UUID,
val ident: String
)

0 comments on commit 4e1286d

Please sign in to comment.