From 0de4459b8cf42f8bc3bdfd1999f09a34f7db921b Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Thu, 31 Oct 2024 12:43:58 +0100 Subject: [PATCH] Implementerte felles sikkerhetsmodul i bekreftelse-api --- apps/bekreftelse-api/build.gradle.kts | 1 + .../api/context/ApplicationContext.kt | 8 +- .../bekreftelse/api/context/RequestContext.kt | 39 -- .../api/context/SecurityContext.kt | 13 - .../exception/BearerTokenManglerException.kt | 6 - .../BrukerHarIkkeTilgangException.kt | 6 - .../api/exception/SystemfeilException.kt | 7 - .../exception/UgyldigBearerTokenException.kt | 6 - .../paw/bekreftelse/api/model/AccessToken.kt | 61 --- .../nav/paw/bekreftelse/api/model/Bruker.kt | 35 -- .../paw/bekreftelse/api/model/Converters.kt | 79 ++++ .../bekreftelse/api/model/Identitetsnummer.kt | 10 - .../api/model/MottaBekreftelseRequest.kt | 54 --- .../api/model/TilgjengeligBekreftelse.kt | 10 - .../api/policy/PoaoTilgangAccessPolicy.kt | 88 ++++ .../api/policy/SluttbrukerAccessPolicy.kt | 33 ++ .../api/routes/BekreftelseRoutes.kt | 51 +- .../api/services/AuthorizationService.kt | 146 +----- .../api/services/BekreftelseService.kt | 27 +- .../bekreftelse/api/utils/Authorization.kt | 33 ++ .../api/utils/{Logger.kt => Logging.kt} | 9 +- .../paw/bekreftelse/api/TestDataGenerator.kt | 132 ------ .../bekreftelse/api/plugin/TestDataPlugin.kt | 5 +- .../bekreftelse/api/routes/AuthRoutesTest.kt | 316 ------------- .../api/routes/BekreftelseRoutesTest.kt | 435 +++++++++++++++--- .../api/{ => test}/ApplicationTestContext.kt | 64 +-- .../api/{ => test}/KafkaTestDataProducer.kt | 18 +- .../nav/paw/bekreftelse/api/test/TestData.kt | 144 ++++++ .../bekreftelse/api/test/TokenTestUtils.kt | 43 ++ .../exception/IngenTilgangException.kt | 2 +- .../exception/UgyldigBrukerException.kt | 2 +- .../interceptor/AuthorizationInterceptor.kt | 7 +- .../authorization/model/AccessDecision.kt | 13 +- .../authorization/model/AccessOperation.kt | 2 +- .../authorization/model/AccessResult.kt | 6 - .../authorization/policy/AccessPolicy.kt | 14 +- .../policy/AccessPolicyEvaluator.kt | 21 - .../authorization/policy/TestPolicies.kt | 12 +- .../security/test/TestApplicationContext.kt | 5 +- 39 files changed, 904 insertions(+), 1059 deletions(-) delete mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/context/RequestContext.kt delete mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/context/SecurityContext.kt delete mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/BearerTokenManglerException.kt delete mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/BrukerHarIkkeTilgangException.kt delete mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/SystemfeilException.kt delete mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/UgyldigBearerTokenException.kt delete mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/AccessToken.kt delete mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/Bruker.kt create mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/Converters.kt delete mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/Identitetsnummer.kt create mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/policy/PoaoTilgangAccessPolicy.kt create mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/policy/SluttbrukerAccessPolicy.kt create mode 100644 apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Authorization.kt rename apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/{Logger.kt => Logging.kt} (87%) delete mode 100644 apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/TestDataGenerator.kt delete mode 100644 apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/routes/AuthRoutesTest.kt rename apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/{ => test}/ApplicationTestContext.kt (75%) rename apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/{ => test}/KafkaTestDataProducer.kt (80%) create mode 100644 apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/TestData.kt create mode 100644 apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/TokenTestUtils.kt rename lib/security/src/main/kotlin/no/nav/paw/security/{authentication => authorization}/exception/IngenTilgangException.kt (75%) rename lib/security/src/main/kotlin/no/nav/paw/security/{authentication => authorization}/exception/UgyldigBrukerException.kt (75%) delete mode 100644 lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessResult.kt delete mode 100644 lib/security/src/main/kotlin/no/nav/paw/security/authorization/policy/AccessPolicyEvaluator.kt diff --git a/apps/bekreftelse-api/build.gradle.kts b/apps/bekreftelse-api/build.gradle.kts index 7d476d1c..86930d94 100644 --- a/apps/bekreftelse-api/build.gradle.kts +++ b/apps/bekreftelse-api/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { // Project implementation(project(":lib:hoplite-config")) implementation(project(":lib:error-handling")) + implementation(project(":lib:security")) implementation(project(":lib:kafka-streams")) implementation(project(":lib:kafka-key-generator-client")) implementation(project(":domain:bekreftelse-interne-hendelser")) diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/context/ApplicationContext.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/context/ApplicationContext.kt index 2134b611..3feedbd3 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/context/ApplicationContext.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/context/ApplicationContext.kt @@ -72,12 +72,7 @@ data class ApplicationContext( ) ) - val authorizationService = AuthorizationService( - serverConfig, - applicationConfig, - kafkaKeysClient, - poaoTilgangClient - ) + val authorizationService = AuthorizationService(serverConfig, poaoTilgangClient) val kafkaConsumerExceptionHandler = KafkaConsumerExceptionHandler( healthIndicatorRepository.addLivenessIndicator(LivenessHealthIndicator(HealthStatus.HEALTHY)), @@ -107,6 +102,7 @@ data class ApplicationContext( serverConfig, applicationConfig, prometheusMeterRegistry, + kafkaKeysClient, bekreftelseKafkaProducer, bekreftelseRepository ) diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/context/RequestContext.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/context/RequestContext.kt deleted file mode 100644 index cfa42f13..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/context/RequestContext.kt +++ /dev/null @@ -1,39 +0,0 @@ -package no.nav.paw.bekreftelse.api.context - -import io.ktor.http.HttpHeaders -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call -import io.ktor.server.auth.principal -import io.ktor.server.request.path -import io.ktor.util.pipeline.PipelineContext -import no.nav.security.token.support.v2.TokenValidationContextPrincipal - -sealed class NavHeader(val name: String) - -data object TraceParent : NavHeader("traceparent") -data object NavCallId : NavHeader("Nav-Call-Id") -data object NavConsumerId : NavHeader("Nav-Consumer-Id") - -data class RequestContext( - val path: String, - val callId: String?, - val traceParent: String?, - val navConsumerId: String?, - val bearerToken: String?, - val identitetsnummer: String?, - val principal: TokenValidationContextPrincipal? -) - -fun PipelineContext.resolveRequest( - identitetsnummer: String? = null -): RequestContext { - return RequestContext( - path = call.request.path(), - callId = call.request.headers[NavCallId.name], - traceParent = call.request.headers[TraceParent.name], - navConsumerId = call.request.headers[NavConsumerId.name], - bearerToken = call.request.headers[HttpHeaders.Authorization], - identitetsnummer = identitetsnummer, - principal = call.principal() - ) -} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/context/SecurityContext.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/context/SecurityContext.kt deleted file mode 100644 index ed7a128f..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/context/SecurityContext.kt +++ /dev/null @@ -1,13 +0,0 @@ -package no.nav.paw.bekreftelse.api.context - -import no.nav.paw.bekreftelse.api.model.AccessToken -import no.nav.paw.bekreftelse.api.model.InnloggetBruker -import no.nav.paw.bekreftelse.api.model.Sluttbruker -import no.nav.poao_tilgang.client.TilgangType - -data class SecurityContext( - val sluttbruker: Sluttbruker, - val innloggetBruker: InnloggetBruker, - val accessToken: AccessToken, - val tilgangType: TilgangType -) diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/BearerTokenManglerException.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/BearerTokenManglerException.kt deleted file mode 100644 index ed54d209..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/BearerTokenManglerException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package no.nav.paw.bekreftelse.api.exception - -import no.nav.paw.error.exception.AuthenticationException - -class BearerTokenManglerException(message: String) : - AuthenticationException("PAW_BEARER_TOKEN_MANGLER", message) \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/BrukerHarIkkeTilgangException.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/BrukerHarIkkeTilgangException.kt deleted file mode 100644 index 6c12f844..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/BrukerHarIkkeTilgangException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package no.nav.paw.bekreftelse.api.exception - -import no.nav.paw.error.exception.AuthorizationException - -class BrukerHarIkkeTilgangException(message: String) : - AuthorizationException("PAW_BRUKER_HAR_IKKE_TILGANG", message) \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/SystemfeilException.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/SystemfeilException.kt deleted file mode 100644 index 40b1df6e..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/SystemfeilException.kt +++ /dev/null @@ -1,7 +0,0 @@ -package no.nav.paw.bekreftelse.api.exception - -import io.ktor.http.HttpStatusCode -import no.nav.paw.error.exception.ServerResponseException - -class SystemfeilException(message: String) : - ServerResponseException(HttpStatusCode.InternalServerError, "PAW_SYSTEMFEIL", message) \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/UgyldigBearerTokenException.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/UgyldigBearerTokenException.kt deleted file mode 100644 index dbd86a0d..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/exception/UgyldigBearerTokenException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package no.nav.paw.bekreftelse.api.exception - -import no.nav.paw.error.exception.AuthorizationException - -class UgyldigBearerTokenException(message: String) : - AuthorizationException("PAW_UGYLDIG_BEARER_TOKEN", message) \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/AccessToken.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/AccessToken.kt deleted file mode 100644 index 21e4d0cf..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/AccessToken.kt +++ /dev/null @@ -1,61 +0,0 @@ -package no.nav.paw.bekreftelse.api.model - -import no.nav.paw.bekreftelse.api.exception.UgyldigBearerTokenException -import no.nav.security.token.support.core.context.TokenValidationContext -import no.nav.security.token.support.core.jwt.JwtToken -import java.util.* - -data class AccessToken( - val jwt: String, - val issuer: Issuer, - val claims: Claims -) - -sealed class Issuer(val name: String) - -data object IdPorten : Issuer("idporten") -data object TokenX : Issuer("tokenx") -data object Azure : Issuer("azure") - -class Claims(private val claims: Map, Any>) { - @Suppress("UNCHECKED_CAST") - operator fun get(claim: Claim): T = - claims[claim] as T? - ?: throw UgyldigBearerTokenException("Bearer Token mangler påkrevd claim ${claim.name}") - - fun isEmpty(): Boolean = claims.isEmpty() -} - -sealed class Claim( - val name: String, - val resolve: (String) -> A -) - -data object PID : Claim("pid", ::Identitetsnummer) -data object OID : Claim("oid", UUID::fromString) -data object Name : Claim("name", { it }) -data object NavIdent : Claim("NAVident", { it }) - -sealed class ResolveToken(val issuer: Issuer, val claims: List>) - -data object IdPortenToken : ResolveToken(IdPorten, listOf(PID)) -data object TokenXToken : ResolveToken(TokenX, listOf(PID)) -data object AzureToken : ResolveToken(Azure, listOf(OID, Name, NavIdent)) - -private val validTokens: List = listOf(IdPortenToken, TokenXToken, AzureToken) - -fun TokenValidationContext.resolveTokens(): List { - return validTokens - .map { it to getJwtToken(it.issuer.name) } - .mapNotNull { (resolveToken, jwtToken) -> - jwtToken?.let { AccessToken(it.encodedToken, resolveToken.issuer, it.resolveClaims(resolveToken)) } - } -} - -private fun JwtToken.resolveClaims(resolveToken: ResolveToken): Claims { - val claims = resolveToken.claims.mapNotNull { claim -> - val value = jwtTokenClaims.getStringClaim(claim.name) - value?.let { claim.resolve(value) }?.let { claim to it } - }.toMap() - return Claims(claims) -} diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/Bruker.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/Bruker.kt deleted file mode 100644 index 349112ad..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/Bruker.kt +++ /dev/null @@ -1,35 +0,0 @@ -package no.nav.paw.bekreftelse.api.model - -import no.nav.paw.bekreftelse.melding.v1.vo.Bruker -import java.util.* - -enum class BrukerType { - UKJENT_VERDI, - SLUTTBRUKER, - VEILEDER -} - -data class InnloggetBruker(val type: BrukerType, val ident: String) - -data class Sluttbruker( - val identitetsnummer: String, - val arbeidssoekerId: Long, - val kafkaKey: Long -) - -fun InnloggetBruker.asBruker(): Bruker { - return Bruker.newBuilder() - .setId(ident) - .setType(type.asBrukerType()) - .build() -} - -fun BrukerType.asBrukerType(): no.nav.paw.bekreftelse.melding.v1.vo.BrukerType { - return when (this) { - BrukerType.SLUTTBRUKER -> no.nav.paw.bekreftelse.melding.v1.vo.BrukerType.SLUTTBRUKER - BrukerType.VEILEDER -> no.nav.paw.bekreftelse.melding.v1.vo.BrukerType.VEILEDER - else -> no.nav.paw.bekreftelse.melding.v1.vo.BrukerType.UKJENT_VERDI - } -} - -data class NavAnsatt(val azureId: UUID, val navIdent: String) \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/Converters.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/Converters.kt new file mode 100644 index 00000000..7c45bf86 --- /dev/null +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/Converters.kt @@ -0,0 +1,79 @@ +package no.nav.paw.bekreftelse.api.model + +import no.nav.paw.bekreftelse.internehendelser.BekreftelseTilgjengelig +import no.nav.paw.bekreftelse.melding.v1.Bekreftelse +import no.nav.paw.bekreftelse.melding.v1.vo.Bekreftelsesloesning +import no.nav.paw.bekreftelse.melding.v1.vo.BrukerType +import no.nav.paw.bekreftelse.melding.v1.vo.Metadata +import no.nav.paw.bekreftelse.melding.v1.vo.Svar +import no.nav.paw.security.authentication.model.Bruker +import no.nav.paw.security.authentication.model.M2MToken +import no.nav.paw.security.authentication.model.NavAnsatt +import no.nav.paw.security.authentication.model.Sluttbruker +import no.nav.paw.security.authorization.exception.IngenTilgangException +import java.time.Instant + +fun Bruker<*>.asBekreftelseBruker(): no.nav.paw.bekreftelse.melding.v1.vo.Bruker { + return when (this) { + is Sluttbruker -> no.nav.paw.bekreftelse.melding.v1.vo.Bruker(BrukerType.SLUTTBRUKER, ident.verdi) + is NavAnsatt -> no.nav.paw.bekreftelse.melding.v1.vo.Bruker(BrukerType.VEILEDER, ident) + is M2MToken -> no.nav.paw.bekreftelse.melding.v1.vo.Bruker(BrukerType.SYSTEM, ident) + else -> throw IngenTilgangException("Ukjent brukergruppe") + } +} + +fun BekreftelseTilgjengelig.asBekreftelse( + harJobbetIDennePerioden: Boolean, + vilFortsetteSomArbeidssoeker: Boolean, + bruker: no.nav.paw.bekreftelse.melding.v1.vo.Bruker, + kilde: String, + aarsak: String, + bekreftelsesloesning: Bekreftelsesloesning +): Bekreftelse { + return Bekreftelse.newBuilder() + .setBekreftelsesloesning(bekreftelsesloesning) + .setId(bekreftelseId) + .setPeriodeId(periodeId) + .setSvar( + asSvar( + harJobbetIDennePerioden, + vilFortsetteSomArbeidssoeker, + bruker, + kilde, + aarsak + ) + ) + .build() +} + +private fun BekreftelseTilgjengelig.asSvar( + harJobbetIDennePerioden: Boolean, + vilFortsetteSomArbeidssoeker: Boolean, + bruker: no.nav.paw.bekreftelse.melding.v1.vo.Bruker, + kilde: String, + aarsak: String +): Svar { + return Svar.newBuilder() + .setSendtInnAv( + Metadata.newBuilder() + .setUtfoertAv(bruker) + .setKilde(kilde) + .setAarsak(aarsak) + .setTidspunkt(Instant.now()) + .build() + ) + .setGjelderFra(gjelderFra) + .setGjelderTil(gjelderTil) + .setHarJobbetIDennePerioden(harJobbetIDennePerioden) + .setVilFortsetteSomArbeidssoeker(vilFortsetteSomArbeidssoeker) + .build() +} + +fun BekreftelseRow.asTilgjengeligBekreftelse() = this.data.asTilgjengeligBekreftelse() + +private fun BekreftelseTilgjengelig.asTilgjengeligBekreftelse() = TilgjengeligBekreftelse( + periodeId = this.periodeId, + bekreftelseId = this.bekreftelseId, + gjelderFra = this.gjelderFra, + gjelderTil = this.gjelderTil +) diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/Identitetsnummer.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/Identitetsnummer.kt deleted file mode 100644 index 0ecce95a..00000000 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/Identitetsnummer.kt +++ /dev/null @@ -1,10 +0,0 @@ -package no.nav.paw.bekreftelse.api.model - -@JvmInline -value class Identitetsnummer(val verdi: String) { - override fun toString(): String { - return "*".repeat(verdi.length) - } -} - -fun String.tilIdentitetsnummer(): Identitetsnummer = Identitetsnummer(this) diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/MottaBekreftelseRequest.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/MottaBekreftelseRequest.kt index a145cd88..d5b9b11c 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/MottaBekreftelseRequest.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/MottaBekreftelseRequest.kt @@ -1,12 +1,5 @@ package no.nav.paw.bekreftelse.api.model -import no.nav.paw.bekreftelse.internehendelser.BekreftelseTilgjengelig -import no.nav.paw.bekreftelse.melding.v1.Bekreftelse -import no.nav.paw.bekreftelse.melding.v1.vo.Bekreftelsesloesning -import no.nav.paw.bekreftelse.melding.v1.vo.Bruker -import no.nav.paw.bekreftelse.melding.v1.vo.Metadata -import no.nav.paw.bekreftelse.melding.v1.vo.Svar -import java.time.Instant import java.util.* data class MottaBekreftelseRequest( @@ -15,50 +8,3 @@ data class MottaBekreftelseRequest( val harJobbetIDennePerioden: Boolean, val vilFortsetteSomArbeidssoeker: Boolean ) - -fun BekreftelseTilgjengelig.asBekreftelse( - harJobbetIDennePerioden: Boolean, - vilFortsetteSomArbeidssoeker: Boolean, - bruker: Bruker, - kilde: String, - aarsak: String, - bekreftelsesloesning: Bekreftelsesloesning -): Bekreftelse { - return Bekreftelse.newBuilder() - .setBekreftelsesloesning(bekreftelsesloesning) - .setId(bekreftelseId) - .setPeriodeId(periodeId) - .setSvar( - asSvar( - harJobbetIDennePerioden, - vilFortsetteSomArbeidssoeker, - bruker, - kilde, - aarsak - ) - ) - .build() -} - -private fun BekreftelseTilgjengelig.asSvar( - harJobbetIDennePerioden: Boolean, - vilFortsetteSomArbeidssoeker: Boolean, - bruker: Bruker, - kilde: String, - aarsak: String -): Svar { - return Svar.newBuilder() - .setSendtInnAv( - Metadata.newBuilder() - .setUtfoertAv(bruker) - .setKilde(kilde) - .setAarsak(aarsak) - .setTidspunkt(Instant.now()) - .build() - ) - .setGjelderFra(gjelderFra) - .setGjelderTil(gjelderTil) - .setHarJobbetIDennePerioden(harJobbetIDennePerioden) - .setVilFortsetteSomArbeidssoeker(vilFortsetteSomArbeidssoeker) - .build() -} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/TilgjengeligBekreftelse.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/TilgjengeligBekreftelse.kt index f6233927..fa7012c5 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/TilgjengeligBekreftelse.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/model/TilgjengeligBekreftelse.kt @@ -1,6 +1,5 @@ package no.nav.paw.bekreftelse.api.model -import no.nav.paw.bekreftelse.internehendelser.BekreftelseTilgjengelig import java.time.Instant import java.util.* @@ -10,12 +9,3 @@ data class TilgjengeligBekreftelse( val gjelderFra: Instant, val gjelderTil: Instant, ) - -fun BekreftelseRow.asTilgjengeligBekreftelse() = this.data.asTilgjengeligBekreftelse() - -private fun BekreftelseTilgjengelig.asTilgjengeligBekreftelse() = TilgjengeligBekreftelse( - periodeId = this.periodeId, - bekreftelseId = this.bekreftelseId, - gjelderFra = this.gjelderFra, - gjelderTil = this.gjelderTil -) diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/policy/PoaoTilgangAccessPolicy.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/policy/PoaoTilgangAccessPolicy.kt new file mode 100644 index 00000000..272db80f --- /dev/null +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/policy/PoaoTilgangAccessPolicy.kt @@ -0,0 +1,88 @@ +package no.nav.paw.bekreftelse.api.policy + +import no.nav.paw.bekreftelse.api.config.ServerConfig +import no.nav.paw.bekreftelse.api.utils.audit +import no.nav.paw.bekreftelse.api.utils.buildAuditLogger +import no.nav.paw.security.authentication.model.Identitetsnummer +import no.nav.paw.security.authentication.model.M2MToken +import no.nav.paw.security.authentication.model.NavAnsatt +import no.nav.paw.security.authentication.model.Sluttbruker +import no.nav.paw.security.authorization.context.AuthorizationContext +import no.nav.paw.security.authorization.model.AccessDecision +import no.nav.paw.security.authorization.model.Action +import no.nav.paw.security.authorization.model.Deny +import no.nav.paw.security.authorization.model.Permit +import no.nav.paw.security.authorization.policy.AccessPolicy +import no.nav.poao_tilgang.client.NavAnsattTilgangTilEksternBrukerPolicyInput +import no.nav.poao_tilgang.client.PoaoTilgangClient +import no.nav.poao_tilgang.client.TilgangType +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +private fun Action.asTilgangType(): TilgangType = when (this) { + Action.READ -> TilgangType.LESE + Action.WRITE -> TilgangType.SKRIVE +} + +class PoaoTilgangAccessPolicy( + private val serverConfig: ServerConfig, + private val poaoTilgangClient: PoaoTilgangClient, + private val identitetsnummer: Identitetsnummer? +) : AccessPolicy { + + private val logger = LoggerFactory.getLogger("no.nav.paw.logger.security.authorization") + private val auditLogger: Logger = buildAuditLogger + + override fun hasAccess(action: Action, context: AuthorizationContext): AccessDecision { + val tilgangType = action.asTilgangType() + val (bruker, _) = context.securityContext + + when (bruker) { + is Sluttbruker -> { + logger.debug("Ingen tilgangssjekk for sluttbruker") + return Permit("Sluttbruker har $tilgangType-tilgang") + } + + is NavAnsatt -> { + if (identitetsnummer == null) { + return Deny("Veileder må sende med identitetsnummer for sluttbruker") + } + + val navAnsattTilgang = poaoTilgangClient.evaluatePolicy( + NavAnsattTilgangTilEksternBrukerPolicyInput( + navAnsattAzureId = bruker.oid, + tilgangType = tilgangType, + norskIdent = identitetsnummer.verdi + ) + ) + val tilgang = navAnsattTilgang.get() + if (tilgang == null) { + return Deny("Kunne ikke finne tilgang for ansatt") + } else if (tilgang.isDeny) { + return Deny("NAV-ansatt har ikke $tilgangType-tilgang til sluttbruker") + } else { + logger.debug("NAV-ansatt har benyttet {}-tilgang til informasjon om sluttbruker", tilgangType) + auditLogger.audit( + runtimeEnvironment = serverConfig.runtimeEnvironment, + aktorIdent = bruker.ident, + sluttbrukerIdent = identitetsnummer.verdi, + tilgangType = tilgangType, + melding = "NAV-ansatt har benyttet $tilgangType-tilgang til informasjon om sluttbruker" + ) + return Permit("Veileder har $tilgangType-tilgang til sluttbruker") + } + } + + is M2MToken -> { + if (identitetsnummer == null) { + return Deny("M2M-token må sende med identitetsnummer for sluttbruker") + } + return Permit("M2M-token har $tilgangType-tilgang til sluttbruker") + } + + else -> { + return Deny("Ukjent brukergruppe") + } + } + } +} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/policy/SluttbrukerAccessPolicy.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/policy/SluttbrukerAccessPolicy.kt new file mode 100644 index 00000000..ae882839 --- /dev/null +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/policy/SluttbrukerAccessPolicy.kt @@ -0,0 +1,33 @@ +package no.nav.paw.bekreftelse.api.policy + +import no.nav.paw.security.authentication.model.Identitetsnummer +import no.nav.paw.security.authentication.model.Sluttbruker +import no.nav.paw.security.authorization.context.AuthorizationContext +import no.nav.paw.security.authorization.model.AccessDecision +import no.nav.paw.security.authorization.model.Action +import no.nav.paw.security.authorization.model.Deny +import no.nav.paw.security.authorization.model.Permit +import no.nav.paw.security.authorization.policy.AccessPolicy + +class SluttbrukerAccessPolicy( + private val identitetsnummer: Identitetsnummer? +) : AccessPolicy { + + override fun hasAccess(action: Action, context: AuthorizationContext): AccessDecision { + val (bruker, _) = context.securityContext + + when (bruker) { + is Sluttbruker -> { + // TODO Håndtere verge + if (identitetsnummer != null && identitetsnummer != bruker.ident) { + return Deny("Sluttbruke kan ikke hente data for annen bruker") + } + return Permit("Sluttbruker har tilgang") + } + + else -> { + return Permit("Ikke sluttbruker") + } + } + } +} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/BekreftelseRoutes.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/BekreftelseRoutes.kt index 810e56e1..e861f25c 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/BekreftelseRoutes.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/routes/BekreftelseRoutes.kt @@ -9,43 +9,50 @@ import io.ktor.server.routing.get import io.ktor.server.routing.post import io.ktor.server.routing.route import no.nav.paw.bekreftelse.api.context.ApplicationContext -import no.nav.paw.bekreftelse.api.context.resolveRequest -import no.nav.paw.bekreftelse.api.model.Azure -import no.nav.paw.bekreftelse.api.model.IdPorten import no.nav.paw.bekreftelse.api.model.MottaBekreftelseRequest import no.nav.paw.bekreftelse.api.model.TilgjengeligeBekreftelserRequest -import no.nav.paw.bekreftelse.api.model.TokenX -import no.nav.poao_tilgang.client.TilgangType +import no.nav.paw.bekreftelse.api.utils.hentSluttbrukerIdentitet +import no.nav.paw.security.authentication.model.asIdentitetsnummer +import no.nav.paw.security.authentication.token.AzureAd +import no.nav.paw.security.authentication.token.IdPorten +import no.nav.paw.security.authentication.token.TokenX +import no.nav.paw.security.authorization.interceptor.authorize +import no.nav.paw.security.authorization.model.Action fun Route.bekreftelseRoutes(applicationContext: ApplicationContext) { val authorizationService = applicationContext.authorizationService val bekreftelseService = applicationContext.bekreftelseService route("/api/v1") { - authenticate(IdPorten.name, TokenX.name, Azure.name) { + authenticate(IdPorten.name, TokenX.name, AzureAd.name) { get("/tilgjengelige-bekreftelser") { - val requestContext = resolveRequest() - val securityContext = authorizationService.authorize(requestContext, TilgangType.LESE) - val response = bekreftelseService.finnTilgjengeligBekreftelser(securityContext.sluttbruker) - call.respond(HttpStatusCode.OK, response) + val accessPolicies = authorizationService.accessPolicies() + authorize(Action.READ, accessPolicies) { (_, securityContext) -> + val (bruker, _) = securityContext + val identitetsnummer = bruker.hentSluttbrukerIdentitet() + val response = bekreftelseService.finnTilgjengeligBekreftelser(identitetsnummer) + call.respond(HttpStatusCode.OK, response) + } } post("/tilgjengelige-bekreftelser") { request -> - val requestContext = resolveRequest(request.identitetsnummer) - val securityContext = authorizationService.authorize(requestContext, TilgangType.LESE) - val response = bekreftelseService.finnTilgjengeligBekreftelser(securityContext.sluttbruker) - call.respond(HttpStatusCode.OK, response) + val accessPolicies = authorizationService.accessPolicies(request.identitetsnummer?.asIdentitetsnummer()) + authorize(Action.READ, accessPolicies) { (_, securityContext) -> + val (bruker, _) = securityContext + val identitetsnummer = bruker.hentSluttbrukerIdentitet(request.identitetsnummer) + val response = bekreftelseService.finnTilgjengeligBekreftelser(identitetsnummer) + call.respond(HttpStatusCode.OK, response) + } } post("/bekreftelse") { request -> - val requestContext = resolveRequest(request.identitetsnummer) - val securityContext = authorizationService.authorize(requestContext, TilgangType.SKRIVE) - bekreftelseService.mottaBekreftelse( - securityContext.innloggetBruker, - securityContext.sluttbruker, - request, - ) - call.respond(HttpStatusCode.OK) + val accessPolicies = authorizationService.accessPolicies(request.identitetsnummer?.asIdentitetsnummer()) + authorize(Action.WRITE, accessPolicies) { (_, securityContext) -> + val (bruker, _) = securityContext + val identitetsnummer = bruker.hentSluttbrukerIdentitet(request.identitetsnummer) + bekreftelseService.mottaBekreftelse(bruker, identitetsnummer, request) + call.respond(HttpStatusCode.OK) + } } } } diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/services/AuthorizationService.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/services/AuthorizationService.kt index eb56ec49..6e7044f1 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/services/AuthorizationService.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/services/AuthorizationService.kt @@ -1,151 +1,21 @@ package no.nav.paw.bekreftelse.api.services -import io.opentelemetry.api.trace.Span -import io.opentelemetry.instrumentation.annotations.WithSpan -import no.nav.paw.bekreftelse.api.config.ApplicationConfig import no.nav.paw.bekreftelse.api.config.ServerConfig -import no.nav.paw.bekreftelse.api.context.RequestContext -import no.nav.paw.bekreftelse.api.context.SecurityContext -import no.nav.paw.bekreftelse.api.exception.BearerTokenManglerException -import no.nav.paw.bekreftelse.api.exception.BrukerHarIkkeTilgangException -import no.nav.paw.bekreftelse.api.exception.UgyldigBearerTokenException -import no.nav.paw.bekreftelse.api.model.AccessToken -import no.nav.paw.bekreftelse.api.model.Azure -import no.nav.paw.bekreftelse.api.model.BrukerType -import no.nav.paw.bekreftelse.api.model.Claims -import no.nav.paw.bekreftelse.api.model.InnloggetBruker -import no.nav.paw.bekreftelse.api.model.NavAnsatt -import no.nav.paw.bekreftelse.api.model.NavIdent -import no.nav.paw.bekreftelse.api.model.OID -import no.nav.paw.bekreftelse.api.model.PID -import no.nav.paw.bekreftelse.api.model.Sluttbruker -import no.nav.paw.bekreftelse.api.model.resolveTokens -import no.nav.paw.bekreftelse.api.utils.audit -import no.nav.paw.bekreftelse.api.utils.buildAuditLogger -import no.nav.paw.kafkakeygenerator.client.KafkaKeysClient -import no.nav.poao_tilgang.client.NavAnsattTilgangTilEksternBrukerPolicyInput +import no.nav.paw.bekreftelse.api.policy.PoaoTilgangAccessPolicy +import no.nav.paw.bekreftelse.api.policy.SluttbrukerAccessPolicy +import no.nav.paw.security.authentication.model.Identitetsnummer +import no.nav.paw.security.authorization.policy.AccessPolicy import no.nav.poao_tilgang.client.PoaoTilgangClient -import no.nav.poao_tilgang.client.TilgangType -import org.slf4j.Logger -import org.slf4j.LoggerFactory class AuthorizationService( private val serverConfig: ServerConfig, - private val applicationConfig: ApplicationConfig, - private val kafkaKeysClient: KafkaKeysClient, private val poaoTilgangClient: PoaoTilgangClient ) { - private val logger: Logger = LoggerFactory.getLogger("no.nav.paw.logger.auth") - private val auditLogger: Logger = buildAuditLogger - - @WithSpan - suspend fun authorize(requestContext: RequestContext, tilgangType: TilgangType): SecurityContext { - val tokenContext = requestContext.principal?.context ?: throw BearerTokenManglerException("Sesjon mangler") - - val accessToken = tokenContext.resolveTokens().firstOrNull() - ?: throw UgyldigBearerTokenException("Ingen gyldige Bearer Tokens funnet") - - if (accessToken.claims.isEmpty()) { - throw UgyldigBearerTokenException("Bearer Token mangler påkrevd innhold") - } - - val securityContext = SecurityContext( - sluttbruker = resolveSluttbruker(accessToken, requestContext.identitetsnummer), - innloggetBruker = resolveInnloggetBruker(accessToken), - accessToken = accessToken, - tilgangType = tilgangType + fun accessPolicies(identitetsnummer: Identitetsnummer? = null): List { + return listOf( + SluttbrukerAccessPolicy(identitetsnummer), + PoaoTilgangAccessPolicy(serverConfig, poaoTilgangClient, identitetsnummer) ) - return authorize(securityContext) - } - - private fun authorize(securityContext: SecurityContext): SecurityContext { - val (sluttbruker, _, accessToken, tilgangType) = securityContext - - when (accessToken.issuer) { - is Azure -> { - val navAnsatt = accessToken.claims.toNavAnsatt() - - val navAnsattTilgang = poaoTilgangClient.evaluatePolicy( - NavAnsattTilgangTilEksternBrukerPolicyInput( - navAnsattAzureId = navAnsatt.azureId, - tilgangType = tilgangType, - norskIdent = sluttbruker.identitetsnummer - ) - ) - val tilgang = navAnsattTilgang.getOrDefault { - throw BrukerHarIkkeTilgangException("Kunne ikke finne tilgang for ansatt") - } - - if (tilgang.isDeny) { - throw BrukerHarIkkeTilgangException("NAV-ansatt har ikke $tilgangType-tilgang til bruker") - } else { - logger.debug("NAV-ansatt har benyttet {}-tilgang til informasjon om bruker", tilgangType) - auditLogger.audit( - serverConfig.runtimeEnvironment, - sluttbruker.identitetsnummer, - navAnsatt, - tilgangType, - "NAV-ansatt har benyttet $tilgangType-tilgang til informasjon om bruker" - ) - } - } - - else -> { - // TODO Håndtere verge - logger.debug("Ingen tilgangssjekk for sluttbruker") - } - } - - return securityContext } - - private suspend fun resolveSluttbruker(accessToken: AccessToken, identitetsnummer: String?): Sluttbruker { - val sluttbrukerIdentitetsnummer = when (accessToken.issuer) { - is Azure -> { - // Veiledere skal alltid sende inn identitetsnummer for sluttbruker - identitetsnummer - ?: throw BrukerHarIkkeTilgangException("Veileder må sende med identitetsnummer for sluttbruker") - } - - else -> { - val pid = accessToken.claims[PID].verdi - if (identitetsnummer != null && identitetsnummer != pid) { - // TODO Håndtere verge - throw BrukerHarIkkeTilgangException("Bruker har ikke tilgang til sluttbrukers informasjon") - } - identitetsnummer ?: pid - } - } - - val kafkaKeysResponse = kafkaKeysClient.getIdAndKey(sluttbrukerIdentitetsnummer) - - return Sluttbruker( - identitetsnummer = sluttbrukerIdentitetsnummer, - arbeidssoekerId = kafkaKeysResponse.id, - kafkaKey = kafkaKeysResponse.key - ) - } - - private fun resolveInnloggetBruker(accessToken: AccessToken): InnloggetBruker { - return when (accessToken.issuer) { - is Azure -> { - val ident = accessToken.claims[NavIdent] - InnloggetBruker( - type = BrukerType.VEILEDER, - ident = ident - ) - } - - else -> { - val ident = accessToken.claims[PID].verdi - InnloggetBruker( - type = BrukerType.SLUTTBRUKER, - ident = ident - ) - } - } - } - - private fun Claims.toNavAnsatt() = NavAnsatt(azureId = this[OID], navIdent = this[NavIdent]) } diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/services/BekreftelseService.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/services/BekreftelseService.kt index 01975cae..8b07f23f 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/services/BekreftelseService.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/services/BekreftelseService.kt @@ -8,13 +8,11 @@ import no.nav.paw.bekreftelse.api.config.ApplicationConfig import no.nav.paw.bekreftelse.api.config.ServerConfig import no.nav.paw.bekreftelse.api.exception.DataIkkeFunnetForIdException import no.nav.paw.bekreftelse.api.exception.DataTilhoererIkkeBrukerException -import no.nav.paw.bekreftelse.api.model.InnloggetBruker import no.nav.paw.bekreftelse.api.model.MottaBekreftelseRequest -import no.nav.paw.bekreftelse.api.model.Sluttbruker import no.nav.paw.bekreftelse.api.model.TilgjengeligBekreftelserResponse import no.nav.paw.bekreftelse.api.model.asBekreftelse +import no.nav.paw.bekreftelse.api.model.asBekreftelseBruker import no.nav.paw.bekreftelse.api.model.asBekreftelseRow -import no.nav.paw.bekreftelse.api.model.asBruker import no.nav.paw.bekreftelse.api.model.asTilgjengeligBekreftelse import no.nav.paw.bekreftelse.api.producer.BekreftelseKafkaProducer import no.nav.paw.bekreftelse.api.repository.BekreftelseRepository @@ -33,6 +31,9 @@ import no.nav.paw.bekreftelse.internehendelser.PeriodeAvsluttet import no.nav.paw.bekreftelse.internehendelser.meldingMottattHendelseType import no.nav.paw.bekreftelse.melding.v1.vo.Bekreftelsesloesning import no.nav.paw.config.env.appImageOrDefaultForLocal +import no.nav.paw.kafkakeygenerator.client.KafkaKeysClient +import no.nav.paw.security.authentication.model.Bruker +import no.nav.paw.security.authentication.model.Identitetsnummer import org.apache.kafka.clients.consumer.ConsumerRecord import org.jetbrains.exposed.sql.transactions.transaction @@ -40,26 +41,31 @@ class BekreftelseService( private val serverConfig: ServerConfig, private val applicationConfig: ApplicationConfig, private val meterRegistry: MeterRegistry, + private val kafkaKeysClient: KafkaKeysClient, private val bekreftelseKafkaProducer: BekreftelseKafkaProducer, private val bekreftelseRepository: BekreftelseRepository, ) { private val logger = buildLogger @WithSpan(value = "finnTilgjengeligBekreftelser") - fun finnTilgjengeligBekreftelser(sluttbruker: Sluttbruker): TilgjengeligBekreftelserResponse { + suspend fun finnTilgjengeligBekreftelser(identitetsnummer: Identitetsnummer): TilgjengeligBekreftelserResponse { + val kafkaKeysResponse = kafkaKeysClient.getIdAndKey(identitetsnummer.verdi) + return transaction { logger.info("Skal hente tilgjengelige bekreftelser") - val bekreftelser = bekreftelseRepository.findByArbeidssoekerId(sluttbruker.arbeidssoekerId) + val bekreftelser = bekreftelseRepository.findByArbeidssoekerId(kafkaKeysResponse.id) bekreftelser.map { it.asTilgjengeligBekreftelse() } } } @WithSpan(value = "mottaBekreftelse") - fun mottaBekreftelse( - innloggetBruker: InnloggetBruker, - sluttbruker: Sluttbruker, + suspend fun mottaBekreftelse( + bruker: Bruker<*>, + identitetsnummer: Identitetsnummer, request: MottaBekreftelseRequest ) { + val kafkaKeysResponse = kafkaKeysClient.getIdAndKey(identitetsnummer.verdi) + return transaction { logger.info("Har mottatt bekreftelse") meterRegistry.receiveBekreftelseCounter(meldingMottattHendelseType) @@ -68,14 +74,15 @@ class BekreftelseService( val bekreftelse = bekreftelseRepository.getByBekreftelseId(request.bekreftelseId) if (bekreftelse != null) { - if (bekreftelse.arbeidssoekerId != sluttbruker.arbeidssoekerId) { + + if (bekreftelse.arbeidssoekerId != kafkaKeysResponse.id) { throw DataTilhoererIkkeBrukerException("Bekreftelse tilhører ikke bruker") } val key = bekreftelse.recordKey val message = bekreftelse.data.asBekreftelse( request.harJobbetIDennePerioden, request.vilFortsetteSomArbeidssoeker, - innloggetBruker.asBruker(), + bruker.asBekreftelseBruker(), kilde, "Bekreftelse levert", Bekreftelsesloesning.ARBEIDSSOEKERREGISTERET diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Authorization.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Authorization.kt new file mode 100644 index 00000000..5bef08e2 --- /dev/null +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Authorization.kt @@ -0,0 +1,33 @@ +package no.nav.paw.bekreftelse.api.utils + +import no.nav.paw.security.authentication.model.Bruker +import no.nav.paw.security.authentication.model.Identitetsnummer +import no.nav.paw.security.authentication.model.M2MToken +import no.nav.paw.security.authentication.model.NavAnsatt +import no.nav.paw.security.authentication.model.Sluttbruker +import no.nav.paw.security.authentication.model.asIdentitetsnummer +import no.nav.paw.security.authorization.exception.IngenTilgangException + +fun Bruker<*>.hentSluttbrukerIdentitet(): Identitetsnummer { + return when (this) { + is Sluttbruker -> ident + else -> throw IngenTilgangException("Endepunkt kan kun benyttes av sluttbruker") + } +} + +fun Bruker<*>.hentSluttbrukerIdentitet(identitetsnummer: String?): Identitetsnummer { + return when (this) { + is Sluttbruker -> { + identitetsnummer?.let { if (ident.verdi != it) throw IngenTilgangException("Bruker har ikke tilgang til sluttbrukers informasjon") } + ident + } + + is NavAnsatt -> identitetsnummer?.asIdentitetsnummer() + ?: throw IngenTilgangException("Veileder må sende med identitetsnummer for sluttbruker") + + is M2MToken -> identitetsnummer?.asIdentitetsnummer() + ?: throw IngenTilgangException("M2M må sende med identitetsnummer for sluttbruker") + + else -> throw IngenTilgangException("Endepunkt kan ikke benyttes av ukjent brukergruppe") + } +} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Logger.kt b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Logging.kt similarity index 87% rename from apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Logger.kt rename to apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Logging.kt index dfcfff6c..ca5d0c09 100644 --- a/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Logger.kt +++ b/apps/bekreftelse-api/src/main/kotlin/no/nav/paw/bekreftelse/api/utils/Logging.kt @@ -3,7 +3,6 @@ package no.nav.paw.bekreftelse.api.utils import no.nav.common.audit_log.cef.CefMessage import no.nav.common.audit_log.cef.CefMessageEvent import no.nav.common.audit_log.cef.CefMessageSeverity -import no.nav.paw.bekreftelse.api.model.NavAnsatt import no.nav.paw.config.env.RuntimeEnvironment import no.nav.paw.config.env.appNameOrDefaultForLocal import no.nav.poao_tilgang.client.TilgangType @@ -18,8 +17,8 @@ inline val buildAuditLogger: Logger get() = LoggerFactory.getLogger("AuditLogger fun Logger.audit( runtimeEnvironment: RuntimeEnvironment, - identitetsnummer: String, - navAnsatt: NavAnsatt, + aktorIdent: String, + sluttbrukerIdent: String, tilgangType: TilgangType, melding: String, ) { @@ -28,8 +27,8 @@ fun Logger.audit( .event(if (tilgangType == TilgangType.LESE) CefMessageEvent.ACCESS else CefMessageEvent.UPDATE) .name("Sporingslogg") .severity(CefMessageSeverity.INFO) - .sourceUserId(navAnsatt.navIdent) - .destinationUserId(identitetsnummer) + .sourceUserId(aktorIdent) + .destinationUserId(sluttbrukerIdent) .timeEnded(System.currentTimeMillis()) .extension("msg", melding) .build() diff --git a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/TestDataGenerator.kt b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/TestDataGenerator.kt deleted file mode 100644 index 8611911b..00000000 --- a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/TestDataGenerator.kt +++ /dev/null @@ -1,132 +0,0 @@ -package no.nav.paw.bekreftelse.api - -import io.mockk.mockk -import kotlinx.coroutines.Deferred -import no.nav.paw.bekreftelse.api.model.BekreftelseRow -import no.nav.paw.bekreftelse.api.model.MottaBekreftelseRequest -import no.nav.paw.bekreftelse.api.model.TilgjengeligBekreftelse -import no.nav.paw.bekreftelse.internehendelser.BekreftelseMeldingMottatt -import no.nav.paw.bekreftelse.internehendelser.BekreftelseTilgjengelig -import org.apache.kafka.clients.producer.RecordMetadata -import java.time.Duration -import java.time.Instant -import java.util.* -import java.util.concurrent.Future - -class TestDataGenerator { - - val fnr1 = "01017012345" - val fnr2 = "02017012345" - val fnr3 = "03017012345" - val arbeidssoekerId1 = 10001L - val arbeidssoekerId2 = 10002L - val arbeidssoekerId3 = 10003L - val kafkaKey1 = -10001L - val kafkaKey2 = -10002L - val kafkaKey3 = -10003L - val periodeId1 = UUID.fromString("4c0cb50a-3b4a-45df-b5b6-2cb45f04d19b") - val periodeId2 = UUID.fromString("0fc3de47-a6cd-4ad5-8433-53235738200d") - val bekreftelseId1 = UUID.fromString("0cd73e66-e5a2-4dae-88de-2dd89a910a19") - val bekreftelseId2 = UUID.fromString("7b769364-4d48-40f8-ac64-4489bb8080dd") - val bekreftelseId3 = UUID.fromString("b6e3b543-da44-4524-860f-9474bd6d505e") - val hendelseId1 = UUID.fromString("d69695e0-4249-4756-b0ef-02979ac66fea") - val hendelseId2 = UUID.fromString("9830a768-553c-4e11-b1f8-165b4e499be7") - - fun nyBekreftelseRow( - version: Int = 1, - partition: Int = 1, - offset: Long = 1, - recordKey: Long = kafkaKey1, - arbeidssoekerId: Long = arbeidssoekerId1, - periodeId: UUID = periodeId1, - bekreftelseId: UUID = bekreftelseId1, - data: BekreftelseTilgjengelig = nyBekreftelseTilgjengelig( - arbeidssoekerId = arbeidssoekerId, - periodeId = periodeId, - bekreftelseId = bekreftelseId, - ) - ) = BekreftelseRow( - version = version, - partition = partition, - offset = offset, - recordKey = recordKey, - arbeidssoekerId = arbeidssoekerId, - periodeId = periodeId, - bekreftelseId = bekreftelseId, - data = data - ) - - fun nyBekreftelseRows( - arbeidssoekerId: Long = arbeidssoekerId1, - periodeId: UUID = periodeId1, - bekreftelseRow: List> - ): List { - return bekreftelseRow.mapIndexed { index, value -> - nyBekreftelseRow( - offset = value.first, - arbeidssoekerId = arbeidssoekerId, - periodeId = periodeId, - bekreftelseId = value.second - ) - } - } - - fun nyTilgjengeligBekreftelse( - periodeId: UUID = periodeId1, - bekreftelseId: UUID = bekreftelseId1, - gjelderFra: Instant = Instant.now(), - gjelderTil: Instant = Instant.now().plus(Duration.ofDays(14)), - ) = TilgjengeligBekreftelse( - periodeId = periodeId, - bekreftelseId = bekreftelseId, - gjelderFra = gjelderFra, - gjelderTil = gjelderTil - ) - - fun nyBekreftelseTilgjengelig( - hendelseId: UUID = hendelseId1, - periodeId: UUID = periodeId1, - arbeidssoekerId: Long = arbeidssoekerId1, - bekreftelseId: UUID = bekreftelseId1, - gjelderFra: Instant = Instant.now(), - gjelderTil: Instant = Instant.now().plus(Duration.ofDays(14)), - hendelseTidspunkt: Instant = Instant.now() - ) = BekreftelseTilgjengelig( - hendelseId = hendelseId, - periodeId = periodeId, - arbeidssoekerId = arbeidssoekerId, - hendelseTidspunkt = hendelseTidspunkt, - bekreftelseId = bekreftelseId, - gjelderFra = gjelderFra, - gjelderTil = gjelderTil - ) - - fun nyBekreftelseRequest( - identitetsnummer: String? = null, - bekreftelseId: UUID = bekreftelseId1, - harJobbetIDennePerioden: Boolean = false, - vilFortsetteSomArbeidssoeker: Boolean = true - ) = MottaBekreftelseRequest( - identitetsnummer = identitetsnummer, - bekreftelseId = bekreftelseId, - harJobbetIDennePerioden = harJobbetIDennePerioden, - vilFortsetteSomArbeidssoeker = vilFortsetteSomArbeidssoeker - ) - - fun nyProducerFuture() = mockk>() - fun nyProducerDeferred() = mockk>() - - fun nyBekreftelseMeldingMottatt( - hendelseId: UUID = hendelseId1, - periodeId: UUID = periodeId1, - arbeidssoekerId: Long = arbeidssoekerId1, - hendelseTidspunkt: Instant = Instant.now(), - bekreftelseId: UUID = bekreftelseId1 - ) = BekreftelseMeldingMottatt( - hendelseId = hendelseId, - periodeId = periodeId, - arbeidssoekerId = arbeidssoekerId, - hendelseTidspunkt = hendelseTidspunkt, - bekreftelseId = bekreftelseId, - ) -} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/plugin/TestDataPlugin.kt b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/plugin/TestDataPlugin.kt index c6c5cb4c..a3619ab8 100644 --- a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/plugin/TestDataPlugin.kt +++ b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/plugin/TestDataPlugin.kt @@ -10,12 +10,9 @@ import io.ktor.util.KtorDsl import no.nav.paw.bekreftelse.api.model.BekreftelseRow import no.nav.paw.bekreftelse.api.plugins.custom.FlywayMigrationCompleted import no.nav.paw.bekreftelse.api.repository.BekreftelseRepository +import no.nav.paw.bekreftelse.api.test.TestData import org.jetbrains.exposed.sql.transactions.transaction -data class TestData( - val bereftelseRows: List = listOf() -) - @KtorDsl class TestDataPluginConfig { var testData: TestData? = null diff --git a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/routes/AuthRoutesTest.kt b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/routes/AuthRoutesTest.kt deleted file mode 100644 index 7f4e9961..00000000 --- a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/routes/AuthRoutesTest.kt +++ /dev/null @@ -1,316 +0,0 @@ -package no.nav.paw.bekreftelse.api.routes - -import io.kotest.core.spec.style.FreeSpec -import io.kotest.matchers.shouldBe -import io.ktor.client.call.body -import io.ktor.client.request.bearerAuth -import io.ktor.client.request.get -import io.ktor.client.request.headers -import io.ktor.client.request.post -import io.ktor.client.request.setBody -import io.ktor.client.statement.bodyAsText -import io.ktor.http.ContentType -import io.ktor.http.HttpHeaders -import io.ktor.http.HttpStatusCode -import io.ktor.http.append -import io.ktor.server.testing.testApplication -import io.mockk.clearAllMocks -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.confirmVerified -import io.mockk.every -import io.mockk.verify -import no.nav.paw.bekreftelse.api.ApplicationTestContext -import no.nav.paw.bekreftelse.api.model.TilgjengeligeBekreftelserRequest -import no.nav.paw.error.model.ProblemDetails -import no.nav.paw.kafkakeygenerator.client.KafkaKeysResponse -import no.nav.poao_tilgang.client.Decision -import no.nav.poao_tilgang.client.NavAnsattTilgangTilEksternBrukerPolicyInput -import no.nav.poao_tilgang.client.api.ApiResult - -class AuthRoutesTest : FreeSpec({ - with(ApplicationTestContext()) { - - beforeSpec { - clearAllMocks() - mockOAuth2Server.start() - } - - afterSpec { - mockOAuth2Server.shutdown() - confirmVerified( - kafkaKeysClientMock, - poaoTilgangClientMock - ) - } - - /* - * GENERELLE TESTER - */ - "Test suite for generell autentisering og autorisering" - { - - "Skal få 401 ved manglende Bearer Token" { - testApplication { - configureSimpleTestApplication(bekreftelseServiceMock) - val client = configureTestClient() - - val response = client.get("/api/secured") - - response.status shouldBe HttpStatusCode.Unauthorized - } - } - - "Skal få 401 ved token utstedt av ukjent issuer" { - testApplication { - configureSimpleTestApplication(bekreftelseServiceMock) - val client = configureTestClient() - - val token = mockOAuth2Server.issueToken( - issuerId = "whatever", - claims = mapOf( - "acr" to "idporten-loa-high", - "pid" to "01017012345" - ) - ) - - val response = client.get("/api/secured") { - bearerAuth(token.serialize()) - } - - response.status shouldBe HttpStatusCode.Unauthorized - } - } - - "Skal få 403 ved token uten noen claims" { - testApplication { - configureSimpleTestApplication(bekreftelseServiceMock) - val client = configureTestClient() - - val token = mockOAuth2Server.issueToken() - - val response = client.get("/api/secured") { - bearerAuth(token.serialize()) - } - - response.status shouldBe HttpStatusCode.Forbidden - val body = response.body() - body.status shouldBe HttpStatusCode.Forbidden - body.code shouldBe "PAW_UGYLDIG_BEARER_TOKEN" - } - } - } - - /* - * SLUTTBRUKER TESTER - */ - "Test suite for sluttbruker autentisering og autorisering" - { - - "Skal få 403 ved TokenX-token uten pid claim" { - testApplication { - configureSimpleTestApplication(bekreftelseServiceMock) - val client = configureTestClient() - - val token = mockOAuth2Server.issueToken( - claims = mapOf( - "acr" to "idporten-loa-high" - ) - ) - - val response = client.get("/api/secured") { - bearerAuth(token.serialize()) - } - - response.status shouldBe HttpStatusCode.Forbidden - val body = response.body() - body.status shouldBe HttpStatusCode.Forbidden - body.code shouldBe "PAW_UGYLDIG_BEARER_TOKEN" - } - } - - "Skal få 403 ved TokenX-token når innsendt ident ikke er lik pid claim" { - testApplication { - configureSimpleTestApplication(bekreftelseServiceMock) - val client = configureTestClient() - - val token = mockOAuth2Server.issueToken( - claims = mapOf( - "acr" to "idporten-loa-high", - "pid" to "01017012345" - ) - ) - - val response = client.post("/api/secured") { - bearerAuth(token.serialize()) - headers { - append(HttpHeaders.ContentType, ContentType.Application.Json) - } - setBody(TilgjengeligeBekreftelserRequest("02017012345")) - } - - response.status shouldBe HttpStatusCode.Forbidden - val body = response.body() - body.status shouldBe HttpStatusCode.Forbidden - body.code shouldBe "PAW_BRUKER_HAR_IKKE_TILGANG" - } - } - - "Skal få 200 ved TokenX-token når innsendt ident er lik pid claim" { - coEvery { kafkaKeysClientMock.getIdAndKey(any()) } returns KafkaKeysResponse(1, 1) - - testApplication { - configureSimpleTestApplication(bekreftelseServiceMock) - val client = configureTestClient() - - val token = mockOAuth2Server.issueToken( - claims = mapOf( - "acr" to "idporten-loa-high", - "pid" to "01017012345" - ) - ) - - val response = client.post("/api/secured") { - bearerAuth(token.serialize()) - headers { - append(HttpHeaders.ContentType, ContentType.Application.Json) - } - setBody(TilgjengeligeBekreftelserRequest("01017012345")) - } - - response.status shouldBe HttpStatusCode.OK - val body = response.bodyAsText() - body shouldBe "WHATEVER" - - coVerify { kafkaKeysClientMock.getIdAndKey(any()) } - } - } - } - - - /* - * VEILEDER TESTER - */ - "Test suite for veilder autentisering og autorisering" - { - - "Skal få 403 ved Azure-token men GET-request" { - testApplication { - configureSimpleTestApplication(bekreftelseServiceMock) - val client = configureTestClient() - - val token = mockOAuth2Server.issueToken( - claims = mapOf( - "oid" to "a6a2e743-acc1-4af0-b89e-d5980500fc2a", - "NAVident" to "12345" - ) - ) - - val response = client.get("/api/secured") { - bearerAuth(token.serialize()) - } - - response.status shouldBe HttpStatusCode.Forbidden - val body = response.body() - body.status shouldBe HttpStatusCode.Forbidden - body.code shouldBe "PAW_BRUKER_HAR_IKKE_TILGANG" - } - } - - "Skal få 403 ved Azure-token med POST-request uten ident" { - testApplication { - configureSimpleTestApplication(bekreftelseServiceMock) - val client = configureTestClient() - - val token = mockOAuth2Server.issueToken( - claims = mapOf( - "oid" to "a6a2e743-acc1-4af0-b89e-d5980500fc2a", - "NAVident" to "12345" - ) - ) - - val response = client.post("/api/secured") { - bearerAuth(token.serialize()) - headers { - append(HttpHeaders.ContentType, ContentType.Application.Json) - } - setBody(TilgjengeligeBekreftelserRequest()) - } - - response.status shouldBe HttpStatusCode.Forbidden - val body = response.body() - body.status shouldBe HttpStatusCode.Forbidden - body.code shouldBe "PAW_BRUKER_HAR_IKKE_TILGANG" - } - } - - "Skal få 403 ved Azure-token med POST-request men uten POAO tilgang" { - coEvery { kafkaKeysClientMock.getIdAndKey(any()) } returns KafkaKeysResponse(1, 1) - every { poaoTilgangClientMock.evaluatePolicy(any()) } returns ApiResult( - throwable = null, - result = Decision.Deny("You shall not pass!", "Balrogs suck") - ) - - testApplication { - configureSimpleTestApplication(bekreftelseServiceMock) - val client = configureTestClient() - - val token = mockOAuth2Server.issueToken( - claims = mapOf( - "oid" to "a6a2e743-acc1-4af0-b89e-d5980500fc2a", - "NAVident" to "12345" - ) - ) - - val response = client.post("/api/secured") { - bearerAuth(token.serialize()) - headers { - append(HttpHeaders.ContentType, ContentType.Application.Json) - } - setBody(TilgjengeligeBekreftelserRequest("01017012345")) - } - - response.status shouldBe HttpStatusCode.Forbidden - val body = response.body() - body.status shouldBe HttpStatusCode.Forbidden - body.code shouldBe "PAW_BRUKER_HAR_IKKE_TILGANG" - - coVerify { kafkaKeysClientMock.getIdAndKey(any()) } - verify { poaoTilgangClientMock.evaluatePolicy(any()) } - } - } - - "Skal få 200 ved Azure-token med POST-request og med POAO tilgang" { - coEvery { kafkaKeysClientMock.getIdAndKey(any()) } returns KafkaKeysResponse(1, 1) - every { poaoTilgangClientMock.evaluatePolicy(any()) } returns ApiResult( - throwable = null, - result = Decision.Permit - ) - - testApplication { - configureSimpleTestApplication(bekreftelseServiceMock) - val client = configureTestClient() - - val token = mockOAuth2Server.issueToken( - claims = mapOf( - "oid" to "a6a2e743-acc1-4af0-b89e-d5980500fc2a", - "NAVident" to "12345" - ) - ) - - val response = client.post("/api/secured") { - bearerAuth(token.serialize()) - headers { - append(HttpHeaders.ContentType, ContentType.Application.Json) - } - setBody(TilgjengeligeBekreftelserRequest("01017012345")) - } - - response.status shouldBe HttpStatusCode.OK - val body = response.bodyAsText() - body shouldBe "WHATEVER" - - coVerify { kafkaKeysClientMock.getIdAndKey(any()) } - verify { poaoTilgangClientMock.evaluatePolicy(any()) } - } - } - } - } -}) \ No newline at end of file diff --git a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/routes/BekreftelseRoutesTest.kt b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/routes/BekreftelseRoutesTest.kt index 046dde78..97999fd8 100644 --- a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/routes/BekreftelseRoutesTest.kt +++ b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/routes/BekreftelseRoutesTest.kt @@ -5,13 +5,8 @@ import io.kotest.matchers.shouldBe import io.ktor.client.call.body import io.ktor.client.request.bearerAuth import io.ktor.client.request.get -import io.ktor.client.request.headers import io.ktor.client.request.post -import io.ktor.client.request.setBody -import io.ktor.http.ContentType -import io.ktor.http.HttpHeaders import io.ktor.http.HttpStatusCode -import io.ktor.http.append import io.ktor.server.testing.testApplication import io.mockk.clearAllMocks import io.mockk.coEvery @@ -21,11 +16,19 @@ import io.mockk.every import io.mockk.just import io.mockk.runs import io.mockk.verify -import no.nav.paw.bekreftelse.api.ApplicationTestContext import no.nav.paw.bekreftelse.api.model.TilgjengeligBekreftelserResponse -import no.nav.paw.bekreftelse.api.plugin.TestData +import no.nav.paw.bekreftelse.api.model.TilgjengeligeBekreftelserRequest +import no.nav.paw.bekreftelse.api.test.ApplicationTestContext +import no.nav.paw.bekreftelse.api.test.TestData +import no.nav.paw.bekreftelse.api.test.issueAzureToken +import no.nav.paw.bekreftelse.api.test.issueTokenXToken +import no.nav.paw.bekreftelse.api.test.setJsonBody import no.nav.paw.bekreftelse.melding.v1.Bekreftelse +import no.nav.paw.error.model.ProblemDetails import no.nav.paw.kafkakeygenerator.client.KafkaKeysResponse +import no.nav.poao_tilgang.client.Decision +import no.nav.poao_tilgang.client.NavAnsattTilgangTilEksternBrukerPolicyInput +import no.nav.poao_tilgang.client.api.ApiResult class BekreftelseRoutesTest : FreeSpec({ with(ApplicationTestContext()) { @@ -35,7 +38,6 @@ class BekreftelseRoutesTest : FreeSpec({ } afterSpec { - coVerify { kafkaKeysClientMock.getIdAndKey(any()) } confirmVerified( kafkaKeysClientMock, poaoTilgangClientMock, @@ -48,35 +50,128 @@ class BekreftelseRoutesTest : FreeSpec({ } /* - * SLUTTBRUKER TESTER - */ - "Test suite for sluttbruker" - { - "Skal hente tilgjengelige bekreftelser" { - coEvery { kafkaKeysClientMock.getIdAndKey(any()) } returns KafkaKeysResponse( - testData.arbeidssoekerId1, - testData.kafkaKey1 - ) + * FELLES TESTER + */ + "Test suite for felleslogikk" - { + "Skal få 401 ved manglende Bearer Token" { + testApplication { + configureTestApplication(bekreftelseServiceMock) + val client = configureTestClient() + + val response = client.get("/api/v1/tilgjengelige-bekreftelser") + response.status shouldBe HttpStatusCode.Unauthorized + } + } + + "Skal få 401 ved token utstedt av ukjent issuer" { testApplication { - configureCompleteTestApplication( - bekreftelseService, TestData( - bereftelseRows = testData.nyBekreftelseRows( - arbeidssoekerId = testData.arbeidssoekerId1, - periodeId = testData.periodeId1, - bekreftelseRow = listOf( - 1L to testData.bekreftelseId1, 2L to testData.bekreftelseId2 - ) - ) + configureTestApplication(bekreftelseServiceMock) + val client = configureTestClient() + + val token = mockOAuth2Server.issueToken( + issuerId = "whatever", + claims = mapOf( + "acr" to "idporten-loa-high", + "pid" to TestData.fnr1 ) ) + + val response = client.get("/api/v1/tilgjengelige-bekreftelser") { + bearerAuth(token.serialize()) + } + + response.status shouldBe HttpStatusCode.Unauthorized + } + } + + "Skal få 403 ved token uten noen claims" { + testApplication { + configureTestApplication(bekreftelseServiceMock) + val client = configureTestClient() + + val token = mockOAuth2Server.issueToken() + + val response = client.get("/api/v1/tilgjengelige-bekreftelser") { + bearerAuth(token.serialize()) + } + + response.status shouldBe HttpStatusCode.Forbidden + val body = response.body() + body.status shouldBe HttpStatusCode.Forbidden + body.code shouldBe "PAW_UGYLDIG_BEARER_TOKEN" + } + } + } + + /* + * SLUTTBRUKER TESTER + */ + "Test suite for sluttbruker" - { + "Skal få 403 Forbidden ved TokenX-token uten pid claim" { + testApplication { + configureTestApplication(bekreftelseServiceMock) val client = configureTestClient() val token = mockOAuth2Server.issueToken( claims = mapOf( - "acr" to "idporten-loa-high", - "pid" to testData.fnr1 + "acr" to "idporten-loa-high" + ) + ) + + val response = client.get("/api/v1/tilgjengelige-bekreftelser") { + bearerAuth(token.serialize()) + } + + response.status shouldBe HttpStatusCode.Forbidden + val body = response.body() + body.status shouldBe HttpStatusCode.Forbidden + body.code shouldBe "PAW_UGYLDIG_BEARER_TOKEN" + } + } + + "Skal 403 Forbidden ved hente tilgjengelige bekreftelser med POST-request når innsendt fnr ikke er samme som token pid" { + val request = TestData.nyTilgjengeligeBekreftelserRequest(identitetsnummer = TestData.fnr2) + + testApplication { + configureTestApplication(bekreftelseService) + val client = configureTestClient() + + val token = mockOAuth2Server.issueTokenXToken(pid = TestData.fnr1) + + val response = client.post("/api/v1/tilgjengelige-bekreftelser") { + bearerAuth(token.serialize()) + setJsonBody(request) + } + + response.status shouldBe HttpStatusCode.Forbidden + val body = response.body() + body.status shouldBe HttpStatusCode.Forbidden + body.code shouldBe "PAW_INGEN_TILGANG" + } + } + + "Skal hente tilgjengelige bekreftelser med GET-request" { + coEvery { kafkaKeysClientMock.getIdAndKey(any()) } returns KafkaKeysResponse( + TestData.arbeidssoekerId1, + TestData.kafkaKey1 + ) + val opprettTestData = TestData( + bereftelseRows = TestData.nyBekreftelseRows( + arbeidssoekerId = TestData.arbeidssoekerId1, + periodeId = TestData.periodeId1, + bekreftelseRow = listOf( + TestData.kafkaOffset1 to TestData.bekreftelseId1, + TestData.kafkaOffset2 to TestData.bekreftelseId2 ) ) + ) + + testApplication { + configureTestApplication(bekreftelseService, opprettTestData) + val client = configureTestClient() + + val token = mockOAuth2Server.issueTokenXToken(pid = TestData.fnr1) val response = client.get("/api/v1/tilgjengelige-bekreftelser") { bearerAuth(token.serialize()) @@ -85,82 +180,288 @@ class BekreftelseRoutesTest : FreeSpec({ response.status shouldBe HttpStatusCode.OK val body = response.body() body.size shouldBe 2 + } + + coVerify { kafkaKeysClientMock.getIdAndKey(any()) } } - "Skal motta bekreftelse" { + "Skal hente tilgjengelige bekreftelser med POST-request" { coEvery { kafkaKeysClientMock.getIdAndKey(any()) } returns KafkaKeysResponse( - testData.arbeidssoekerId2, - testData.kafkaKey2 + TestData.arbeidssoekerId2, + TestData.kafkaKey2 ) - every { bekreftelseKafkaProducerMock.produceMessage(any(), any()) } just runs - val request = testData.nyBekreftelseRequest( - identitetsnummer = testData.fnr2, - bekreftelseId = testData.bekreftelseId3 + val opprettTestData = TestData( + bereftelseRows = TestData.nyBekreftelseRows( + arbeidssoekerId = TestData.arbeidssoekerId2, + periodeId = TestData.periodeId2, + bekreftelseRow = listOf( + TestData.kafkaOffset3 to TestData.bekreftelseId3, + TestData.kafkaOffset4 to TestData.bekreftelseId4 + ) + ) ) + val request = TestData.nyTilgjengeligeBekreftelserRequest(identitetsnummer = TestData.fnr2) testApplication { - configureCompleteTestApplication( - bekreftelseService, TestData( - bereftelseRows = testData.nyBekreftelseRows( - arbeidssoekerId = testData.arbeidssoekerId2, - periodeId = testData.periodeId2, - bekreftelseRow = listOf( - 3L to testData.bekreftelseId3 - ) - ) - ) - ) + configureTestApplication(bekreftelseService, opprettTestData) val client = configureTestClient() - val token = mockOAuth2Server.issueToken( - claims = mapOf( - "acr" to "idporten-loa-high", - "pid" to testData.fnr2 + val token = mockOAuth2Server.issueTokenXToken(pid = TestData.fnr2) + + val response = client.post("/api/v1/tilgjengelige-bekreftelser") { + bearerAuth(token.serialize()) + setJsonBody(request) + } + + response.status shouldBe HttpStatusCode.OK + val body = response.body() + body.size shouldBe 2 + + } + + coVerify { kafkaKeysClientMock.getIdAndKey(any()) } + } + + "Skal få 403 Forbidden ved motta bekreftelse når ident og pid er ulik" { + val request = TestData.nyBekreftelseRequest( + identitetsnummer = TestData.fnr4, + bekreftelseId = TestData.bekreftelseId5 + ) + + testApplication { + configureTestApplication(bekreftelseService) + val client = configureTestClient() + + val token = mockOAuth2Server.issueTokenXToken(pid = TestData.fnr3) + + val response = client.post("/api/v1/bekreftelse") { + bearerAuth(token.serialize()) + setJsonBody(request) + } + + response.status shouldBe HttpStatusCode.Forbidden + val body = response.body() + body.status shouldBe HttpStatusCode.Forbidden + body.code shouldBe "PAW_INGEN_TILGANG" + } + } + + "Skal motta bekreftelse" { + coEvery { kafkaKeysClientMock.getIdAndKey(any()) } returns KafkaKeysResponse( + TestData.arbeidssoekerId3, + TestData.kafkaKey3 + ) + every { bekreftelseKafkaProducerMock.produceMessage(any(), any()) } just runs + val opprettTestData = TestData( + bereftelseRows = TestData.nyBekreftelseRows( + arbeidssoekerId = TestData.arbeidssoekerId3, + periodeId = TestData.periodeId3, + bekreftelseRow = listOf( + TestData.kafkaOffset5 to TestData.bekreftelseId5 ) ) + ) + val request = TestData.nyBekreftelseRequest( + identitetsnummer = TestData.fnr3, + bekreftelseId = TestData.bekreftelseId5 + ) + + testApplication { + configureTestApplication(bekreftelseService, opprettTestData) + val client = configureTestClient() + + val token = mockOAuth2Server.issueTokenXToken(pid = TestData.fnr3) val response = client.post("/api/v1/bekreftelse") { bearerAuth(token.serialize()) - headers { - append(HttpHeaders.ContentType, ContentType.Application.Json) - } - setBody(request) + setJsonBody(request) } response.status shouldBe HttpStatusCode.OK - verify { bekreftelseKafkaProducerMock.produceMessage(any(), any()) } } + + coVerify { kafkaKeysClientMock.getIdAndKey(any()) } + verify { bekreftelseKafkaProducerMock.produceMessage(any(), any()) } } "Skal motta bekreftelse men finner ikke relatert tilgjengelig bekreftelse" { coEvery { kafkaKeysClientMock.getIdAndKey(any()) } returns KafkaKeysResponse( - testData.arbeidssoekerId3, - testData.kafkaKey3 + TestData.arbeidssoekerId4, + TestData.kafkaKey4 ) - val request = testData.nyBekreftelseRequest(identitetsnummer = testData.fnr3) + val request = TestData.nyBekreftelseRequest(identitetsnummer = TestData.fnr4) testApplication { - configureCompleteTestApplication(bekreftelseService) + configureTestApplication(bekreftelseService) val client = configureTestClient() - val token = mockOAuth2Server.issueToken( - claims = mapOf( - "acr" to "idporten-loa-high", - "pid" to testData.fnr3 + val token = mockOAuth2Server.issueTokenXToken(pid = TestData.fnr4) + + val response = client.post("/api/v1/bekreftelse") { + bearerAuth(token.serialize()) + setJsonBody(request) + } + + response.status shouldBe HttpStatusCode.BadRequest + } + + coVerify { kafkaKeysClientMock.getIdAndKey(any()) } + } + } + + /* + * VEILEDER TESTER + */ + "Test suite for veildere" - { + + "Skal få 403 Forbidden ved GET-request" { + testApplication { + configureTestApplication(bekreftelseServiceMock) + val client = configureTestClient() + + val token = mockOAuth2Server.issueAzureToken() + + val response = client.get("/api/v1/tilgjengelige-bekreftelser") { + bearerAuth(token.serialize()) + } + + response.status shouldBe HttpStatusCode.Forbidden + val body = response.body() + body.status shouldBe HttpStatusCode.Forbidden + body.code shouldBe "PAW_INGEN_TILGANG" + } + } + + "Skal få 403 Forbidden ved POST-request uten ident" { + testApplication { + configureTestApplication(bekreftelseServiceMock) + val client = configureTestClient() + + val token = mockOAuth2Server.issueAzureToken() + + val response = client.post("/api/v1/tilgjengelige-bekreftelser") { + bearerAuth(token.serialize()) + setJsonBody(TilgjengeligeBekreftelserRequest()) + } + + response.status shouldBe HttpStatusCode.Forbidden + val body = response.body() + body.status shouldBe HttpStatusCode.Forbidden + body.code shouldBe "PAW_INGEN_TILGANG" + } + } + + "Skal få 403 Forbidden ved POST-request men uten POAO tilgang" { + coEvery { kafkaKeysClientMock.getIdAndKey(any()) } returns KafkaKeysResponse(1, 1) + every { poaoTilgangClientMock.evaluatePolicy(any()) } returns ApiResult( + throwable = null, + result = Decision.Deny("You shall not pass!", "Balrogs suck") + ) + + testApplication { + configureTestApplication(bekreftelseServiceMock) + val client = configureTestClient() + + val token = mockOAuth2Server.issueAzureToken() + + val response = client.post("/api/v1/tilgjengelige-bekreftelser") { + bearerAuth(token.serialize()) + setJsonBody(TilgjengeligeBekreftelserRequest("01017012345")) + } + + response.status shouldBe HttpStatusCode.Forbidden + val body = response.body() + body.status shouldBe HttpStatusCode.Forbidden + body.code shouldBe "PAW_INGEN_TILGANG" + } + + coVerify { kafkaKeysClientMock.getIdAndKey(any()) } + verify { poaoTilgangClientMock.evaluatePolicy(any()) } + } + + "Skal hente tilgjengelige bekreftelser" { + coEvery { kafkaKeysClientMock.getIdAndKey(any()) } returns KafkaKeysResponse( + TestData.arbeidssoekerId5, + TestData.kafkaKey5 + ) + every { poaoTilgangClientMock.evaluatePolicy(any()) } returns ApiResult( + throwable = null, + result = Decision.Permit + ) + val opprettTestData = TestData( + bereftelseRows = TestData.nyBekreftelseRows( + arbeidssoekerId = TestData.arbeidssoekerId5, + periodeId = TestData.periodeId4, + bekreftelseRow = listOf( + TestData.kafkaOffset6 to TestData.bekreftelseId6, + TestData.kafkaOffset7 to TestData.bekreftelseId7 + ) + ) + ) + val request = TestData.nyTilgjengeligeBekreftelserRequest(identitetsnummer = TestData.fnr4) + + testApplication { + configureTestApplication(bekreftelseService, opprettTestData) + val client = configureTestClient() + + val token = mockOAuth2Server.issueAzureToken() + + val response = client.post("/api/v1/tilgjengelige-bekreftelser") { + bearerAuth(token.serialize()) + setJsonBody(request) + } + + response.status shouldBe HttpStatusCode.OK + val body = response.body() + body.size shouldBe 2 + } + + coVerify { kafkaKeysClientMock.getIdAndKey(any()) } + verify { poaoTilgangClientMock.evaluatePolicy(any()) } + } + + "Skal motta bekreftelse" { + coEvery { kafkaKeysClientMock.getIdAndKey(any()) } returns KafkaKeysResponse( + TestData.arbeidssoekerId6, + TestData.kafkaKey6 + ) + every { poaoTilgangClientMock.evaluatePolicy(any()) } returns ApiResult( + throwable = null, + result = Decision.Permit + ) + every { bekreftelseKafkaProducerMock.produceMessage(any(), any()) } just runs + val opprettTestData = TestData( + bereftelseRows = TestData.nyBekreftelseRows( + arbeidssoekerId = TestData.arbeidssoekerId6, + periodeId = TestData.periodeId5, + bekreftelseRow = listOf( + TestData.kafkaOffset8 to TestData.bekreftelseId8 ) ) + ) + val request = TestData.nyBekreftelseRequest( + identitetsnummer = TestData.fnr5, + bekreftelseId = TestData.bekreftelseId8 + ) + + testApplication { + configureTestApplication(bekreftelseService, opprettTestData) + val client = configureTestClient() + + val token = mockOAuth2Server.issueAzureToken() val response = client.post("/api/v1/bekreftelse") { bearerAuth(token.serialize()) - headers { - append(HttpHeaders.ContentType, ContentType.Application.Json) - } - setBody(request) + setJsonBody(request) } - response.status shouldBe HttpStatusCode.BadRequest + response.status shouldBe HttpStatusCode.OK } + + coVerify { kafkaKeysClientMock.getIdAndKey(any()) } + verify { poaoTilgangClientMock.evaluatePolicy(any()) } + verify { bekreftelseKafkaProducerMock.produceMessage(any(), any()) } } } } diff --git a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/ApplicationTestContext.kt b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/ApplicationTestContext.kt similarity index 75% rename from apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/ApplicationTestContext.kt rename to apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/ApplicationTestContext.kt index 95a6d1a0..9998014e 100644 --- a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/ApplicationTestContext.kt +++ b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/ApplicationTestContext.kt @@ -1,15 +1,8 @@ -package no.nav.paw.bekreftelse.api +package no.nav.paw.bekreftelse.api.test import io.ktor.client.HttpClient import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.serialization.jackson.jackson -import io.ktor.server.application.call -import io.ktor.server.auth.authenticate -import io.ktor.server.response.respond -import io.ktor.server.routing.Route -import io.ktor.server.routing.get -import io.ktor.server.routing.post -import io.ktor.server.routing.route import io.ktor.server.routing.routing import io.ktor.server.testing.ApplicationTestBuilder import io.micrometer.prometheusmetrics.PrometheusConfig @@ -22,13 +15,7 @@ import no.nav.paw.bekreftelse.api.config.AuthProviderClaims import no.nav.paw.bekreftelse.api.config.SERVER_CONFIG_FILE_NAME import no.nav.paw.bekreftelse.api.config.ServerConfig import no.nav.paw.bekreftelse.api.context.ApplicationContext -import no.nav.paw.bekreftelse.api.context.resolveRequest import no.nav.paw.bekreftelse.api.handler.KafkaConsumerExceptionHandler -import no.nav.paw.bekreftelse.api.model.Azure -import no.nav.paw.bekreftelse.api.model.IdPorten -import no.nav.paw.bekreftelse.api.model.TilgjengeligeBekreftelserRequest -import no.nav.paw.bekreftelse.api.model.TokenX -import no.nav.paw.bekreftelse.api.plugin.TestData import no.nav.paw.bekreftelse.api.plugin.configTestDataPlugin import no.nav.paw.bekreftelse.api.plugins.configureAuthentication import no.nav.paw.bekreftelse.api.plugins.configureDatabase @@ -48,8 +35,10 @@ import no.nav.paw.health.model.LivenessHealthIndicator import no.nav.paw.health.model.ReadinessHealthIndicator import no.nav.paw.health.repository.HealthIndicatorRepository import no.nav.paw.kafkakeygenerator.client.KafkaKeysClient +import no.nav.paw.security.authentication.token.AzureAd +import no.nav.paw.security.authentication.token.IdPorten +import no.nav.paw.security.authentication.token.TokenX import no.nav.poao_tilgang.client.PoaoTilgangClient -import no.nav.poao_tilgang.client.TilgangType import no.nav.security.mock.oauth2.MockOAuth2Server import org.apache.kafka.clients.consumer.KafkaConsumer import org.apache.kafka.clients.producer.Producer @@ -59,7 +48,6 @@ import javax.sql.DataSource class ApplicationTestContext { - val testData = TestDataGenerator() val serverConfig = loadNaisOrLocalConfiguration(SERVER_CONFIG_FILE_NAME) val applicationConfig = loadNaisOrLocalConfiguration(APPLICATION_CONFIG_FILE_NAME) val dataSource = createTestDataSource() @@ -73,18 +61,14 @@ class ApplicationTestContext { LivenessHealthIndicator(), ReadinessHealthIndicator() ) - val authorizationService = AuthorizationService( - serverConfig, - applicationConfig, - kafkaKeysClientMock, - poaoTilgangClientMock - ) + val authorizationService = AuthorizationService(serverConfig, poaoTilgangClientMock) val bekreftelseRepository = BekreftelseRepository() val bekreftelseServiceMock = mockk() val bekreftelseService = BekreftelseService( serverConfig, applicationConfig, prometheusMeterRegistry, + kafkaKeysClientMock, bekreftelseKafkaProducerMock, bekreftelseRepository ) @@ -104,20 +88,7 @@ class ApplicationTestContext { bekreftelseService ) - fun ApplicationTestBuilder.configureSimpleTestApplication(bekreftelseService: BekreftelseService) { - val applicationContext = createApplicationContext(bekreftelseService) - - application { - configureHTTP(applicationContext) - configureAuthentication(applicationContext) - configureSerialization() - routing { - testRoutes() - } - } - } - - fun ApplicationTestBuilder.configureCompleteTestApplication( + fun ApplicationTestBuilder.configureTestApplication( bekreftelseService: BekreftelseService, testData: TestData = TestData() ) { @@ -145,25 +116,6 @@ class ApplicationTestContext { } } - private fun Route.testRoutes() { - route("/api/secured") { - authenticate(TokenX.name, Azure.name) { - get("/") { - authorizationService.authorize(resolveRequest(), TilgangType.LESE) - call.respond("WHATEVER") - } - - post("/") { request -> - authorizationService.authorize( - requestContext = resolveRequest(request.identitetsnummer), - tilgangType = TilgangType.SKRIVE - ) - call.respond("WHATEVER") - } - } - } - } - private fun MockOAuth2Server.createAuthProviders(): List { val wellKnownUrl = wellKnownUrl("default").toString() return listOf( @@ -182,7 +134,7 @@ class ApplicationTestContext { ) ), AuthProvider( - Azure.name, wellKnownUrl, "default", AuthProviderClaims( + AzureAd.name, wellKnownUrl, "default", AuthProviderClaims( listOf( "NAVident" ) diff --git a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/KafkaTestDataProducer.kt b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/KafkaTestDataProducer.kt similarity index 80% rename from apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/KafkaTestDataProducer.kt rename to apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/KafkaTestDataProducer.kt index 3668273e..e124a38a 100644 --- a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/KafkaTestDataProducer.kt +++ b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/KafkaTestDataProducer.kt @@ -1,4 +1,4 @@ -package no.nav.paw.bekreftelse.api +package no.nav.paw.bekreftelse.api.test import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -26,19 +26,13 @@ fun main() { valueSerializer = BekreftelseHendelseSerializer::class ) - val arbeidssoekerId = 1L - val periodeId = UUID.fromString("3e415602-b7b6-47d4-bbd7-efdda468ca20") - val bekreftelseId = UUID.randomUUID() - - val testData = TestDataGenerator() - val topic = applicationConfig.kafkaTopology.bekreftelseHendelsesloggTopic - val key = 1L - val value = testData.nyBekreftelseTilgjengelig( + val key = TestData.kafkaKey1 + val value = TestData.nyBekreftelseTilgjengelig( hendelseId = UUID.randomUUID(), - periodeId = periodeId, - arbeidssoekerId = arbeidssoekerId, - bekreftelseId = bekreftelseId, + periodeId = TestData.periodeId1, + arbeidssoekerId = TestData.arbeidssoekerId1, + bekreftelseId = TestData.bekreftelseId1, gjelderFra = Instant.now(), gjelderTil = Instant.now().plus(Duration.ofDays(14)), ) diff --git a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/TestData.kt b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/TestData.kt new file mode 100644 index 00000000..cd5d76a1 --- /dev/null +++ b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/TestData.kt @@ -0,0 +1,144 @@ +package no.nav.paw.bekreftelse.api.test + +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.headers +import io.ktor.client.request.setBody +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.append +import no.nav.paw.bekreftelse.api.model.BekreftelseRow +import no.nav.paw.bekreftelse.api.model.MottaBekreftelseRequest +import no.nav.paw.bekreftelse.api.model.TilgjengeligeBekreftelserRequest +import no.nav.paw.bekreftelse.internehendelser.BekreftelseTilgjengelig +import java.time.Duration +import java.time.Instant +import java.util.* + +inline fun HttpRequestBuilder.setJsonBody(body: T) { + headers { + append(HttpHeaders.ContentType, ContentType.Application.Json) + } + setBody(body) +} + +class TestData(val bereftelseRows: List = listOf()) { + + companion object { + val fnr1 = "01017012345" + val fnr2 = "02017012345" + val fnr3 = "03017012345" + val fnr4 = "04017012345" + val fnr5 = "05017012345" + val arbeidssoekerId1 = 10001L + val arbeidssoekerId2 = 10002L + val arbeidssoekerId3 = 10003L + val arbeidssoekerId4 = 10004L + val arbeidssoekerId5 = 10005L + val arbeidssoekerId6 = 10006L + val kafkaKey1 = -10001L + val kafkaKey2 = -10002L + val kafkaKey3 = -10003L + val kafkaKey4 = -10004L + val kafkaKey5 = -10005L + val kafkaKey6 = -10006L + val kafkaOffset1 = 1L + val kafkaOffset2 = 2L + val kafkaOffset3 = 3L + val kafkaOffset4 = 4L + val kafkaOffset5 = 5L + val kafkaOffset6 = 6L + val kafkaOffset7 = 7L + val kafkaOffset8 = 8L + val periodeId1 = UUID.fromString("4c0cb50a-3b4a-45df-b5b6-2cb45f04d19b") + val periodeId2 = UUID.fromString("0fc3de47-a6cd-4ad5-8433-53235738200d") + val periodeId3 = UUID.fromString("12cf8147-a76d-4b62-85d2-4792fea08995") + val periodeId4 = UUID.fromString("f6f2f98a-2f2b-401d-b837-6ad26e45d4bf") + val periodeId5 = UUID.fromString("f6384bc5-a0ec-4bdc-9262-f6ebf952269f") + val bekreftelseId1 = UUID.fromString("0cd73e66-e5a2-4dae-88de-2dd89a910a19") + val bekreftelseId2 = UUID.fromString("7b769364-4d48-40f8-ac64-4489bb8080dd") + val bekreftelseId3 = UUID.fromString("b6e3b543-da44-4524-860f-9474bd6d505e") + val bekreftelseId4 = UUID.fromString("a59581e6-c9be-4aec-b9f4-c635f1826c71") + val bekreftelseId5 = UUID.fromString("de94b7ab-360f-4e5f-9ad1-3dc572b3e6a5") + val bekreftelseId6 = UUID.fromString("095779b3-64c5-4609-afcd-7392bd33b2d0") + val bekreftelseId7 = UUID.fromString("92f92510-182b-4681-bd6f-9984b2c329a5") + val bekreftelseId8 = UUID.fromString("a1342750-73e9-43e6-93b1-739e99ee79cf") + val navIdent1 = "NAV1001" + val navIdent2 = "NAV1002" + val navIdent3 = "NAV1003" + + fun nyBekreftelseRow( + version: Int = 1, + partition: Int = 1, + offset: Long = 1, + recordKey: Long = kafkaKey1, + arbeidssoekerId: Long = arbeidssoekerId1, + periodeId: UUID = periodeId1, + bekreftelseId: UUID = bekreftelseId1, + data: BekreftelseTilgjengelig = nyBekreftelseTilgjengelig( + arbeidssoekerId = arbeidssoekerId, + periodeId = periodeId, + bekreftelseId = bekreftelseId, + ) + ) = BekreftelseRow( + version = version, + partition = partition, + offset = offset, + recordKey = recordKey, + arbeidssoekerId = arbeidssoekerId, + periodeId = periodeId, + bekreftelseId = bekreftelseId, + data = data + ) + + fun nyBekreftelseRows( + arbeidssoekerId: Long = arbeidssoekerId1, + periodeId: UUID = periodeId1, + bekreftelseRow: List> + ): List { + return bekreftelseRow.map { + nyBekreftelseRow( + offset = it.first, + arbeidssoekerId = arbeidssoekerId, + periodeId = periodeId, + bekreftelseId = it.second + ) + } + } + + fun nyBekreftelseTilgjengelig( + hendelseId: UUID = UUID.randomUUID(), + periodeId: UUID = periodeId1, + arbeidssoekerId: Long = arbeidssoekerId1, + bekreftelseId: UUID = bekreftelseId1, + gjelderFra: Instant = Instant.now(), + gjelderTil: Instant = Instant.now().plus(Duration.ofDays(14)), + hendelseTidspunkt: Instant = Instant.now() + ) = BekreftelseTilgjengelig( + hendelseId = hendelseId, + periodeId = periodeId, + arbeidssoekerId = arbeidssoekerId, + hendelseTidspunkt = hendelseTidspunkt, + bekreftelseId = bekreftelseId, + gjelderFra = gjelderFra, + gjelderTil = gjelderTil + ) + + fun nyTilgjengeligeBekreftelserRequest( + identitetsnummer: String? = null + ) = TilgjengeligeBekreftelserRequest( + identitetsnummer = identitetsnummer + ) + + fun nyBekreftelseRequest( + identitetsnummer: String? = null, + bekreftelseId: UUID = bekreftelseId1, + harJobbetIDennePerioden: Boolean = false, + vilFortsetteSomArbeidssoeker: Boolean = true + ) = MottaBekreftelseRequest( + identitetsnummer = identitetsnummer, + bekreftelseId = bekreftelseId, + harJobbetIDennePerioden = harJobbetIDennePerioden, + vilFortsetteSomArbeidssoeker = vilFortsetteSomArbeidssoeker + ) + } +} \ No newline at end of file diff --git a/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/TokenTestUtils.kt b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/TokenTestUtils.kt new file mode 100644 index 00000000..1b0607a5 --- /dev/null +++ b/apps/bekreftelse-api/src/test/kotlin/no/nav/paw/bekreftelse/api/test/TokenTestUtils.kt @@ -0,0 +1,43 @@ +package no.nav.paw.bekreftelse.api.test + +import com.nimbusds.jwt.SignedJWT +import no.nav.security.mock.oauth2.MockOAuth2Server +import java.util.* + +fun MockOAuth2Server.issueTokenXToken( + acr: String = "idporten-loa-high", + pid: String = TestData.fnr1 +): SignedJWT { + return issueToken( + claims = mapOf( + "acr" to acr, + "pid" to pid + ) + ) +} + +fun MockOAuth2Server.issueAzureToken( + oid: UUID = UUID.randomUUID(), + name: String = "Kari Nordmann", + navIdent: String = TestData.navIdent1 +): SignedJWT { + return issueToken( + claims = mapOf( + "oid" to oid.toString(), + "name" to name, + "NAVident" to navIdent + ) + ) +} + +fun MockOAuth2Server.issueAzureM2MToken( + oid: UUID = UUID.randomUUID(), + roles: List = listOf("access_as_application"), +): SignedJWT { + return issueToken( + claims = mapOf( + "oid" to oid.toString(), + "roles" to roles + ) + ) +} diff --git a/lib/security/src/main/kotlin/no/nav/paw/security/authentication/exception/IngenTilgangException.kt b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/exception/IngenTilgangException.kt similarity index 75% rename from lib/security/src/main/kotlin/no/nav/paw/security/authentication/exception/IngenTilgangException.kt rename to lib/security/src/main/kotlin/no/nav/paw/security/authorization/exception/IngenTilgangException.kt index 973c8fe0..01780351 100644 --- a/lib/security/src/main/kotlin/no/nav/paw/security/authentication/exception/IngenTilgangException.kt +++ b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/exception/IngenTilgangException.kt @@ -1,4 +1,4 @@ -package no.nav.paw.security.authentication.exception +package no.nav.paw.security.authorization.exception import no.nav.paw.error.exception.AuthorizationException diff --git a/lib/security/src/main/kotlin/no/nav/paw/security/authentication/exception/UgyldigBrukerException.kt b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/exception/UgyldigBrukerException.kt similarity index 75% rename from lib/security/src/main/kotlin/no/nav/paw/security/authentication/exception/UgyldigBrukerException.kt rename to lib/security/src/main/kotlin/no/nav/paw/security/authorization/exception/UgyldigBrukerException.kt index 119f7ab9..eb3c329b 100644 --- a/lib/security/src/main/kotlin/no/nav/paw/security/authentication/exception/UgyldigBrukerException.kt +++ b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/exception/UgyldigBrukerException.kt @@ -1,4 +1,4 @@ -package no.nav.paw.security.authentication.exception +package no.nav.paw.security.authorization.exception import no.nav.paw.error.exception.AuthorizationException diff --git a/lib/security/src/main/kotlin/no/nav/paw/security/authorization/interceptor/AuthorizationInterceptor.kt b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/interceptor/AuthorizationInterceptor.kt index af023103..f23023d0 100644 --- a/lib/security/src/main/kotlin/no/nav/paw/security/authorization/interceptor/AuthorizationInterceptor.kt +++ b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/interceptor/AuthorizationInterceptor.kt @@ -5,21 +5,22 @@ import io.ktor.util.pipeline.PipelineContext import no.nav.paw.security.authorization.context.AuthorizationContext import no.nav.paw.security.authorization.context.resolveRequestContext import no.nav.paw.security.authorization.context.resolveSecurityContext +import no.nav.paw.security.authorization.model.Action import no.nav.paw.security.authorization.policy.AccessPolicy -import no.nav.paw.security.authorization.policy.AccessPolicyEvaluator import org.slf4j.LoggerFactory private val logger = LoggerFactory.getLogger("no.nav.paw.logger.security.authorization") suspend fun PipelineContext.authorize( - policies: List = emptyList(), + action: Action, + accessPolicies: List = emptyList(), body: suspend PipelineContext.(AuthorizationContext) -> Unit ): PipelineContext { logger.debug("Kjører autorisasjon") val requestContext = resolveRequestContext() val securityContext = requestContext.resolveSecurityContext() val authorizationContext = AuthorizationContext(requestContext, securityContext) - AccessPolicyEvaluator(policies).checkAccess(authorizationContext) + accessPolicies.forEach { it.checkAccess(action, authorizationContext) } body(authorizationContext) return this } \ No newline at end of file diff --git a/lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessDecision.kt b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessDecision.kt index b45b3bc9..a59182de 100644 --- a/lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessDecision.kt +++ b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessDecision.kt @@ -1,7 +1,16 @@ package no.nav.paw.security.authorization.model -enum class AccessDecision { +enum class Decision { PERMIT, DENY, DEFER -} \ No newline at end of file +} + +sealed class AccessDecision( + val decision: Decision, + open val description: String +) + +data class Permit(override val description: String) : AccessDecision(Decision.PERMIT, description) +data class Deny(override val description: String) : AccessDecision(Decision.DENY, description) +data class Defer(override val description: String) : AccessDecision(Decision.DEFER, description) diff --git a/lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessOperation.kt b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessOperation.kt index f86f50d1..e52ee225 100644 --- a/lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessOperation.kt +++ b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessOperation.kt @@ -1,3 +1,3 @@ package no.nav.paw.security.authorization.model -enum class AccessOperation { READ, WRITE } \ No newline at end of file +enum class Action { READ, WRITE } \ No newline at end of file diff --git a/lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessResult.kt b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessResult.kt deleted file mode 100644 index 58774a10..00000000 --- a/lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessResult.kt +++ /dev/null @@ -1,6 +0,0 @@ -package no.nav.paw.security.authorization.model - -data class AccessResult( - val decision: AccessDecision, - val details: String -) \ No newline at end of file diff --git a/lib/security/src/main/kotlin/no/nav/paw/security/authorization/policy/AccessPolicy.kt b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/policy/AccessPolicy.kt index e1b31008..3f649907 100644 --- a/lib/security/src/main/kotlin/no/nav/paw/security/authorization/policy/AccessPolicy.kt +++ b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/policy/AccessPolicy.kt @@ -1,8 +1,18 @@ package no.nav.paw.security.authorization.policy import no.nav.paw.security.authorization.context.AuthorizationContext -import no.nav.paw.security.authorization.model.AccessResult +import no.nav.paw.security.authorization.exception.IngenTilgangException +import no.nav.paw.security.authorization.model.AccessDecision +import no.nav.paw.security.authorization.model.Action +import no.nav.paw.security.authorization.model.Decision interface AccessPolicy { - fun hasAccess(context: AuthorizationContext): AccessResult + fun hasAccess(action: Action, context: AuthorizationContext): AccessDecision + + fun checkAccess(action: Action, context: AuthorizationContext) { + val accessDecision = hasAccess(action, context) + if (accessDecision.decision == Decision.DENY) { + throw IngenTilgangException(accessDecision.description) + } + } } diff --git a/lib/security/src/main/kotlin/no/nav/paw/security/authorization/policy/AccessPolicyEvaluator.kt b/lib/security/src/main/kotlin/no/nav/paw/security/authorization/policy/AccessPolicyEvaluator.kt deleted file mode 100644 index 17d25e1c..00000000 --- a/lib/security/src/main/kotlin/no/nav/paw/security/authorization/policy/AccessPolicyEvaluator.kt +++ /dev/null @@ -1,21 +0,0 @@ -package no.nav.paw.security.authorization.policy - -import no.nav.paw.security.authentication.exception.IngenTilgangException -import no.nav.paw.security.authorization.context.AuthorizationContext -import no.nav.paw.security.authorization.model.AccessDecision -import org.slf4j.LoggerFactory - -class AccessPolicyEvaluator(private val policies: List) { - - private val logger = LoggerFactory.getLogger("no.nav.paw.logger.security.authorization") - - fun checkAccess(context: AuthorizationContext) { - logger.debug("Sjekker {} tilgangsregler", policies.size) - val results = policies.map { it.hasAccess(context) } - val deny = results.filter { it.decision == AccessDecision.DENY } - if (deny.isNotEmpty()) { - logger.debug("Evaluering av tilgangsregler resulterte i {} DENY decisions [{}]", deny.size, deny) - throw IngenTilgangException("Ingen tilgang") - } - } -} \ No newline at end of file diff --git a/lib/security/src/test/kotlin/no/nav/paw/security/authorization/policy/TestPolicies.kt b/lib/security/src/test/kotlin/no/nav/paw/security/authorization/policy/TestPolicies.kt index 2b232f65..0e68b7a5 100644 --- a/lib/security/src/test/kotlin/no/nav/paw/security/authorization/policy/TestPolicies.kt +++ b/lib/security/src/test/kotlin/no/nav/paw/security/authorization/policy/TestPolicies.kt @@ -2,18 +2,20 @@ package no.nav.paw.security.authorization.policy import no.nav.paw.security.authorization.context.AuthorizationContext import no.nav.paw.security.authorization.model.AccessDecision -import no.nav.paw.security.authorization.model.AccessResult +import no.nav.paw.security.authorization.model.Action +import no.nav.paw.security.authorization.model.Deny +import no.nav.paw.security.authorization.model.Permit class TestPermitPolicy : AccessPolicy { - override fun hasAccess(context: AuthorizationContext): AccessResult { - return AccessResult(AccessDecision.PERMIT, "FULL STEAM AHEAD!") + override fun hasAccess(action: Action, context: AuthorizationContext): AccessDecision { + return Permit("FULL STEAM AHEAD!") } } class TestDenyPolicy : AccessPolicy { - override fun hasAccess(context: AuthorizationContext): AccessResult { - return AccessResult(AccessDecision.DENY, "YOU SHALL NOT PASS!") + override fun hasAccess(action: Action, context: AuthorizationContext): AccessDecision { + return Deny("YOU SHALL NOT PASS!") } } \ No newline at end of file diff --git a/lib/security/src/test/kotlin/no/nav/paw/security/test/TestApplicationContext.kt b/lib/security/src/test/kotlin/no/nav/paw/security/test/TestApplicationContext.kt index ccc66401..369f13a1 100644 --- a/lib/security/src/test/kotlin/no/nav/paw/security/test/TestApplicationContext.kt +++ b/lib/security/src/test/kotlin/no/nav/paw/security/test/TestApplicationContext.kt @@ -20,6 +20,7 @@ import io.ktor.server.routing.routing import io.ktor.server.testing.ApplicationTestBuilder import no.nav.paw.error.handler.handleException import no.nav.paw.security.authorization.interceptor.authorize +import no.nav.paw.security.authorization.model.Action import no.nav.paw.security.authorization.policy.AccessPolicy import no.nav.security.mock.oauth2.MockOAuth2Server import no.nav.security.token.support.v2.IssuerConfig @@ -57,13 +58,13 @@ class TestApplicationContext { routing { authenticate("tokenx") { get("/api/dummy") { - authorize(policies) { + authorize(Action.READ, policies) { call.respond(TestResponse("All Quiet on the Western Front")) } } post("/api/dummy") { - authorize(policies) { + authorize(Action.WRITE, policies) { call.respond(TestResponse("All Quiet on the Western Front")) } }