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 1cb5bdf
Show file tree
Hide file tree
Showing 19 changed files with 356 additions and 67 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
34 changes: 5 additions & 29 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,6 @@ 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 +15,18 @@ 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
import no.nav.familie.log.filter.LogFilter
import no.nav.familie.prosessering.config.ProsesseringInfoProvider

@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
36 changes: 36 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,36 @@
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 @@ -26,6 +25,7 @@ import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import java.util.Base64
import java.util.Properties
import no.nav.familie.tilbake.http.RessursException

@Service
class PdfBrevService(
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 @@ -19,6 +18,7 @@ import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import java.time.LocalDateTime
import java.util.UUID
import no.nav.familie.tilbake.http.RessursException

const val ANTALL_SEKUNDER_I_EN_UKE = 604800L

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 @@ -17,6 +16,7 @@ import no.nav.familie.tilbake.log.TracedLogger
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import java.util.UUID
import no.nav.familie.tilbake.http.RessursException

@Service
@TaskStepBeskrivelse(
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 java.net.URI
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

abstract class AbstractBearerTokenInterceptor(
protected val oAuth2AccessTokenService: OAuth2AccessTokenService,
protected val clientConfigurationProperties: ClientConfigurationProperties,
) : ClientHttpRequestInterceptor {
protected fun genererAccessToken(clientProperties: ClientProperties): String {
return 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 java.net.URI
import java.util.concurrent.TimeUnit
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

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(
respons: ResponseEntity<T>,
uri: URI,
): T {
if (!respons.statusCode.is2xxSuccessful) {
secureLogger.info("Kall mot $uri feilet: ${respons.body}")
log.info("Kall mot $uri feilet: ${respons.statusCode}")
throw HttpServerErrorException(respons.statusCode, "", respons.body?.toString()?.toByteArray(), Charsets.UTF_8)
}
return respons.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", e)
lesRessurs(e)?.let { throw RessursException(it, e) } ?: throw e
} catch (e: HttpClientErrorException) {
responsFailure.increment()
secureLogger.warn("HttpClientErrorException ved kall mot uri=$uri", e)
lesRessurs(e)?.let { throw RessursException(it, e) } ?: throw e
} catch (e: ResourceAccessException) {
responsFailure.increment()
secureLogger.warn("ResourceAccessException ved kall mot uri=$uri", e)
throw e
} catch (e: Exception) {
responsFailure.increment()
secureLogger.warn("Feil ved kall mot uri=$uri", 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 1cb5bdf

Please sign in to comment.