Skip to content

Commit

Permalink
Virksomhetapi (#53)
Browse files Browse the repository at this point in the history
* Draft with tests for VirksomhetController.

* Cleanup round for VirksomhetController.

* Better naming for VirksomhetAPI.

* Remove unused class, and use only one version for token validation.

* Remove unused classes

* Add missing ereg_baseurl

* Refactor, and apply check inside data class.

* Enable observability in dev

* Remove unnecessary CacheConfig and related classes.

* Get back RedisConfig to fix deploy.

* Get back @Cacheable already was there.

* Use @Cacheable for ereg_client.

* Use new variables from Ereg and refactoring by removing Inject because it's solved by newer version of Spring-boot.

* Refactoring.
  • Loading branch information
MalazAlkoj authored Jun 26, 2024
1 parent a978ca9 commit ae34f55
Show file tree
Hide file tree
Showing 14 changed files with 266 additions and 1 deletion.
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,13 @@ dependencies {
implementation("com.zaxxer:HikariCP:$hikariVersion")
implementation("org.springframework.boot:spring-boot-starter-logging")
implementation("net.logstash.logback:logstash-logback-encoder:$logstashLogbackEncoderVersion")
implementation("javax.inject:javax.inject:1")
developmentOnly("org.springframework.boot:spring-boot-devtools")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")


testImplementation("junit:junit")
testImplementation("no.nav.security:token-validation-spring-test:$tokenSupportVersion")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.mockk:mockk:$mockkVersion")
testImplementation("io.kotest:kotest-runner-junit5-jvm:$kotestVersion")
Expand Down
7 changes: 7 additions & 0 deletions nais/nais-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ spec:
prometheus:
enabled: true
path: /internal/prometheus
observability:
autoInstrumentation:
enabled: true
runtime: java
ingresses:
- "https://oppfolgingsplan-backend.intern.dev.nav.no"
resources:
Expand All @@ -45,6 +49,7 @@ spec:
outbound:
external:
- host: "pdl-api.dev-fss-pub.nais.io"
- host: "ereg-services-q1.dev-fss-pub.nais.io"
rules:
- application: digdir-krr-proxy
namespace: team-rocket
Expand Down Expand Up @@ -105,6 +110,8 @@ spec:
value: "dev-fss.pdl.pdl-api"
- name: PDL_URL
value: "https://pdl-api.dev-fss-pub.nais.io/graphql"
- name: EREG_URL
value: https://ereg-services-q1.dev-fss-pub.nais.io
redis:
- instance: oppfolgingsplan
access: readwrite
3 changes: 3 additions & 0 deletions nais/nais-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ spec:
outbound:
external:
- host: "pdl-api.prod-fss-pub.nais.io"
- host: "ereg-services.prod-fss-pub.nais.io"
rules:
- application: digdir-krr-proxy
namespace: team-rocket
Expand Down Expand Up @@ -109,6 +110,8 @@ spec:
value: "prod-fss.pdl.pdl-api"
- name: PDL_URL
value: "https://pdl-api.prod-fss-pub.nais.io/graphql"
- name: EREG_URL
value: https://ereg-services.prod-fss-pub.nais.io
redis:
- instance: oppfolgingsplan
access: readwrite
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import org.springframework.stereotype.Service
class BrukertilgangService @Autowired constructor(
private var brukertilgangClient: BrukertilgangClient
) {

@Cacheable(
cacheNames = ["tilgangtilident"],
key = "#innloggetIdent.concat(#oppslaattFnr)",
Expand Down
6 changes: 6 additions & 0 deletions src/main/kotlin/no/nav/syfo/domain/Virksomhet.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package no.nav.syfo.domain

data class Virksomhet(
val virksomhetsnummer: String,
val navn: String = ""
)
11 changes: 11 additions & 0 deletions src/main/kotlin/no/nav/syfo/domain/Virksomhetsnummer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package no.nav.syfo.domain

data class Virksomhetsnummer(val value: String) {
private val nineDigits = Regex("^\\d{9}$")

init {
require(nineDigits.matches(value)) {
"Value is not a valid Virksomhetsnummer"
}
}
}
72 changes: 72 additions & 0 deletions src/main/kotlin/no/nav/syfo/ereg/EregClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package no.nav.syfo.ereg

import no.nav.syfo.metric.Metrikk
import no.nav.syfo.util.APP_CONSUMER_ID
import no.nav.syfo.util.NAV_CALL_ID_HEADER
import no.nav.syfo.util.NAV_CONSUMER_ID_HEADER
import no.nav.syfo.util.createCallId
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.annotation.Cacheable
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
import org.springframework.http.MediaType
import org.springframework.stereotype.Service
import org.springframework.web.client.RestClientResponseException
import org.springframework.web.client.RestTemplate

@Service
class EregClient (
@Value("\${ereg.url}") private val baseUrl: String,
private val metric: Metrikk,
) {
fun eregResponse(virksomhetsnummer: String): EregOrganisasjonResponse {
try {
val response = RestTemplate().exchange(
getEregUrl(),
HttpMethod.GET,
entity(),
EregOrganisasjonResponse::class.java,
virksomhetsnummer,
)
val eregResponse = response.body!!
metric.tellHendelse(METRIC_CALL_EREG_SUCCESS)
return eregResponse
} catch (e: RestClientResponseException) {
metric.tellHendelse(METRIC_CALL_EREG_FAIL)
val message =
"Call to get name Virksomhetsnummer from EREG failed with status:" +
" ${e.statusCode.value()} and message: ${e.responseBodyAsString}"
LOG.error(message)
throw e
}
}
@Cacheable(
value = ["ereg_virksomhetsnavn"],
key = "#virksomhetsnummer",
condition = "#virksomhetsnummer != null",
)
fun virksomhetsnavn(virksomhetsnummer: String): String {
return eregResponse(virksomhetsnummer).navn()
}

private fun getEregUrl(): String {
return "$baseUrl/ereg/api/v2/organisasjon/{virksomhetsnummer}"
}

private fun entity(): HttpEntity<String> {
val headers = HttpHeaders()
headers.contentType = MediaType.APPLICATION_JSON
headers[NAV_CONSUMER_ID_HEADER] = APP_CONSUMER_ID
headers[NAV_CALL_ID_HEADER] = createCallId()
return HttpEntity(headers)
}

companion object {
private val LOG = LoggerFactory.getLogger(EregClient::class.java)

private const val METRIC_CALL_EREG_SUCCESS = "call_ereg_success"
private const val METRIC_CALL_EREG_FAIL = "call_ereg_fail"
}
}
24 changes: 24 additions & 0 deletions src/main/kotlin/no/nav/syfo/ereg/EregOrganisasjonResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package no.nav.syfo.ereg

import com.fasterxml.jackson.annotation.JsonIgnoreProperties

@JsonIgnoreProperties(ignoreUnknown = true)
data class EregOrganisasjonResponse(
val navn: EregOrganisasjonNavn
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class EregOrganisasjonNavn(
val navnelinje1: String,
val sammensattnavn: String?
)

fun EregOrganisasjonResponse.navn(): String {
return this.navn.let {
if (it.sammensattnavn?.isNotEmpty() == true) {
it.sammensattnavn
} else {
it.navnelinje1
}
}
}
2 changes: 2 additions & 0 deletions src/main/kotlin/no/nav/syfo/util/StringUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ fun fodselsnummerInvalid(fnr: String): Boolean = !fodselsnummerValid(fnr)
fun String.lowerCapitalize(): String {
return this.lowercase().replaceFirstChar { it.uppercase() }
}


56 changes: 56 additions & 0 deletions src/main/kotlin/no/nav/syfo/virksomhet/VirksomhetController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package no.nav.syfo.virksomhet

import no.nav.security.token.support.core.api.ProtectedWithClaims
import no.nav.security.token.support.core.context.TokenValidationContextHolder
import no.nav.syfo.auth.tokenx.TokenXUtil.TokenXIssuer.TOKENX
import no.nav.syfo.ereg.EregClient
import no.nav.syfo.domain.Virksomhet
import no.nav.syfo.auth.tokenx.TokenXUtil
import no.nav.syfo.domain.Virksomhetsnummer
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController


@RestController
@ProtectedWithClaims(issuer = TOKENX, claimMap = ["acr=Level4", "acr=idporten-loa-high"], combineWithOr = true)
@RequestMapping(value = ["/api/v1/virksomhet/{virksomhetsnummer}"])
class VirksomhetController(
private val contextHolder: TokenValidationContextHolder,
private val eregClient: EregClient,
@Value("\${oppfolgingsplan.frontend.client.id}")
private val oppfolgingsplanClientId: String,
) {
@GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
fun getVirksomhet(
@PathVariable("virksomhetsnummer") virksomhetsnummer: String,
): ResponseEntity<Virksomhet> {
TokenXUtil.validateTokenXClaims(contextHolder, oppfolgingsplanClientId)

return try {
val virksomhetsnummerAsObject = Virksomhetsnummer(virksomhetsnummer)
ResponseEntity
.status(HttpStatus.OK)
.body(
Virksomhet(
virksomhetsnummer = virksomhetsnummerAsObject.value,
navn = eregClient.virksomhetsnavn(virksomhetsnummer),
),
)
} catch (e: IllegalArgumentException) {
LOG.warn("${e.message}, Invalid virksomhetsnummer: $virksomhetsnummer")
ResponseEntity.status(HttpStatus.BAD_REQUEST).build()
}
}

companion object {
private val LOG = LoggerFactory.getLogger(VirksomhetController::class.java)
}

}
13 changes: 13 additions & 0 deletions src/test/kotlin/no/nav/syfo/LocalApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package no.nav.syfo

import no.nav.security.token.support.spring.api.EnableJwtTokenValidation
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@EnableJwtTokenValidation
@SpringBootApplication
class LocalApplication

fun main(args: Array<String>) {
runApplication<LocalApplication>(*args)
}
5 changes: 5 additions & 0 deletions src/test/kotlin/no/nav/syfo/testhelper/UserConstants.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package no.nav.syfo.testhelper

object UserConstants {
const val VIRKSOMHETSNUMMER = "123456789"
}
61 changes: 61 additions & 0 deletions src/test/kotlin/no/nav/syfo/virksomhet/VirksomhetControllerTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package no.nav.syfo.virksomhet

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import no.nav.security.token.support.core.context.TokenValidationContext
import no.nav.security.token.support.core.context.TokenValidationContextHolder
import no.nav.security.token.support.core.jwt.JwtTokenClaims
import no.nav.syfo.domain.Virksomhet
import no.nav.syfo.ereg.EregClient
import no.nav.syfo.testhelper.UserConstants.VIRKSOMHETSNUMMER
import org.springframework.http.ResponseEntity
import org.springframework.http.HttpStatus

class VirksomhetControllerTest : FunSpec({

val contextHolder = mockk<TokenValidationContextHolder>()
val mockTokenValidationContext = mockk<TokenValidationContext>()
val mockJwtTokenClaims = mockk<JwtTokenClaims>()

val eregClient = mockk<EregClient>()
val virksomhetsNavn = "Tull og fanteri AS"
val virksomhet = Virksomhet(VIRKSOMHETSNUMMER, virksomhetsNavn)

val virksomhetController = VirksomhetController(
contextHolder = contextHolder,
eregClient = eregClient,
oppfolgingsplanClientId = "123456789"
)

beforeTest {
every { contextHolder.getTokenValidationContext() } returns mockTokenValidationContext
every { mockTokenValidationContext.getClaims("tokenx") } returns mockJwtTokenClaims
every { mockJwtTokenClaims.getStringClaim("client_id") } returns "123456789"
every { eregClient.virksomhetsnavn(VIRKSOMHETSNUMMER) } returns virksomhetsNavn
}

test("should return virksomhet with valid virksomhetsnummer") {
val res: ResponseEntity<Virksomhet> = virksomhetController.getVirksomhet(VIRKSOMHETSNUMMER)
val body = res.body

res.statusCode shouldBe HttpStatus.OK
body?.virksomhetsnummer shouldBe virksomhet.virksomhetsnummer
body?.navn shouldBe virksomhet.navn
}

test("should return 400 status code with invalid virksomhetsnummer") {
val res: ResponseEntity<Virksomhet> = virksomhetController.getVirksomhet("12345678")

res.statusCode shouldBe HttpStatus.BAD_REQUEST
res.body shouldBe null
}

test("should return 400 status code with invalid contains numeric virksomhetsnummer") {
val res: ResponseEntity<Virksomhet> = virksomhetController.getVirksomhet("a12345678")

res.statusCode shouldBe HttpStatus.BAD_REQUEST
res.body shouldBe null
}
})
1 change: 1 addition & 0 deletions src/test/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ azure.openid.config.token.endpoint: "http://azure"
azure.app.client.id: 'client.id'
azure.app.client.secret: 'client.secret'
security.token.service.rest.url: "http://security-token-service.url"
ereg.url: "http://ereg"

krr.url: "http://krr.url"
krr.scope: "krr.scope"

0 comments on commit ae34f55

Please sign in to comment.