From c9c5e40be74a8b3664bc6ace50e4a52c3cfe478a Mon Sep 17 00:00:00 2001 From: Espen Johansen Velsvik Date: Fri, 16 Aug 2024 07:41:10 +0200 Subject: [PATCH 1/3] =?UTF-8?q?Resttjeneste=20for=20akt=C3=B8rbytte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Akt\303\270rBytteController.kt" | 33 +++++++++++++++++++ .../forvaltning/Akt\303\270rBytteRequest.kt" | 19 +++++++++++ .../forvaltning/Akt\303\270rBytteRespons.kt" | 13 ++++++++ .../sifinnsynapi/soknad/S\303\270knadDAO.kt" | 2 +- .../soknad/S\303\270knadService.kt" | 13 ++++++-- 5 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 "src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteController.kt" create mode 100644 "src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteRequest.kt" create mode 100644 "src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteRespons.kt" diff --git "a/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteController.kt" "b/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteController.kt" new file mode 100644 index 00000000..e2eeaac6 --- /dev/null +++ "b/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteController.kt" @@ -0,0 +1,33 @@ +package no.nav.sifinnsynapi.forvaltning + +import no.nav.security.token.support.core.api.ProtectedWithClaims +import no.nav.security.token.support.core.api.RequiredIssuers +import no.nav.sifinnsynapi.common.AktørId +import no.nav.sifinnsynapi.config.Issuers +import no.nav.sifinnsynapi.soknad.SøknadService +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequiredIssuers( + ProtectedWithClaims(issuer = Issuers.TOKEN_X, claimMap = ["acr=Level4"]) +) +class AktørBytteController( + private val søknadService: SøknadService +) { + + @PostMapping("forvaltning/oppdaterAktoerId", + produces = [MediaType.APPLICATION_JSON_VALUE]) + fun oppdaterAktoerId(@RequestBody aktørBytteRequest: AktørBytteRequest): ResponseEntity { + val antallOppdaterte = søknadService.oppdaterAktørId( + AktørId(aktørBytteRequest.gyldigAktør), + AktørId(aktørBytteRequest.utgåttAktør) + ) + return ResponseEntity.ok(AktørBytteRespons(antallOppdaterte)) + } + + +} diff --git "a/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteRequest.kt" "b/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteRequest.kt" new file mode 100644 index 00000000..492e74b2 --- /dev/null +++ "b/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteRequest.kt" @@ -0,0 +1,19 @@ +package no.nav.sifinnsynapi.forvaltning + +import com.fasterxml.jackson.annotation.JsonProperty +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size +import org.jetbrains.annotations.NotNull + +data class AktørBytteRequest( + @JsonProperty + @NotNull + @Size(max = 20) + @Pattern(regexp = "^\\d+$", message = "AktørId [\${validatedValue}] matcher ikke tillatt pattern [{regexp}]") + val utgåttAktør: String, + @JsonProperty + @NotNull + @Size(max = 20) + @Pattern(regexp = "^\\d+$", message = "AktørId [\${validatedValue}] matcher ikke tillatt pattern [{regexp}]") + val gyldigAktør: String +) diff --git "a/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteRespons.kt" "b/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteRespons.kt" new file mode 100644 index 00000000..d84945c2 --- /dev/null +++ "b/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteRespons.kt" @@ -0,0 +1,13 @@ +package no.nav.sifinnsynapi.forvaltning + +import com.fasterxml.jackson.annotation.JsonProperty +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size +import org.jetbrains.annotations.NotNull + +data class AktørBytteRespons( + @JsonProperty + @NotNull + val antallOppdaterteRader: Int, + +) diff --git "a/src/main/kotlin/no/nav/sifinnsynapi/soknad/S\303\270knadDAO.kt" "b/src/main/kotlin/no/nav/sifinnsynapi/soknad/S\303\270knadDAO.kt" index 9722c7e2..a8c534d5 100644 --- "a/src/main/kotlin/no/nav/sifinnsynapi/soknad/S\303\270knadDAO.kt" +++ "b/src/main/kotlin/no/nav/sifinnsynapi/soknad/S\303\270knadDAO.kt" @@ -22,7 +22,7 @@ import java.util.* @Entity(name = "søknad") data class SøknadDAO( @Column(name = "id") @Id @JdbcTypeCode(SqlTypes.UUID) val id: UUID = UUID.randomUUID(), - @Column(name = "aktør_id") @Embedded val aktørId: AktørId, + @Column(name = "aktør_id") @Embedded internal val aktørId: AktørId, @Column(name = "fødselsnummer") @Embedded val fødselsnummer: Fødselsnummer, @Column(name = "søknadstype") @Enumerated(STRING) val søknadstype: Søknadstype, @Column(name = "status") @Enumerated(STRING) val status: SøknadsStatus, diff --git "a/src/main/kotlin/no/nav/sifinnsynapi/soknad/S\303\270knadService.kt" "b/src/main/kotlin/no/nav/sifinnsynapi/soknad/S\303\270knadService.kt" index a6097977..45f946ae 100644 --- "a/src/main/kotlin/no/nav/sifinnsynapi/soknad/S\303\270knadService.kt" +++ "b/src/main/kotlin/no/nav/sifinnsynapi/soknad/S\303\270knadService.kt" @@ -3,6 +3,7 @@ package no.nav.sifinnsynapi.soknad import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper import no.nav.sifinnsynapi.common.AktørId +import no.nav.sifinnsynapi.common.Fødselsnummer import no.nav.sifinnsynapi.common.Søknadstype import no.nav.sifinnsynapi.dokument.DokumentDTO import no.nav.sifinnsynapi.dokument.DokumentService @@ -53,8 +54,9 @@ class SøknadService( return søknadDAOs .map { søknadDAO -> - val relevanteDokumenter = dokumentOversikt.filter { it.journalpostId == søknadDAO.journalpostId } - søknadDAO.tilSøknadDTO(relevanteDokumenter) } + val relevanteDokumenter = dokumentOversikt.filter { it.journalpostId == søknadDAO.journalpostId } + søknadDAO.tilSøknadDTO(relevanteDokumenter) + } } fun hentSøknad(søknadId: UUID): SøknadDTO { @@ -121,6 +123,13 @@ class SøknadService( fun finnUnikeSøknaderUtenMikrofrontendSisteSeksMåneder(søknadstype: Søknadstype, limit: Int): List { return repo.finnUnikeSøknaderUtenMikrofrontendSisteSeksMåneder(søknadstype.name, limit) } + + fun oppdaterAktørId(gyldigAktørId: AktørId, utgåttAktørId: AktørId): Int { + val søknader = repo.findAllByAktørId(utgåttAktørId); + søknader.map { it.copy( aktørId = gyldigAktørId ) } + .forEach { repo.save(it) } + return søknader.size; + } } class SøknadNotFoundException(søknadId: String) : From 13b6ee21c8e90fb7270a4263a11e9d79b2342663 Mon Sep 17 00:00:00 2001 From: Espen Johansen Velsvik Date: Fri, 16 Aug 2024 14:17:08 +0200 Subject: [PATCH 2/3] =?UTF-8?q?Legger=20til=20sjekk=20p=C3=A5=20driftsgrup?= =?UTF-8?q?pe,=20innlogging=20med=20azure=20og=20swaggeroppsett?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nais/dev-gcp.json | 16 +- nais/naiserator.yaml | 11 ++ nais/prod-gcp.json | 4 +- .../config/SecurityConfiguration.kt | 1 + .../config/SwaggerConfiguration.kt | 47 +++++- .../Akt\303\270rBytteController.kt" | 24 ++- .../sikkerhet/AuthorizationService.kt | 22 +++ .../sifinnsynapi/sikkerhet/ContextHolder.kt | 39 +++++ src/main/resources/application.yml | 12 ++ .../Akt\303\270rBytteControllerTest.kt" | 153 ++++++++++++++++++ src/test/resources/application-test.yml | 4 + 11 files changed, 323 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/no/nav/sifinnsynapi/sikkerhet/AuthorizationService.kt create mode 100644 src/main/kotlin/no/nav/sifinnsynapi/sikkerhet/ContextHolder.kt create mode 100644 "src/test/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteControllerTest.kt" diff --git a/nais/dev-gcp.json b/nais/dev-gcp.json index d359d4f0..a8b9ec9f 100644 --- a/nais/dev-gcp.json +++ b/nais/dev-gcp.json @@ -21,6 +21,17 @@ "diskAutoresize": "false", "highAvailability": "false" }, + "azure": { + "replyURLs": [ + "https://sif-innsyn-api.intern.dev.nav.no/swagger-ui/oauth2-redirect.html" + ], + "groups": [ + { + "name": "0000-GA-k9-drift", + "objectId": "0bc9661c-975c-4adb-86d1-a97172490662" + } + ] + }, "azureTenant": "trygdeetaten.no", "kafkaPool": "nav-dev", "env": { @@ -39,7 +50,10 @@ "SAF_AZURE_SCOPE": "api://dev-fss.teamdokumenthandtering.saf-q1/.default", "K9_SAK_INNSYN_API_TOKEN_X_AUDIENCE": "dev-gcp:dusseldorf:k9-sak-innsyn-api", "K9_SELVBETJENING_OPPSLAG_TOKEN_X_AUDIENCE": "dev-gcp:dusseldorf:k9-selvbetjening-oppslag", - "SWAGGER_ENABLED": "true" + "SWAGGER_ENABLED": "true", + "AZURE_LOGIN_URL": "https://login.microsoftonline.com/navq.onmicrosoft.com/oauth2/v2.0", + "K9_DRIFT_GRUPPE_ID": "0bc9661c-975c-4adb-86d1-a97172490662" + }, "slack-channel": "sif-alerts-dev", "slack-notify-type": " | sif-innsyn-api | " diff --git a/nais/naiserator.yaml b/nais/naiserator.yaml index 0c066a75..95e43d45 100644 --- a/nais/naiserator.yaml +++ b/nais/naiserator.yaml @@ -13,6 +13,17 @@ spec: application: enabled: true tenant: {{azureTenant}} + claims: + extra: + - "NAVident" + groups: + {{#each azure.groups as |group|}} + - id: {{group.objectId}} + {{/each}} + replyURLs: + {{#each azure.replyURLs as |url|}} + - {{url}} + {{/each}} tokenx: enabled: true accessPolicy: diff --git a/nais/prod-gcp.json b/nais/prod-gcp.json index 89a5e2a1..71fa6615 100644 --- a/nais/prod-gcp.json +++ b/nais/prod-gcp.json @@ -38,7 +38,9 @@ "SAF_AZURE_SCOPE": "api://prod-fss.teamdokumenthandtering.saf/.default", "SAFSELVBETJENING_TOKEN_X_AUDIENCE": "prod-fss:teamdokumenthandtering:safselvbetjening", "K9_SAK_INNSYN_API_TOKEN_X_AUDIENCE": "prod-gcp:dusseldorf:k9-sak-innsyn-api", - "K9_SELVBETJENING_OPPSLAG_TOKEN_X_AUDIENCE": "prod-gcp:dusseldorf:k9-selvbetjening-oppslag" + "K9_SELVBETJENING_OPPSLAG_TOKEN_X_AUDIENCE": "prod-gcp:dusseldorf:k9-selvbetjening-oppslag", + "AZURE_LOGIN_URL": "https://login.microsoftonline.com/navno.onmicrosoft.com/oauth2/v2.0", + "K9_DRIFT_GRUPPE_ID": "1509dc91-a955-4e72-b64c-2f049e37c0c6" }, "slack-channel": "sif-alerts", "slack-notify-type": " | sif-innsyn-api | " diff --git a/src/main/kotlin/no/nav/sifinnsynapi/config/SecurityConfiguration.kt b/src/main/kotlin/no/nav/sifinnsynapi/config/SecurityConfiguration.kt index 09330789..e75e745b 100644 --- a/src/main/kotlin/no/nav/sifinnsynapi/config/SecurityConfiguration.kt +++ b/src/main/kotlin/no/nav/sifinnsynapi/config/SecurityConfiguration.kt @@ -9,4 +9,5 @@ internal class SecurityConfiguration object Issuers { const val TOKEN_X = "tokenx" + const val AZURE = "azure" } diff --git a/src/main/kotlin/no/nav/sifinnsynapi/config/SwaggerConfiguration.kt b/src/main/kotlin/no/nav/sifinnsynapi/config/SwaggerConfiguration.kt index a1f0de2f..3e054c24 100644 --- a/src/main/kotlin/no/nav/sifinnsynapi/config/SwaggerConfiguration.kt +++ b/src/main/kotlin/no/nav/sifinnsynapi/config/SwaggerConfiguration.kt @@ -1,16 +1,26 @@ package no.nav.sifinnsynapi.config +import io.swagger.v3.oas.models.Components import io.swagger.v3.oas.models.ExternalDocumentation import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.info.Info +import io.swagger.v3.oas.models.security.* import io.swagger.v3.oas.models.servers.Server +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.EnvironmentAware import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Profile +import org.springframework.core.env.Environment @Configuration -@Profile("local", "dev-gcp") -class SwaggerConfiguration { +class SwaggerConfiguration( + @Value("\${springdoc.oAuthFlow.authorizationUrl}") val authorizationUrl: String, + @Value("\${springdoc.oAuthFlow.tokenUrl}") val tokenUrl: String, + @Value("\${springdoc.oAuthFlow.apiScope}") val apiScope: String +) : EnvironmentAware { + + private var env: Environment? = null + @Bean fun openAPI(): OpenAPI { @@ -28,6 +38,37 @@ class SwaggerConfiguration { ExternalDocumentation() .description("Sif Innsyn Api GitHub repository") .url("https://github.com/navikt/sif-innsyn-api") + ).components( + Components() + .addSecuritySchemes("oauth2", azureLogin()) + ) + .addSecurityItem( + SecurityRequirement() + .addList("oauth2", listOf("read", "write")) + .addList("Authorization") ) } + + + private fun azureLogin(): SecurityScheme { + return SecurityScheme() + .name("oauth2") + .type(SecurityScheme.Type.OAUTH2) + .scheme("oauth2") + .`in`(SecurityScheme.In.HEADER) + .flows( + OAuthFlows() + .authorizationCode( + OAuthFlow().authorizationUrl(authorizationUrl) + .tokenUrl(tokenUrl) + .scopes(Scopes().addString(apiScope, "read,write")) + ) + ) + } + + override fun setEnvironment(environment: Environment) { + this.env = environment; + } + + } diff --git "a/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteController.kt" "b/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteController.kt" index e2eeaac6..1e446d96 100644 --- "a/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteController.kt" +++ "b/src/main/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteController.kt" @@ -4,24 +4,39 @@ import no.nav.security.token.support.core.api.ProtectedWithClaims import no.nav.security.token.support.core.api.RequiredIssuers import no.nav.sifinnsynapi.common.AktørId import no.nav.sifinnsynapi.config.Issuers +import no.nav.sifinnsynapi.sikkerhet.AuthorizationService +import no.nav.sifinnsynapi.sikkerhet.ContextHolder import no.nav.sifinnsynapi.soknad.SøknadService +import org.springframework.http.HttpStatus import org.springframework.http.MediaType +import org.springframework.http.ProblemDetail import org.springframework.http.ResponseEntity +import org.springframework.web.ErrorResponseException import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController @RestController @RequiredIssuers( - ProtectedWithClaims(issuer = Issuers.TOKEN_X, claimMap = ["acr=Level4"]) + ProtectedWithClaims(issuer = Issuers.AZURE) ) class AktørBytteController( - private val søknadService: SøknadService + private val søknadService: SøknadService, + private val authorizationService: AuthorizationService ) { - @PostMapping("forvaltning/oppdaterAktoerId", - produces = [MediaType.APPLICATION_JSON_VALUE]) + @PostMapping( + "/forvaltning/oppdaterAktoerId", + consumes = [MediaType.APPLICATION_JSON_VALUE], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + @ProtectedWithClaims(issuer = ContextHolder.AZURE_AD, claimMap = ["NAVident=*"]) fun oppdaterAktoerId(@RequestBody aktørBytteRequest: AktørBytteRequest): ResponseEntity { + if (!authorizationService.harTilgangTilDriftRolle()) { + val problemDetail = ProblemDetail.forStatus(HttpStatus.FORBIDDEN) + problemDetail.detail = "Mangler driftsrolle" + throw ErrorResponseException(HttpStatus.FORBIDDEN, problemDetail, null) + } val antallOppdaterte = søknadService.oppdaterAktørId( AktørId(aktørBytteRequest.gyldigAktør), AktørId(aktørBytteRequest.utgåttAktør) @@ -29,5 +44,4 @@ class AktørBytteController( return ResponseEntity.ok(AktørBytteRespons(antallOppdaterte)) } - } diff --git a/src/main/kotlin/no/nav/sifinnsynapi/sikkerhet/AuthorizationService.kt b/src/main/kotlin/no/nav/sifinnsynapi/sikkerhet/AuthorizationService.kt new file mode 100644 index 00000000..a1eb0b59 --- /dev/null +++ b/src/main/kotlin/no/nav/sifinnsynapi/sikkerhet/AuthorizationService.kt @@ -0,0 +1,22 @@ +package no.nav.sifinnsynapi.sikkerhet + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +class AuthorizationService( + private val contextHolder: ContextHolder, + private val k9DriftGruppeId: String, +) { + fun harTilgangTilDriftRolle(): Boolean { + return contextHolder.requestKontekst()?.jwtToken?.containsClaim("groups", k9DriftGruppeId) ?: false + } +} + +@Configuration +class AuthorizationConfig( + @Value("\${no.nav.security.k9-drift-gruppe}") private val k9DriftGruppeId: String, +) { + @Bean + fun authorizationService() = AuthorizationService(ContextHolder.INSTANCE, k9DriftGruppeId) +} diff --git a/src/main/kotlin/no/nav/sifinnsynapi/sikkerhet/ContextHolder.kt b/src/main/kotlin/no/nav/sifinnsynapi/sikkerhet/ContextHolder.kt new file mode 100644 index 00000000..df43a461 --- /dev/null +++ b/src/main/kotlin/no/nav/sifinnsynapi/sikkerhet/ContextHolder.kt @@ -0,0 +1,39 @@ +package no.nav.sifinnsynapi.sikkerhet + +import no.nav.security.token.support.core.jwt.JwtToken +import no.nav.security.token.support.spring.SpringTokenValidationContextHolder +import org.springframework.web.context.request.RequestContextHolder + +class ContextHolder private constructor(private val context: SpringTokenValidationContextHolder) { + + companion object { + const val AZURE_AD = "azure" + private var instans: ContextHolder? = null + val INSTANCE: ContextHolder + get() { + if (instans == null) { + instans = ContextHolder(SpringTokenValidationContextHolder()) + } + return instans!! + } + + } + + fun requestKontekst(): RequestKontekst? { + if (RequestContextHolder.getRequestAttributes() == null) + return null + + val tokenContext = context.getTokenValidationContext() + val reqIssuerShortNames = tokenContext.issuers //alle issuers på alle validerte tokens i context + if (reqIssuerShortNames.contains(AZURE_AD)) { + val jwtToken: JwtToken? = tokenContext.getJwtToken(AZURE_AD) + return jwtToken?.let { RequestKontekst(it, AZURE_AD) } + } + return null + } + + @JvmRecord + data class RequestKontekst(val jwtToken: JwtToken, val issuerShortname: String) + + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8210a537..ebf89fdb 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -29,10 +29,14 @@ no.nav: saf-selvbetjening-base-url: # Settes i nais/.json security: + k9-drift-gruppe: ${K9_DRIFT_GRUPPE_ID} cors: allowed-origins: # Settes i nais/.json jwt: issuer: + azure: + discoveryUrl: ${AZURE_APP_WELL_KNOWN_URL} + accepted_audience: ${AZURE_APP_CLIENT_ID} tokenx: discoveryUrl: ${TOKEN_X_WELL_KNOWN_URL} accepted_audience: ${TOKEN_X_CLIENT_ID} @@ -187,3 +191,11 @@ springdoc: enabled: ${SWAGGER_ENABLED:false} disable-swagger-default-url: true path: swagger-ui.html + oauth: + use-pkce-with-authorization-code-grant: true + client-id: ${AZURE_APP_CLIENT_ID} + scope-separator: "," + oAuthFlow: + authorizationUrl: ${AZURE_LOGIN_URL:http://localhost:8080}/authorize + tokenUrl: ${AZURE_LOGIN_URL:http://localhost:8080}/token + apiScope: api://${AZURE_APP_CLIENT_ID:abc123}/.default diff --git "a/src/test/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteControllerTest.kt" "b/src/test/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteControllerTest.kt" new file mode 100644 index 00000000..9ec36665 --- /dev/null +++ "b/src/test/kotlin/no/nav/sifinnsynapi/forvaltning/Akt\303\270rBytteControllerTest.kt" @@ -0,0 +1,153 @@ +package no.nav.sifinnsynapi.forvaltning + +import com.fasterxml.jackson.databind.ObjectMapper +import com.nimbusds.jose.JOSEObjectType.JWT +import com.nimbusds.jwt.JWTClaimsSet +import com.ninjasquad.springmockk.MockkBean +import io.mockk.every +import no.nav.security.mock.oauth2.MockOAuth2Server +import no.nav.security.mock.oauth2.token.DefaultOAuth2TokenCallback +import no.nav.security.token.support.spring.test.EnableMockOAuth2Server +import no.nav.sifinnsynapi.config.SecurityConfiguration +import no.nav.sifinnsynapi.sikkerhet.AuthorizationConfig +import no.nav.sifinnsynapi.sikkerhet.ContextHolder +import no.nav.sifinnsynapi.soknad.SøknadService +import no.nav.sifinnsynapi.util.CallIdGenerator +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.context.annotation.Import +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.* +import java.net.URI +import java.util.* +import java.util.concurrent.TimeUnit.MINUTES + +@ExtendWith(SpringExtension::class) +@EnableMockOAuth2Server +@Import(CallIdGenerator::class, SecurityConfiguration::class, AuthorizationConfig::class) +@WebMvcTest(controllers = [AktørBytteController::class]) +@AutoConfigureMockMvc +@ActiveProfiles("test") +open class AktørBytteControllerTest { + + @Autowired + lateinit var mockMvc: MockMvc + + @Autowired + lateinit var mockOAuth2Server: MockOAuth2Server + + @Autowired + private lateinit var objectMapper: ObjectMapper + + @MockkBean(relaxed = true) + private lateinit var søknadService: SøknadService + + + private val PATH = "/forvaltning/oppdaterAktoerId" + private val gyldigTestDriftRolle = "testdrift" + private val ugyldigTestDriftRolle = "enugyldigrolle" + + + @Test + fun `skal autentisere med claims og tillate å endre aktørid`() { + val gammelAktørId = "123" + val nyAktørId = "567" + every { + søknadService.oppdaterAktørId(any(), any()) + } returns 2 + + val token = token( + ContextHolder.AZURE_AD, + defaultJwtClaimsSetBuilder() + .claim("NAVident", "TEST") + .claim("groups", arrayOf(gyldigTestDriftRolle)) //definert i application.properties + .build() + ) + + val requestBody = objectMapper.writeValueAsString(AktørBytteRequest(gammelAktørId, nyAktørId)) + + mockMvc.perform( + post(URI(PATH)) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.antallOppdaterteRader").value(2)) + + } + + @Test + fun `skal gi 403 dersom det mangler driftsrolle`() { + val gammelAktørId = "123" + val nyAktørId = "567" + every { + søknadService.oppdaterAktørId(any(), any()) + } returns 2 + + val token = token( + ContextHolder.AZURE_AD, + defaultJwtClaimsSetBuilder() + .claim("NAVident", "TEST") + .claim("groups", arrayOf(ugyldigTestDriftRolle)) //definert i application.properties + .build() + ) + + val requestBody = objectMapper.writeValueAsString(AktørBytteRequest(gammelAktørId, nyAktørId)) + + mockMvc.perform( + post(URI(PATH)) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + ).andExpect(status().isForbidden) + .andExpect( + content().json( + """ + { + "type":"about:blank", + "title":"Forbidden", + "status":403, + "detail":"Mangler driftsrolle", + "instance":"/forvaltning/oppdaterAktoerId" + } + """.trimIndent(), true + ) + ) + + } + + private fun token(issuerId: String, jwtClaimsSet: JWTClaimsSet) = mockOAuth2Server.issueToken( + issuerId, + "theclientid", + DefaultOAuth2TokenCallback( + issuerId, + "saksbehandler", + JWT.type, + listOf("aud-localhost"), + jwtClaimsSet.claims, + 30L + ) + ).serialize() + + + private fun defaultJwtClaimsSetBuilder(): JWTClaimsSet.Builder { + val now = Date() + return JWTClaimsSet.Builder() + .subject("testsub") + .audience("aud-localhost") + .jwtID(UUID.randomUUID().toString()) + .claim("auth_time", now) + .notBeforeTime(now) + .issueTime(now) + .expirationTime(Date(now.time + MINUTES.toMillis(5))) + } +} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 5e6b2439..31059d6d 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -5,8 +5,12 @@ no.nav: saf-base-url: http://localhost:${wiremock.server.port}/saf-api-mock saf-selvbetjening-base-url: http://localhost:${wiremock.server.port}/saf-selvbetjening-graphql-mock security: + k9-drift-gruppe: testdrift jwt: issuer: + azure: + discoveryurl: http://localhost:${mock-oauth2-server.port}/azure/.well-known/openid-configuration + accepted_audience: aud-localhost tokenx: discoveryurl: http://localhost:${mock-oauth2-server.port}/tokenx/.well-known/openid-configuration accepted_audience: aud-localhost From 3abf6ee2991124c79d3af01e42ce34bfb66e1204 Mon Sep 17 00:00:00 2001 From: Espen Johansen Velsvik Date: Fri, 16 Aug 2024 14:22:12 +0200 Subject: [PATCH 3/3] Legger til groups i prod --- nais/prod-gcp.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/nais/prod-gcp.json b/nais/prod-gcp.json index 71fa6615..b9c0ff8b 100644 --- a/nais/prod-gcp.json +++ b/nais/prod-gcp.json @@ -21,6 +21,17 @@ "diskAutoresize": "true", "highAvailability": "true" }, + "azure": { + "replyURLs": [ + "https://sif-innsyn-api.intern.nav.no/swagger-ui/oauth2-redirect.html" + ], + "groups": [ + { + "name": "0000-GA-k9-drift", + "objectId": "1509dc91-a955-4e72-b64c-2f049e37c0c6" + } + ] + }, "azureTenant": "nav.no", "kafkaPool": "nav-prod", "env": {