Skip to content

Commit

Permalink
Flytt over relevant kode fra familie-felles http
Browse files Browse the repository at this point in the history
  • Loading branch information
sillerud committed Feb 25, 2025
1 parent 4768140 commit fe7057f
Show file tree
Hide file tree
Showing 19 changed files with 365 additions and 73 deletions.
9 changes: 7 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,19 @@
</dependency>
<dependency>
<groupId>no.nav.security</groupId>
<artifactId>token-validation-core</artifactId>
<artifactId>token-client-spring</artifactId>
<version>${token-validation-spring.version}</version>
</dependency>
<dependency>
<groupId>no.nav.familie.felles</groupId>
<artifactId>http-client</artifactId>
<artifactId>log</artifactId>
<version>${felles.version}</version>
</dependency>
<dependency>
<groupId>no.nav.security</groupId>
<artifactId>token-validation-core</artifactId>
<version>${token-validation-spring.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <!-- Mulig fiks av snyk-meldinger som gjelder logback-syslog4j -->
Expand Down
30 changes: 3 additions & 27 deletions src/main/kotlin/no/nav/familie/tilbake/config/ApplicationConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@ package no.nav.familie.tilbake.config

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import no.nav.familie.http.client.RetryOAuth2HttpClient
import no.nav.familie.http.config.RestTemplateAzure
import no.nav.familie.log.filter.LogFilter
import no.nav.familie.prosessering.config.ProsesseringInfoProvider
import no.nav.security.token.support.client.core.http.OAuth2HttpClient
import no.nav.security.token.support.client.core.oauth2.OAuth2AccessTokenResponse
import no.nav.security.token.support.client.spring.oauth2.EnableOAuth2Client
import no.nav.security.token.support.spring.SpringTokenValidationContextHolder
import no.nav.security.token.support.spring.api.EnableJwtTokenValidation
Expand All @@ -21,19 +17,16 @@ import org.springframework.boot.web.servlet.server.ServletWebServerFactory
import org.springframework.cache.annotation.EnableCaching
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Import
import org.springframework.context.annotation.Primary
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.web.client.RestClient
import org.springframework.web.client.RestTemplate
import java.time.Duration
import java.time.temporal.ChronoUnit

@SpringBootConfiguration
@ComponentScan(ApplicationConfig.PAKKE_NAVN, "no.nav.familie.sikkerhet", "no.nav.familie.prosessering", "no.nav.familie.unleash")
@ComponentScan(ApplicationConfig.PAKKE_NAVN, "no.nav.familie.prosessering", "no.nav.familie.unleash")
@EnableJwtTokenValidation(ignore = ["org.springframework", "org.springdoc"])
@Import(RestTemplateAzure::class)
@EnableOAuth2Client(cacheEnabled = true)
@EnableScheduling
@EnableCaching
Expand Down Expand Up @@ -65,28 +58,11 @@ class ApplicationConfig {
fun restTemplateBuilder(objectMapper: ObjectMapper): RestTemplateBuilder {
val jackson2HttpMessageConverter = MappingJackson2HttpMessageConverter(objectMapper)
return RestTemplateBuilder()
.setConnectTimeout(Duration.of(2, ChronoUnit.SECONDS))
.setReadTimeout(Duration.of(30, ChronoUnit.SECONDS))
.connectTimeout(Duration.of(2, ChronoUnit.SECONDS))
.readTimeout(Duration.of(30, ChronoUnit.SECONDS))
.additionalMessageConverters(listOf(jackson2HttpMessageConverter) + RestTemplate().messageConverters)
}

/**
* Overskriver OAuth2HttpClient som settes opp i token-support som ikke kan få med objectMapper fra felles
* pga. .setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
* og [OAuth2AccessTokenResponse] som burde settes med setters, då feltnavn heter noe annet enn feltet i json
*/
@Bean
@Primary
fun oAuth2HttpClient(): OAuth2HttpClient =
RetryOAuth2HttpClient(
RestClient.create(
RestTemplateBuilder()
.setConnectTimeout(Duration.of(2, ChronoUnit.SECONDS))
.setReadTimeout(Duration.of(4, ChronoUnit.SECONDS))
.build(),
),
)

@Bean
fun prosesseringInfoProvider(
@Value("\${rolle.prosessering}") prosesseringRolle: String,
Expand Down
38 changes: 38 additions & 0 deletions src/main/kotlin/no/nav/familie/tilbake/config/HttpClientConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package no.nav.familie.tilbake.config

import no.nav.familie.tilbake.http.BearerTokenClientInterceptor
import no.nav.familie.tilbake.http.ConsumerIdClientInterceptor
import no.nav.familie.tilbake.http.MdcValuesPropagatingClientInterceptor
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.client.RestOperations

@Configuration
class HttpClientConfig {
private val restTemplateBuilder = RestTemplateBuilder()

@Bean("azure")
fun restTemplateEntraIDBearer(
consumerIdClientInterceptor: ConsumerIdClientInterceptor,
bearerTokenClientInterceptor: BearerTokenClientInterceptor,
): RestOperations =
restTemplateBuilder
.additionalInterceptors(
consumerIdClientInterceptor,
bearerTokenClientInterceptor,
MdcValuesPropagatingClientInterceptor(),
).build()

@Bean("azureClientCredential")
fun restTemplateClientCredentialEntraIdBearer(
consumerIdClientInterceptor: ConsumerIdClientInterceptor,
bearerTokenClientInterceptor: BearerTokenClientInterceptor,
): RestOperations =
restTemplateBuilder
.additionalInterceptors(
consumerIdClientInterceptor,
bearerTokenClientInterceptor,
MdcValuesPropagatingClientInterceptor(),
).build()
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class JournalføringService(
fun hentJournalposter(behandlingId: UUID): List<Journalpost> {
val behandling = behandlingRepository.findById(behandlingId).orElseThrow()
val fagsak = behandling.let { fagsakRepository.findById(it.fagsakId).orElseThrow() }
val logContext = SecureLog.Context.medBehandling(fagsak.eksternFagsakId, behandling.id.toString())
val journalposter =
fagsak.let {
integrasjonerClient.hentJournalposterForBruker(
Expand All @@ -56,6 +57,7 @@ class JournalføringService(
),
tema = listOf(hentTema(fagsystem = fagsak.fagsystem)),
),
logContext,
)
}
return journalposter.filter { it.sak?.fagsakId == fagsak.eksternFagsakId }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package no.nav.familie.tilbake.dokumentbestilling.felles.pdf

import no.nav.familie.http.client.RessursException
import no.nav.familie.kontrakter.felles.dokdist.Distribusjonstidspunkt
import no.nav.familie.kontrakter.felles.dokdist.Distribusjonstype
import no.nav.familie.kontrakter.felles.objectMapper
Expand All @@ -16,6 +15,7 @@ import no.nav.familie.tilbake.dokumentbestilling.felles.header.TekstformatererHe
import no.nav.familie.tilbake.dokumentbestilling.felles.task.PubliserJournalpostTask
import no.nav.familie.tilbake.dokumentbestilling.felles.task.PubliserJournalpostTaskData
import no.nav.familie.tilbake.dokumentbestilling.fritekstbrev.JournalpostIdOgDokumentId
import no.nav.familie.tilbake.http.RessursException
import no.nav.familie.tilbake.log.SecureLog
import no.nav.familie.tilbake.micrometer.TellerService
import no.nav.familie.tilbake.pdfgen.Dokumentvariant
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package no.nav.familie.tilbake.dokumentbestilling.felles.task

import no.nav.familie.http.client.RessursException
import no.nav.familie.kontrakter.felles.Fagsystem
import no.nav.familie.kontrakter.felles.dokdist.Distribusjonstidspunkt
import no.nav.familie.kontrakter.felles.dokdist.Distribusjonstype
Expand All @@ -12,6 +11,7 @@ import no.nav.familie.tilbake.dokumentbestilling.felles.Brevmottager
import no.nav.familie.tilbake.dokumentbestilling.felles.domain.Brevtype
import no.nav.familie.tilbake.historikkinnslag.HistorikkService
import no.nav.familie.tilbake.historikkinnslag.TilbakekrevingHistorikkinnslagstype
import no.nav.familie.tilbake.http.RessursException
import no.nav.familie.tilbake.integration.familie.IntegrasjonerClient
import no.nav.familie.tilbake.log.SecureLog.Context.Companion.logContext
import no.nav.familie.tilbake.log.TracedLogger
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package no.nav.familie.tilbake.dokumentbestilling.felles.task

import no.nav.familie.http.client.RessursException
import no.nav.familie.kontrakter.felles.Fagsystem
import no.nav.familie.kontrakter.felles.dokdist.Distribusjonstidspunkt
import no.nav.familie.kontrakter.felles.dokdist.Distribusjonstype
Expand All @@ -10,6 +9,7 @@ import no.nav.familie.prosessering.AsyncTaskStep
import no.nav.familie.prosessering.TaskStepBeskrivelse
import no.nav.familie.prosessering.domene.Task
import no.nav.familie.tilbake.behandling.task.TracableTaskService
import no.nav.familie.tilbake.http.RessursException
import no.nav.familie.tilbake.integration.familie.IntegrasjonerClient
import no.nav.familie.tilbake.log.SecureLog
import no.nav.familie.tilbake.log.SecureLog.Context.Companion.logContext
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package no.nav.familie.tilbake.http

import com.nimbusds.oauth2.sdk.GrantType
import no.nav.security.token.support.client.core.ClientProperties
import no.nav.security.token.support.client.core.oauth2.OAuth2AccessTokenService
import no.nav.security.token.support.client.spring.ClientConfigurationProperties
import no.nav.security.token.support.core.exceptions.JwtTokenValidatorException
import org.springframework.http.client.ClientHttpRequestInterceptor
import java.net.URI

abstract class AbstractBearerTokenInterceptor(
protected val oAuth2AccessTokenService: OAuth2AccessTokenService,
protected val clientConfigurationProperties: ClientConfigurationProperties,
) : ClientHttpRequestInterceptor {
protected fun genererAccessToken(clientProperties: ClientProperties): String =
oAuth2AccessTokenService
.getAccessToken(clientProperties)
.access_token ?: throw JwtTokenValidatorException("Kunne ikke hente accesstoken")

protected fun ClientConfigurationProperties.findByURI(uri: URI) =
registration
.values
.filter { uri.toString().startsWith(it.resourceUrl.toString()) }

protected fun clientPropertiesForGrantType(
values: List<ClientProperties>,
grantType: GrantType,
uri: URI,
): ClientProperties =
values.firstOrNull { grantType == it.grantType }
?: error("could not find oauth2 client config for uri=$uri and grant type=$grantType")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import com.fasterxml.jackson.module.kotlin.readValue
import io.micrometer.core.instrument.Counter
import io.micrometer.core.instrument.Metrics
import io.micrometer.core.instrument.Timer
import no.nav.familie.kontrakter.felles.Ressurs
import no.nav.familie.kontrakter.felles.objectMapper
import no.nav.familie.tilbake.http.RessursException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
import org.springframework.http.ResponseEntity
import org.springframework.web.client.HttpClientErrorException
import org.springframework.web.client.HttpServerErrorException
import org.springframework.web.client.ResourceAccessException
import org.springframework.web.client.RestClientResponseException
import org.springframework.web.client.RestOperations
import org.springframework.web.client.exchange
import java.net.URI
import java.util.concurrent.TimeUnit

abstract class AbstractPingableRestClient(
val operations: RestOperations,
metricsPrefix: String,
) {
private val secureLogger = LoggerFactory.getLogger("secureLogger")
protected val log: Logger = LoggerFactory.getLogger(this::class.java)
private val responstid: Timer = Metrics.timer("$metricsPrefix.tid")
private val responsSuccess: Counter = Metrics.counter("$metricsPrefix.response", "status", "success")
private val responsFailure: Counter = Metrics.counter("$metricsPrefix.response", "status", "failure")

inline fun <reified T : Any> getForEntity(
uri: URI,
httpHeaders: HttpHeaders? = null,
): T = executeMedMetrics(uri) { operations.exchange<T>(uri, HttpMethod.GET, HttpEntity(null, httpHeaders)) }

inline fun <reified T : Any> postForEntity(
uri: URI,
payload: Any,
httpHeaders: HttpHeaders? = null,
): T = executeMedMetrics(uri) { operations.exchange<T>(uri, HttpMethod.POST, HttpEntity(payload, httpHeaders)) }

inline fun <reified T : Any> putForEntity(
uri: URI,
payload: Any,
httpHeaders: HttpHeaders? = null,
): T = executeMedMetrics(uri) { operations.exchange<T>(uri, HttpMethod.PUT, HttpEntity(payload, httpHeaders)) }

inline fun <reified T : Any> patchForEntity(
uri: URI,
payload: Any,
httpHeaders: HttpHeaders? = null,
): T = executeMedMetrics(uri) { operations.exchange<T>(uri, HttpMethod.PATCH, HttpEntity(payload, httpHeaders)) }

inline fun <reified T : Any> deleteForEntity(
uri: URI,
payload: Any? = null,
httpHeaders: HttpHeaders? = null,
): T = executeMedMetrics(uri) { operations.exchange<T>(uri, HttpMethod.DELETE, HttpEntity(payload, httpHeaders)) }

private fun <T> validerOgPakkUt(
response: ResponseEntity<T>,
uri: URI,
): T {
if (!response.statusCode.is2xxSuccessful) {
secureLogger.info("Kall mot {} feilet: {}", uri.toString(), response.body?.toString())
log.info("Kall mot {} feilet: {}", uri.toString(), response.statusCode.toString())
throw HttpServerErrorException(response.statusCode, "", response.body?.toString()?.toByteArray(), Charsets.UTF_8)
}
return response.body as T
}

fun <T> executeMedMetrics(
uri: URI,
function: () -> ResponseEntity<T>,
): T {
try {
val startTime = System.nanoTime()
val responseEntity = function.invoke()
responstid.record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS)
responsSuccess.increment()
return validerOgPakkUt(responseEntity, uri)
} catch (e: RestClientResponseException) {
responsFailure.increment()
secureLogger.warn("RestClientResponseException ved kall mot uri={}", uri.toString(), e)
lesRessurs(e)?.let { throw RessursException(it, e) } ?: throw e
} catch (e: HttpClientErrorException) {
responsFailure.increment()
secureLogger.warn("HttpClientErrorException ved kall mot uri={}", uri.toString(), e)
lesRessurs(e)?.let { throw RessursException(it, e) } ?: throw e
} catch (e: ResourceAccessException) {
responsFailure.increment()
secureLogger.warn("ResourceAccessException ved kall mot uri={}", uri.toString(), e)
throw e
} catch (e: Exception) {
responsFailure.increment()
secureLogger.warn("Feil ved kall mot uri={}", uri.toString(), e)
throw RuntimeException("Feil ved kall mot uri=$uri", e)
}
}

private fun lesRessurs(e: RestClientResponseException): Ressurs<Any>? =
try {
if (e.responseBodyAsString.contains("status")) {
objectMapper.readValue<Ressurs<Any>>(e.responseBodyAsString)
} else {
null
}
} catch (ex: Exception) {
null
}

override fun toString(): String = this::class.simpleName + " [operations=" + operations + "]"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package no.nav.familie.tilbake.http

import com.nimbusds.oauth2.sdk.GrantType
import no.nav.security.token.support.client.core.oauth2.OAuth2AccessTokenService
import no.nav.security.token.support.client.spring.ClientConfigurationProperties
import org.springframework.http.HttpRequest
import org.springframework.http.client.ClientHttpRequestExecution
import org.springframework.http.client.ClientHttpResponse
import org.springframework.stereotype.Component

@Component
class BearerTokenClientCredentialsClientInterceptor(
oAuth2AccessTokenService: OAuth2AccessTokenService,
clientConfigurationProperties: ClientConfigurationProperties,
) : AbstractBearerTokenInterceptor(oAuth2AccessTokenService, clientConfigurationProperties) {
override fun intercept(
request: HttpRequest,
body: ByteArray,
execution: ClientHttpRequestExecution,
): ClientHttpResponse {
val clientProperties = clientPropertiesForGrantType(clientConfigurationProperties.findByURI(request.uri), GrantType.CLIENT_CREDENTIALS, request.uri)
request.headers.setBearerAuth(
genererAccessToken(clientProperties),
)
return execution.execute(request, body)
}
}
Loading

0 comments on commit fe7057f

Please sign in to comment.