Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package no.nav.navnosearchadminapi.consumer.azuread

import no.nav.navnosearchadminapi.consumer.azuread.dto.inbound.TokenRequest
import no.nav.navnosearchadminapi.consumer.azuread.dto.outbound.TokenResponse
import no.nav.navnosearchadminapi.exception.TokenFetchException
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.util.MultiValueMap
import org.springframework.web.client.HttpStatusCodeException
import org.springframework.web.client.RestTemplate
import org.springframework.web.client.exchange

@Component
class AzureADConsumer(
val restTemplate: RestTemplate,
@Value("\${no.nav.security.jwt.issuer.azuread.accepted-audience}") val clientId: String,
@Value("\${no.nav.security.jwt.issuer.azuread.client-secret}") val clientSecret: String,
@Value("\${no.nav.security.jwt.issuer.azuread.token-endpoint}") val tokenEndpoint: String,
) {
fun getAccessToken(scope: String): String {
try {
return restTemplate.exchange<TokenResponse>(
tokenEndpoint,
HttpMethod.POST,
createRequestEntity(scope),
TokenResponse::class
).body!!.accessToken
} catch (e: HttpStatusCodeException) {
throw TokenFetchException("Henting av Azure AD access token feilet. ${e.message}", e)
}
}

private fun createRequestEntity(scope: String): HttpEntity<MultiValueMap<String, String>> {
return HttpEntity(
TokenRequest(clientId = clientId, clientSecret = clientSecret, scope = scope).asMultiValueMap(),
HttpHeaders().apply { contentType = MediaType.APPLICATION_FORM_URLENCODED }
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package no.nav.navnosearchadminapi.consumer.azuread.dto.inbound

import org.springframework.util.LinkedMultiValueMap
import org.springframework.util.MultiValueMap

data class TokenRequest(
val clientId: String,
val clientSecret: String,
val grantType: String = CLIENT_CREDENTIALS,
val scope: String,
) {
fun asMultiValueMap(): MultiValueMap<String, String> {
return LinkedMultiValueMap<String, String>().apply {
add(CLIENT_ID, clientId)
add(CLIENT_SECRET, clientSecret)
add(GRANT_TYPE, grantType)
add(SCOPE, scope)
}
}

companion object {
private const val CLIENT_ID = "client_id"
private const val CLIENT_SECRET = "client_secret"
private const val GRANT_TYPE = "grant_type"
private const val SCOPE = "scope"

private const val CLIENT_CREDENTIALS = "client_credentials"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package no.nav.navnosearchadminapi.consumer.azuread.dto.outbound

import com.fasterxml.jackson.annotation.JsonProperty

data class TokenResponse(
@JsonProperty("access_token") val accessToken: String,
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package no.nav.navnosearchadminapi.consumer.kodeverk

import no.nav.navnosearchadminapi.consumer.azuread.AzureADConsumer
import no.nav.navnosearchadminapi.consumer.kodeverk.dto.KodeverkResponse
import no.nav.navnosearchadminapi.exception.KodeverkConsumerException
import org.springframework.beans.factory.annotation.Value
Expand All @@ -13,7 +14,12 @@ import org.springframework.web.client.RestTemplate
import java.util.*

@Component
class KodeverkConsumer(val restTemplate: RestTemplate, @Value("\${kodeverk.spraak.url}") val kodeverkUrl: String) {
class KodeverkConsumer(
val restTemplate: RestTemplate,
val azureADConsumer: AzureADConsumer,
@Value("\${kodeverk.spraak.url}") val kodeverkUrl: String,
@Value("\${kodeverk.scope}") val kodeverkScope: String,
) {

@Cacheable("spraakkoder")
fun fetchSpraakKoder(): KodeverkResponse {
Expand All @@ -31,9 +37,12 @@ class KodeverkConsumer(val restTemplate: RestTemplate, @Value("\${kodeverk.spraa
}

private fun headers(): HttpHeaders {
return HttpHeaders().apply {
set(NAV_CALL_ID, UUID.randomUUID().toString())
set(NAV_CONSUMER_ID, NAVNO_SEARCH_ADMIN_API)
return azureADConsumer.getAccessToken(kodeverkScope).let { accessToken ->
HttpHeaders().apply {
setBearerAuth(accessToken)
set(NAV_CALL_ID, UUID.randomUUID().toString())
set(NAV_CONSUMER_ID, NAVNO_SEARCH_ADMIN_API)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package no.nav.navnosearchadminapi.exception

class TokenFetchException(message: String, cause: Throwable) : Exception(message, cause) {
}
3 changes: 3 additions & 0 deletions app/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ management:
kodeverk:
spraak:
url: ${KODEVERK_SPRAAK_URL}
scope: ${KODEVERK_SCOPE}

api-key: ${API_KEY}

Expand All @@ -27,3 +28,5 @@ no.nav.security.jwt:
azuread:
discoveryurl: ${AZURE_APP_WELL_KNOWN_URL}
accepted-audience: ${AZURE_APP_CLIENT_ID}
client-secret: ${AZURE_APP_CLIENT_SECRET}
token-endpoint: ${AZURE_OPENID_CONFIG_TOKEN_ENDPOINT}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package no.nav.navnosearchadminapi.integrationtests

import com.fasterxml.jackson.databind.ObjectMapper
import com.github.tomakehurst.wiremock.client.WireMock.aResponse
import com.github.tomakehurst.wiremock.client.WireMock.post
import com.github.tomakehurst.wiremock.client.WireMock.stubFor
import com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching
import com.nimbusds.jose.JOSEObjectType
import no.nav.navnosearchadminapi.common.repository.ContentRepository
import no.nav.navnosearchadminapi.consumer.azuread.dto.outbound.TokenResponse
import no.nav.navnosearchadminapi.integrationtests.config.OpensearchConfiguration
import no.nav.navnosearchadminapi.rest.aspect.HeaderCheckAspect.Companion.API_KEY_HEADER
import no.nav.navnosearchadminapi.utils.initialTestData
Expand All @@ -18,6 +24,9 @@ import org.springframework.cache.CacheManager
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock
import org.springframework.context.annotation.Import
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpHeaders.CONTENT_TYPE
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType.APPLICATION_JSON_VALUE
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.testcontainers.junit.jupiter.Testcontainers
Expand All @@ -31,6 +40,9 @@ import org.testcontainers.junit.jupiter.Testcontainers
@EnableMockOAuth2Server
abstract class AbstractIntegrationTest {

@Autowired
lateinit var objectMapper: ObjectMapper

@Autowired
lateinit var restTemplate: TestRestTemplate

Expand All @@ -46,7 +58,8 @@ abstract class AbstractIntegrationTest {
@LocalServerPort
var serverPort: Int? = null

@Value("\${api-key}") lateinit var apiKey: String
@Value("\${api-key}")
lateinit var apiKey: String

fun host() = "http://localhost:$serverPort"

Expand All @@ -57,6 +70,16 @@ abstract class AbstractIntegrationTest {
repository.saveAll(initialTestData)
}

fun mockAzuread() {
stubFor(
post(urlPathMatching("/azuread")).willReturn(
aResponse().withStatus(HttpStatus.OK.value())
.withBody(objectMapper.writeValueAsString(TokenResponse(accessToken = "token")))
.withHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
)
)
}

fun headers(isAuthValid: Boolean = true, isApiKeyValid: Boolean = true): HttpHeaders {
val headers = HttpHeaders()
val token = if (isAuthValid) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package no.nav.navnosearchadminapi.integrationtests

import com.fasterxml.jackson.databind.ObjectMapper
import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.client.WireMock.aResponse
import com.github.tomakehurst.wiremock.client.WireMock.get
Expand All @@ -24,12 +23,11 @@ import org.springframework.http.ResponseEntity

class AdminIntegrationTest : AbstractIntegrationTest() {

val objectMapper = ObjectMapper()

@BeforeEach
fun setup() {
WireMock.reset()
setupIndex()
mockAzuread()
stubFor(
get(urlPathMatching("/kodeverk")).willReturn(
aResponse().withStatus(HttpStatus.OK.value())
Expand Down
3 changes: 3 additions & 0 deletions app/src/test/resources/application-test.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
kodeverk:
spraak:
url: http://localhost:${wiremock.server.port}/kodeverk
scope: kodeverk-scope

no.nav.security.jwt:
issuer:
azuread:
discoveryurl: http://localhost:${mock-oauth2-server.port}/azuread/.well-known/openid-configuration
accepted-audience: someaudience
client-secret: somesecret
token-endpoint: http://localhost:${wiremock.server.port}/azuread

api-key: dummy