Skip to content

Commit

Permalink
Lagt til noe tilgangsstyring for token, nytt GET-endepunkt for bekref…
Browse files Browse the repository at this point in the history
…telse-api
  • Loading branch information
naviktthomas committed Sep 18, 2024
1 parent 1a83c5e commit 789575f
Show file tree
Hide file tree
Showing 26 changed files with 390 additions and 248 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ fun Application.module(
configureLogging()
configureSerialization()
configureTracing()
if (!applicationConfig.brukMock) { // TODO Bruker mock for utvikling
configureKafka(applicationConfig, listOf(dependencies.bekreftelseKafkaStreams))
}
configureKafka(applicationConfig, listOf(dependencies.bekreftelseKafkaStreams))

routing {
healthRoutes(dependencies.healthIndicatorRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import no.nav.paw.bekreftelse.api.kafka.buildBekreftelseTopology
import no.nav.paw.bekreftelse.api.plugins.buildKafkaStreams
import no.nav.paw.bekreftelse.api.services.AutorisasjonService
import no.nav.paw.bekreftelse.api.services.BekreftelseService
import no.nav.paw.bekreftelse.api.services.BekreftelseServiceImpl
import no.nav.paw.bekreftelse.api.services.BekreftelseServiceMock
import no.nav.paw.health.repository.HealthIndicatorRepository
import no.nav.paw.kafkakeygenerator.auth.azureAdM2MTokenClient
import no.nav.paw.kafkakeygenerator.client.KafkaKeysClient
Expand Down Expand Up @@ -50,22 +48,14 @@ fun createDependencies(applicationConfig: ApplicationConfig): Dependencies {
val bekreftelseTopology = buildBekreftelseTopology(applicationConfig, prometheusMeterRegistry)
val bekreftelseKafkaStreams = buildKafkaStreams(applicationConfig, healthIndicatorRepository, bekreftelseTopology)

// TODO Bruker mock for utvikling
val bekreftelseService: BekreftelseService = if (applicationConfig.brukMock) {
healthIndicatorRepository.getLivenessIndicators().forEach { it.setHealthy() }
healthIndicatorRepository.getReadinessIndicators().forEach { it.setHealthy() }
val bekreftelseProducer = BekreftelseProducer(applicationConfig)

BekreftelseServiceMock()
} else {
val bekreftelseProducer = BekreftelseProducer(applicationConfig)

BekreftelseServiceImpl(
applicationConfig,
httpClient,
bekreftelseKafkaStreams,
bekreftelseProducer
)
}
val bekreftelseService = BekreftelseService(
applicationConfig,
httpClient,
bekreftelseKafkaStreams,
bekreftelseProducer
)

return Dependencies(
kafkaKeysClient,
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ package no.nav.paw.bekreftelse.api.authz
import no.nav.paw.bekreftelse.api.model.Identitetsnummer
import java.util.*

sealed class Issuer(
val name: String
)

data object TokenX : Issuer("tokenx")
data object Azure : Issuer("azure")

sealed class Claim<A : Any>(
val issuer: String,
val issuer: Issuer,
val claimName: String,
val fromString: (String) -> A
)

data object AzureName : Claim<String>("azure", "name", { it })
data object AzureNavIdent : Claim<String>("azure", "NAVident", { it })
data object AzureOID : Claim<UUID>("azure", "oid", UUID::fromString)
data object TokenXPID : Claim<Identitetsnummer>("tokenx", "pid", ::Identitetsnummer)
data object AzureName : Claim<String>(Azure, "name", { it })
data object AzureNavIdent : Claim<String>(Azure, "NAVident", { it })
data object AzureOID : Claim<UUID>(Azure, "oid", UUID::fromString)
data object TokenXPID : Claim<Identitetsnummer>(TokenX, "pid", ::Identitetsnummer)
Original file line number Diff line number Diff line change
@@ -1,35 +1,51 @@
package no.nav.paw.bekreftelse.api.authz

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.plugins.BadRequestException
import io.ktor.server.request.path
import io.ktor.util.pipeline.PipelineContext
import io.opentelemetry.instrumentation.annotations.WithSpan
import no.nav.paw.bekreftelse.api.model.Identitetsnummer
import no.nav.paw.bekreftelse.api.exception.BearerTokenManglerException
import no.nav.paw.bekreftelse.api.exception.UfullstendigBearerTokenException
import no.nav.paw.bekreftelse.api.model.BrukerType
import no.nav.paw.bekreftelse.api.model.InnloggetBruker
import no.nav.paw.bekreftelse.api.model.Sluttbruker
import no.nav.paw.bekreftelse.api.services.AutorisasjonService
import no.nav.paw.kafkakeygenerator.client.KafkaKeysClient
import no.nav.poao_tilgang.client.TilgangType
import no.nav.security.token.support.v2.TokenValidationContextPrincipal

data class RequestScope(
val identitetsnummer: Identitetsnummer,
val arbeidssoekerId: Long,
val sluttbruker: Sluttbruker,
val innloggetBruker: InnloggetBruker,
val claims: ResolvedClaims,
val path: String,
val bearerToken: String,
val callId: String?,
val traceparent: String?,
val navConsumerId: String?,
val useMockData: Boolean
)

@Suppress("ConstPropertyName")
object NavHttpHeaders {
const val TraceParent = "traceparent"
const val NavCallId = "Nav-Call-Id"
const val NavConsumerId = "Nav-Consumer-Id"
}

@WithSpan
suspend fun PipelineContext<Unit, ApplicationCall>.requestScope(
identitetsnummer: String,
identitetsnummer: String?,
kafkaKeyClient: KafkaKeysClient,
autorisasjonService: AutorisasjonService, // TODO Legg til autorisasjon
tilgangType: TilgangType
): RequestScope {
val bearerToken = call.request.headers[HttpHeaders.Authorization]
?: throw BearerTokenManglerException("Request mangler Bearer Token")

val tokenValidationContext = call.principal<TokenValidationContextPrincipal>()

val resolvedClaims = tokenValidationContext
Expand All @@ -41,17 +57,51 @@ suspend fun PipelineContext<Unit, ApplicationCall>.requestScope(
TokenXPID
) ?: ResolvedClaims()

val kafkaKeysResponse = kafkaKeyClient.getIdAndKey(identitetsnummer)
val headers = call.request.headers
if (resolvedClaims.isEmpty()) {
throw UfullstendigBearerTokenException("Bearer Token mangler påkrevd innhold")
}

return RequestScope(
identitetsnummer = Identitetsnummer(identitetsnummer),
// TODO Håndtere at fnr kommer i body
val resolvedIdentitetsnummer = if (resolvedClaims.isTokenX()) {
resolvedClaims[TokenXPID]?.verdi
?: throw UfullstendigBearerTokenException("Bearer Token mangler påkrevd innhold")
} else if (resolvedClaims.isAzure()) {
identitetsnummer ?: throw BadRequestException("Request mangler identitetsnummer")
} else {
throw UfullstendigBearerTokenException("Bearer Token er utstedt av ukjent issuer")
}

val kafkaKeysResponse = kafkaKeyClient.getIdAndKey(resolvedIdentitetsnummer)
val sluttbruker = Sluttbruker(
identitetsnummer = resolvedIdentitetsnummer,
arbeidssoekerId = kafkaKeysResponse.id,
kafkaKey = kafkaKeysResponse.key
)

val innloggetBruker = if (resolvedClaims.isTokenX()) {
InnloggetBruker(
type = BrukerType.SLUTTBRUKER,
ident = resolvedIdentitetsnummer,
bearerToken = bearerToken
)
} else {
val ident = resolvedClaims[AzureNavIdent]
?: throw UfullstendigBearerTokenException("Bearer Token mangler påkrevd innhold")
InnloggetBruker(
type = BrukerType.VEILEDER,
ident = ident,
bearerToken = bearerToken
)
}

return RequestScope(
sluttbruker = sluttbruker,
innloggetBruker = innloggetBruker,
claims = resolvedClaims,
path = call.request.path(),
bearerToken = headers["Authorization"] ?: throw BearerTokenManglerException("Request mangler Bearer Token"),
callId = headers["Nav-Call-Id"],
traceparent = headers["traceparent"],
navConsumerId = headers["Nav-Consumer-Id"]
callId = call.request.headers[NavHttpHeaders.NavCallId],
traceparent = call.request.headers[NavHttpHeaders.TraceParent],
navConsumerId = call.request.headers[NavHttpHeaders.NavConsumerId],
useMockData = call.request.queryParameters["useMockData"].toBoolean()
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ class ResolvedClaims private constructor(

@Suppress("UNCHECKED_CAST")
operator fun <T : Any> get(claim: Claim<T>): T? = map[claim] as T?
fun isResolved(claim: Claim<*>): Boolean = map.containsKey(claim)

fun <T : Any> add(claim: Claim<T>, rawValue: String): ResolvedClaims {
val parsed = claim.fromString(rawValue)
val pair: Pair<Claim<T>, Any> = claim to parsed
return ResolvedClaims(map + pair)
}

fun isTokenX() = map.isNotEmpty() && map.keys.filter { it.issuer == TokenX }.size == map.size

fun isAzure() = map.isNotEmpty() && map.keys.filter { it.issuer == Azure }.size == map.size

fun isResolved(claim: Claim<*>): Boolean = map.containsKey(claim)

fun isEmpty() = map.isEmpty()
}

fun TokenValidationContext?.resolveClaims(vararg claims: Claim<*>): ResolvedClaims =
Expand All @@ -31,7 +38,7 @@ fun TokenValidationContext?.resolveClaims(vararg claims: Claim<*>): ResolvedClai
}

fun TokenValidationContext?.resolve(claim: Claim<*>): String? =
this?.getClaimOrNull(claim.issuer)
this?.getClaimOrNull(claim.issuer.name)
?.getStringClaim(claim.claimName)

fun TokenValidationContext.getClaimOrNull(issuer: String) =
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ data class ApplicationConfig(
val naisEnv: NaisEnv = currentNaisEnv,
val appId: String = currentAppId ?: "UNSPECIFIED",
val appName: String = currentAppName ?: "UNSPECIFIED",
val hostname: String = InetAddress.getLocalHost().hostName,

val brukMock: Boolean = true
val hostname: String = InetAddress.getLocalHost().hostName
)

data class KafkaTopologyConfig(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package no.nav.paw.bekreftelse.api.exception

import no.nav.paw.error.exception.AuthorizationException

class ArbeidssoekerIdIkkeFunnetException(message: String) :
AuthorizationException("PAW_ARBEIDSSOEKER_ID_IKKE_FUNNET", message, null)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package no.nav.paw.bekreftelse.api.exception

import no.nav.paw.error.exception.AuthenticationException

class BearerTokenManglerException(message: String) : AuthenticationException("PAW_BEARER_TOKEN_MANGLER", message, null)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package no.nav.paw.bekreftelse.api.exception

import no.nav.paw.error.exception.AuthorizationException

class UfullstendigBearerTokenException(message: String) :
AuthorizationException("PAW_UFULLSTENDIG_BEARER_TOKEN", message, null)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package no.nav.paw.bekreftelse.api.exception

import no.nav.paw.error.exception.AuthorizationException

class VeilerHarIkkeTilgangException(message: String) :
AuthorizationException("PAW_VEILEDER_HAR_IKKE_TILGANG", message, null) {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package no.nav.paw.bekreftelse.api.kafka

import io.micrometer.core.instrument.MeterRegistry
import no.nav.paw.bekreftelse.api.model.InternState
import no.nav.paw.bekreftelse.internehendelser.BekreftelseHendelse
import no.nav.paw.bekreftelse.internehendelser.BekreftelseMeldingMottatt
Expand All @@ -13,16 +14,18 @@ import org.apache.kafka.streams.processor.api.Record
import org.apache.kafka.streams.state.KeyValueStore

fun KStream<Long, BekreftelseHendelse>.oppdaterBekreftelseHendelseState(
stateStoreName: String
stateStoreName: String,
meterRegistry: MeterRegistry
): KStream<Long, BekreftelseHendelse> {
val processor = {
BekreftelseHendelseProcessor(stateStoreName)
BekreftelseHendelseProcessor(stateStoreName, meterRegistry)
}
return process(processor, Named.`as`("bekreftelseHendelseProcessor"), stateStoreName)
}

class BekreftelseHendelseProcessor(
private val stateStoreName: String,
meterRegistry: MeterRegistry
) : Processor<Long, BekreftelseHendelse, Long, BekreftelseHendelse> {
private var stateStore: KeyValueStore<Long, InternState>? = null
private var context: ProcessorContext<Long, BekreftelseHendelse>? = null
Expand All @@ -33,9 +36,10 @@ class BekreftelseHendelseProcessor(
stateStore = context?.getStateStore(stateStoreName)
}

// TODO Legg til metrics
override fun process(record: Record<Long, BekreftelseHendelse>?) {
val value = record?.value() ?: return
val hendelseStore = requireNotNull(stateStore) { "State store is not initialized" }
val hendelseStore = requireNotNull(stateStore) { "Intern state store er ikke initiert" }
when (value) {
is BekreftelseTilgjengelig -> {
hendelseStore.get(value.arbeidssoekerId)?.let {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package no.nav.paw.bekreftelse.api.kafka

import no.nav.paw.bekreftelse.api.config.ApplicationConfig
import no.nav.paw.bekreftelse.api.model.BekreftelseRequest
import no.nav.paw.bekreftelse.api.utils.buildBekreftelseSerde
import no.nav.paw.bekreftelse.api.utils.logger
import no.nav.paw.bekreftelse.internehendelser.BekreftelseTilgjengelig
import no.nav.paw.bekreftelse.melding.v1.Bekreftelse
import no.nav.paw.config.kafka.KafkaFactory
import no.nav.paw.config.kafka.sendDeferred
Expand Down Expand Up @@ -43,20 +41,3 @@ class BekreftelseProducer(
producer.close()
}
}

fun createMelding(state: BekreftelseTilgjengelig, bekreftelse: BekreftelseRequest): Bekreftelse = TODO()
//Melding.newBuilder()
// .setId(ApplicationInfo.id)
// .setNamespace("paw")
// .setPeriodeId(state.periodeId)
// .setSvar(Svar(
// Metadata(
// Instant.now(),
// Bruker
// )
// ))
// .setGjelderFra(state.gjelderFra)
// .setGjelderTil(state.gjelderTil)
// .setVilFortsetteSomArbeidssoeker(bekreftelse.vilFortsetteSomArbeidssoeker)
// .setHarJobbetIDennePerioden(bekreftelse.harJobbetIDennePerioden)
// .build()
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fun buildBekreftelseTopology(
meterRegistry: MeterRegistry
): Topology = StreamsBuilder().apply {
addInternStateStore(applicationConfig)
addBekreftelseKStream(applicationConfig)
addBekreftelseKStream(applicationConfig, meterRegistry)
}.build()

private fun StreamsBuilder.addInternStateStore(applicationConfig: ApplicationConfig) {
Expand All @@ -28,10 +28,13 @@ private fun StreamsBuilder.addInternStateStore(applicationConfig: ApplicationCon
)
}

private fun StreamsBuilder.addBekreftelseKStream(applicationConfig: ApplicationConfig) {
private fun StreamsBuilder.addBekreftelseKStream(
applicationConfig: ApplicationConfig,
meterRegistry: MeterRegistry
) {
stream(
applicationConfig.kafkaTopology.bekreftelseHendelsesloggTopic,
Consumed.with(Serdes.Long(), BekreftelseHendelseSerde())
)
.oppdaterBekreftelseHendelseState(applicationConfig.kafkaTopology.internStateStoreName)
.oppdaterBekreftelseHendelseState(applicationConfig.kafkaTopology.internStateStoreName, meterRegistry)
}
Loading

0 comments on commit 789575f

Please sign in to comment.