diff --git a/felles/klient/pom.xml b/felles/klient/pom.xml new file mode 100644 index 000000000..6f4df7afc --- /dev/null +++ b/felles/klient/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + + felles + no.nav.foreldrepenger.felles + 0.0.0-SNAPSHOT + + + felles-klient + + + + no.nav.foreldrepenger.felles + felles-feil + + + no.nav.foreldrepenger.felles + felles-log + + + + diff --git a/felles/klient/src/main/java/no/nav/vedtak/klient/http/DefaultHttpKlient.java b/felles/klient/src/main/java/no/nav/vedtak/klient/http/DefaultHttpKlient.java new file mode 100644 index 000000000..e43dd4b5f --- /dev/null +++ b/felles/klient/src/main/java/no/nav/vedtak/klient/http/DefaultHttpKlient.java @@ -0,0 +1,93 @@ +package no.nav.vedtak.klient.http; + +import java.io.IOException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Optional; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import no.nav.vedtak.exception.IntegrasjonException; + +public final class DefaultHttpKlient { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultHttpKlient.class); + + private static DefaultHttpKlient CLIENT; + + private final HttpClient httpClient; + + private DefaultHttpKlient() { + this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(15)).proxy(HttpClient.Builder.NO_PROXY).build(); + } + + public static synchronized DefaultHttpKlient client() { + var inst= CLIENT; + if (inst == null) { + inst = new DefaultHttpKlient(); + CLIENT = inst; + } + return inst; + } + + public String send(HttpRequest request) { + return ResponseHandler.handleResponse(doSendExpectStringRetry(request), request.uri(), Set.of()); + } + + public String send(HttpRequest request, Set permits) { + return ResponseHandler.handleResponse(doSendExpectStringRetry(request), request.uri(), permits); + } + + public Optional sendHandleResponse(HttpRequest request) { + return Optional.ofNullable(ResponseHandler.handleResponse(doSendExpectBytearrayRetry(request), request.uri(), Set.of())); + } + + public HttpResponse sendNoResponseHandler(HttpRequest request) { + return doSendExpectStringRetry(request); + } + + private HttpResponse doSendExpectBytearrayRetry(HttpRequest request) { + try { + return doSendExpectBytearray(request); + } catch (IntegrasjonException e) { + LOG.info("F-157390 IntegrasjonException ved første kall til endepunkt {}", request.uri(), e); + } + return doSendExpectBytearray(request); + } + + private HttpResponse doSendExpectStringRetry(HttpRequest request) { + try { + return doSendExpectString(request); + } catch (IntegrasjonException e) { + LOG.info("F-157390 IntegrasjonException ved første kall til endepunkt {}", request.uri(), e); + } + return doSendExpectString(request); + } + + private HttpResponse doSendExpectBytearray(HttpRequest request) { + try { + return httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray()); + } catch (IOException e) { + throw new IntegrasjonException("F-157391", "Uventet IO-exception mot endepunkt", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IntegrasjonException("F-157392", "InterruptedException ved kall mot endepunkt", e); + } + } + + private HttpResponse doSendExpectString(HttpRequest request) { + try { + return httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException e) { + throw new IntegrasjonException("F-157391", "Uventet IO-exception mot endepunkt", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IntegrasjonException("F-157392", "InterruptedException ved kall mot endepunkt", e); + } + } + +} diff --git a/felles/klient/src/main/java/no/nav/vedtak/klient/http/DefaultRequest.java b/felles/klient/src/main/java/no/nav/vedtak/klient/http/DefaultRequest.java new file mode 100644 index 000000000..dad0f1857 --- /dev/null +++ b/felles/klient/src/main/java/no/nav/vedtak/klient/http/DefaultRequest.java @@ -0,0 +1,45 @@ +package no.nav.vedtak.klient.http; + +import java.net.http.HttpRequest; +import java.time.Duration; +import java.util.function.Supplier; + +import no.nav.vedtak.log.mdc.MDCOperations; + +public final class DefaultRequest { + + private static final String HEADER_NAV_CONSUMER_ID = "Nav-Consumer-Id"; + + private static final String AUTHORIZATION = "Authorization"; + private static final String BASIC_AUTH_HEADER_PREFIX = "Basic "; + + private static final String CONTENT_TYPE = "Content-Type"; + private static final String APPLICATION_FORM_ENCODED = "application/x-www-form-urlencoded"; + + private static final String HEADER_NAV_CALLID = "Nav-Callid"; + private static final String HEADER_NAV_CALL_ID = "nav-call-id"; + + private DefaultRequest() { + // NOSONAR + } + + public static HttpRequest.Builder builder() { + return HttpRequest.newBuilder() + .timeout(Duration.ofSeconds(10)) + .header(HEADER_NAV_CALLID, MDCOperations.getCallId()) + .header(HEADER_NAV_CALL_ID, MDCOperations.getCallId()); + } + + public static HttpRequest.Builder builderForBasicAuth(Supplier authContent) { + return builder() + .header(CONTENT_TYPE, APPLICATION_FORM_ENCODED) + .header(AUTHORIZATION, BASIC_AUTH_HEADER_PREFIX + authContent.get()); + } + + public static HttpRequest.Builder builderForBasicAuth(Supplier authContent, Supplier consumerId) { + return builder() + .header(HEADER_NAV_CONSUMER_ID, consumerId.get()) + .header(CONTENT_TYPE, APPLICATION_FORM_ENCODED) + .header(AUTHORIZATION, BASIC_AUTH_HEADER_PREFIX + authContent.get()); + } +} diff --git a/felles/klient/src/main/java/no/nav/vedtak/klient/http/ResponseHandler.java b/felles/klient/src/main/java/no/nav/vedtak/klient/http/ResponseHandler.java new file mode 100644 index 000000000..3726da3b7 --- /dev/null +++ b/felles/klient/src/main/java/no/nav/vedtak/klient/http/ResponseHandler.java @@ -0,0 +1,31 @@ +package no.nav.vedtak.klient.http; + +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.http.HttpResponse; +import java.util.Set; + +import no.nav.vedtak.exception.IntegrasjonException; +import no.nav.vedtak.exception.ManglerTilgangException; + +final class ResponseHandler { + + private ResponseHandler() { + // NOSONAR + } + + static W handleResponse(final HttpResponse response, URI endpoint, Set permits) { + int status = response.statusCode(); + if (status == HttpURLConnection.HTTP_NO_CONTENT) { + return null; + } + if ((status >= HttpURLConnection.HTTP_OK && status < HttpURLConnection.HTTP_MULT_CHOICE) || permits.contains(status)) { + return response.body(); + } + if (status == HttpURLConnection.HTTP_FORBIDDEN) { + throw new ManglerTilgangException("F-468816", "Feilet mot " + endpoint); + } + throw new IntegrasjonException("F-468817", String.format("Uventet respons %s fra %s", status, endpoint)); + } + +} diff --git a/felles/klient/src/main/resources/META-INF/beans.xml b/felles/klient/src/main/resources/META-INF/beans.xml new file mode 100644 index 000000000..4788e81b1 --- /dev/null +++ b/felles/klient/src/main/resources/META-INF/beans.xml @@ -0,0 +1,6 @@ + + + diff --git a/felles/oidc/pom.xml b/felles/oidc/pom.xml index df322f196..dd149f343 100644 --- a/felles/oidc/pom.xml +++ b/felles/oidc/pom.xml @@ -26,6 +26,10 @@ no.nav.foreldrepenger.felles felles-mapper + + no.nav.foreldrepenger.felles + felles-klient + no.nav.foreldrepenger konfig diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/impl/OidcProviderConfig.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/impl/OidcProviderConfig.java index fde791682..2fa0b2640 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/impl/OidcProviderConfig.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/impl/OidcProviderConfig.java @@ -57,7 +57,7 @@ public final class OidcProviderConfig { private final Set providers; private final Map issuers; - private static volatile OidcProviderConfig instance; // NOSONAR + private static OidcProviderConfig instance; private OidcProviderConfig() { this(init()); diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/AzureSystemTokenKlient.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/AzureSystemTokenKlient.java index ada92a255..e7cdb1e4a 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/AzureSystemTokenKlient.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/AzureSystemTokenKlient.java @@ -23,7 +23,7 @@ public class AzureSystemTokenKlient { private static final Environment ENV = Environment.current(); private static final Logger LOG = LoggerFactory.getLogger(AzureSystemTokenKlient.class); - private static volatile AzureSystemTokenKlient INSTANCE; // NOSONAR + private static AzureSystemTokenKlient INSTANCE; private final URI tokenEndpoint; private final String clientId; diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXExchangeKlient.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXExchangeKlient.java index 75db25021..f3b3041af 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXExchangeKlient.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXExchangeKlient.java @@ -23,7 +23,7 @@ public final class TokenXExchangeKlient { private static final Logger LOG = LoggerFactory.getLogger(TokenXExchangeKlient.class); - private static volatile TokenXExchangeKlient INSTANCE; // NOSONAR + private static TokenXExchangeKlient INSTANCE; private final Optional configuration; private final String cluster; diff --git a/felles/pom.xml b/felles/pom.xml index 2e825f10a..79dbd6687 100644 --- a/felles/pom.xml +++ b/felles/pom.xml @@ -20,6 +20,7 @@ log util mapper + klient db abac xmlutils @@ -68,6 +69,11 @@ felles-mapper ${project.version} + + no.nav.foreldrepenger.felles + felles-klient + ${project.version} + no.nav.foreldrepenger.felles felles-oidc diff --git a/felles/sikkerhet/src/main/java/no/nav/vedtak/sikkerhet/oidc/OidcTokenValidatorConfig.java b/felles/sikkerhet/src/main/java/no/nav/vedtak/sikkerhet/oidc/OidcTokenValidatorConfig.java index fa51f8836..20aa6bf89 100644 --- a/felles/sikkerhet/src/main/java/no/nav/vedtak/sikkerhet/oidc/OidcTokenValidatorConfig.java +++ b/felles/sikkerhet/src/main/java/no/nav/vedtak/sikkerhet/oidc/OidcTokenValidatorConfig.java @@ -8,7 +8,7 @@ public class OidcTokenValidatorConfig { - private static volatile OidcTokenValidatorConfig instance; // NOSONAR + private static OidcTokenValidatorConfig instance; private final Map validators; private OidcTokenValidatorConfig() { diff --git a/felles/sikkerhet/src/main/java/no/nav/vedtak/tokenx/TokenXAssertionGenerator.java b/felles/sikkerhet/src/main/java/no/nav/vedtak/tokenx/TokenXAssertionGenerator.java index ee7baa679..ff7b04709 100644 --- a/felles/sikkerhet/src/main/java/no/nav/vedtak/tokenx/TokenXAssertionGenerator.java +++ b/felles/sikkerhet/src/main/java/no/nav/vedtak/tokenx/TokenXAssertionGenerator.java @@ -23,7 +23,7 @@ final class TokenXAssertionGenerator { - private static volatile TokenXAssertionGenerator INSTANCE; // NOSONAR + private static TokenXAssertionGenerator INSTANCE; private final Optional configuration; private final RSAKey privateKey; diff --git a/integrasjon/arbeidsfordeling-klient/src/main/java/no/nav/vedtak/felles/integrasjon/arbeidsfordeling/rest/ArbeidsfordelingNativeRestKlient.java b/integrasjon/arbeidsfordeling-klient/src/main/java/no/nav/vedtak/felles/integrasjon/arbeidsfordeling/rest/ArbeidsfordelingNativeRestKlient.java new file mode 100644 index 000000000..b63d900b8 --- /dev/null +++ b/integrasjon/arbeidsfordeling-klient/src/main/java/no/nav/vedtak/felles/integrasjon/arbeidsfordeling/rest/ArbeidsfordelingNativeRestKlient.java @@ -0,0 +1,66 @@ +package no.nav.vedtak.felles.integrasjon.arbeidsfordeling.rest; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import no.nav.foreldrepenger.konfig.KonfigVerdi; +import no.nav.vedtak.exception.IntegrasjonException; +import no.nav.vedtak.felles.integrasjon.rest.NativeKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestRequest; +import no.nav.vedtak.sikkerhet.oidc.token.SikkerhetContext; + +@NativeKlient +@ApplicationScoped +public class ArbeidsfordelingNativeRestKlient implements Arbeidsfordeling { + + private static final String DEFAULT_URI = "https://app.adeo.no/norg2/api/v1/arbeidsfordeling/enheter"; + private static final String BEST_MATCH = "/bestmatch"; + + private RestKlient restKlient; + private URI alleEnheterUri; + private URI besteEnhetUri; + + @Inject + public ArbeidsfordelingNativeRestKlient(RestKlient restKlient, + @KonfigVerdi(value = "arbeidsfordeling.rs.url", defaultVerdi = DEFAULT_URI) URI uri) { + this.restKlient = restKlient; + this.alleEnheterUri = uri; + this.besteEnhetUri = URI.create(uri + BEST_MATCH); + } + + ArbeidsfordelingNativeRestKlient() { + // CDI proxyable + } + + @Override + public List hentAlleAktiveEnheter(ArbeidsfordelingRequest request) { + return hentEnheterFor(request, alleEnheterUri); + } + + @Override + public List finnEnhet(ArbeidsfordelingRequest request) { + return hentEnheterFor(request, besteEnhetUri); + } + + private List hentEnheterFor(ArbeidsfordelingRequest request, URI uri) { + try { + var httpRequest = restKlient.request().builder(SikkerhetContext.BRUKER) + .uri(uri) + .POST(RestRequest.serialiser(request)) + .build(); + var respons = restKlient.send(httpRequest, ArbeidsfordelingResponse[].class); + return Arrays.stream(respons) + .filter(response -> "AKTIV".equalsIgnoreCase(response.status())) + .collect(Collectors.toList()); + } catch (Exception e) { + throw new IntegrasjonException("F-016913", String.format("NORG2 arbeidsfordeling feil ved oppslag mot %s", uri), e); + } + } + +} diff --git a/integrasjon/ereg-klient/src/main/java/no/nav/vedtak/felles/integrasjon/organisasjon/OrganisasjonNativeRestKlient.java b/integrasjon/ereg-klient/src/main/java/no/nav/vedtak/felles/integrasjon/organisasjon/OrganisasjonNativeRestKlient.java new file mode 100644 index 000000000..222c53b28 --- /dev/null +++ b/integrasjon/ereg-klient/src/main/java/no/nav/vedtak/felles/integrasjon/organisasjon/OrganisasjonNativeRestKlient.java @@ -0,0 +1,56 @@ +package no.nav.vedtak.felles.integrasjon.organisasjon; + +import java.net.URI; +import java.net.http.HttpRequest; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.core.UriBuilder; + +import no.nav.foreldrepenger.konfig.KonfigVerdi; +import no.nav.vedtak.felles.integrasjon.rest.NativeKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestKlient; +import no.nav.vedtak.sikkerhet.oidc.token.SikkerhetContext; + +@NativeKlient +@ApplicationScoped +public class OrganisasjonNativeRestKlient implements OrgInfo { + + private static final String ENDPOINT_KEY = "organisasjon.rs.url"; + private static final String DEFAULT_URI = "https://modapp.adeo.no/ereg/api/v1/organisasjon"; + + private RestKlient restKlient; + private URI endpoint; + + OrganisasjonNativeRestKlient() { + // CDI proxyable + } + + @Inject + public OrganisasjonNativeRestKlient(RestKlient restKlient, + @KonfigVerdi(value = ENDPOINT_KEY, defaultVerdi = DEFAULT_URI) URI endpoint) { + this.restKlient = restKlient; + this.endpoint = endpoint; + } + + @Override + public OrganisasjonEReg hentOrganisasjon(String orgnummer) { + var request = lagRequest(orgnummer); + return restKlient.send(request, OrganisasjonEReg.class); + } + + @Override + public OrganisasjonAdresse hentOrganisasjonAdresse(String orgnummer) { + var request = lagRequest(orgnummer); + return restKlient.send(request, OrganisasjonAdresse.class); + } + + private HttpRequest lagRequest(String orgnummer) { + var path = UriBuilder.fromUri(endpoint).path(orgnummer).build(); + return restKlient.request().builder(SikkerhetContext.BRUKER) + .uri(path) + .GET() + .build(); + } + +} diff --git a/integrasjon/ereg-klient/src/test/java/no/nav/vedtak/felles/integrasjon/organisasjon/EregRestTest.java b/integrasjon/ereg-klient/src/test/java/no/nav/vedtak/felles/integrasjon/organisasjon/EregRestTest.java index 65bdf4416..149503b1a 100644 --- a/integrasjon/ereg-klient/src/test/java/no/nav/vedtak/felles/integrasjon/organisasjon/EregRestTest.java +++ b/integrasjon/ereg-klient/src/test/java/no/nav/vedtak/felles/integrasjon/organisasjon/EregRestTest.java @@ -10,94 +10,96 @@ class EregRestTest { - private static final String json = "{\n" + - " \"organisasjonsnummer\": \"990983666\",\n" + - " \"type\": \"Virksomhet\",\n" + - " \"navn\": {\n" + - " \"redigertnavn\": \"NAV IKT\",\n" + - " \"navnelinje1\": \"NAV IKT\",\n" + - " \"bruksperiode\": {\n" + - " \"fom\": \"2015-02-23T08:04:53.2\"\n" + - " },\n" + - " \"gyldighetsperiode\": {\n" + - " \"fom\": \"2010-04-09\"\n" + - " }\n" + - " },\n" + - " \"organisasjonDetaljer\": {\n" + - " \"registreringsdato\": \"2007-03-05T00:00:00\",\n" + - " \"opphoersdato\": \"2018-11-06\",\n" + - " \"enhetstyper\": [\n" + - " {\n" + - " \"enhetstype\": \"BEDR\",\n" + - " \"bruksperiode\": {\n" + - " \"fom\": \"2018-11-07T04:02:27.436\"\n" + - " },\n" + - " \"gyldighetsperiode\": {\n" + - " \"fom\": \"2007-03-05\"\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"naeringer\": [\n" + - " {\n" + - " \"naeringskode\": \"84.300\",\n" + - " \"hjelpeenhet\": false,\n" + - " \"bruksperiode\": {\n" + - " \"fom\": \"2014-05-22T01:18:10.661\"\n" + - " },\n" + - " \"gyldighetsperiode\": {\n" + - " \"fom\": \"2006-07-01\"\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"navn\": [\n" + - " {\n" + - " \"redigertnavn\": \"NAV IKT\",\n" + - " \"navnelinje1\": \"NAV IKT\",\n" + - " \"bruksperiode\": {\n" + - " \"fom\": \"2015-02-23T08:04:53.2\"\n" + - " },\n" + - " \"gyldighetsperiode\": {\n" + - " \"fom\": \"2010-04-09\"\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"forretningsadresser\": [\n" + - " {\n" + - " \"type\": \"Forretningsadresse\",\n" + - " \"adresselinje1\": \"Sannergata 2\",\n" + - " \"postnummer\": \"0557\",\n" + - " \"landkode\": \"NO\",\n" + - " \"kommunenummer\": \"0301\",\n" + - " \"bruksperiode\": {\n" + - " \"fom\": \"2015-02-23T10:38:34.403\"\n" + - " },\n" + - " \"gyldighetsperiode\": {\n" + - " \"fom\": \"2007-08-23\"\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"postadresser\": [\n" + - " {\n" + - " \"type\": \"Postadresse\",\n" + - " \"adresselinje1\": \"Postboks 5 St Olavs plass\",\n" + - " \"postnummer\": \"0130\",\n" + - " \"landkode\": \"NO\",\n" + - " \"kommunenummer\": \"0301\",\n" + - " \"bruksperiode\": {\n" + - " \"fom\": \"2015-02-23T10:38:34.403\"\n" + - " },\n" + - " \"gyldighetsperiode\": {\n" + - " \"fom\": \"2010-10-08\"\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"sistEndret\": \"2014-02-17\"\n" + - " },\n" + - " \"virksomhetDetaljer\": {\n" + - " \"enhetstype\": \"BEDR\",\n" + - " \"oppstartsdato\": \"2006-07-01\"\n" + - " }\n" + - "}"; + private static final String json = """ + { + "organisasjonsnummer": "990983666", + "type": "Virksomhet", + "navn": { + "redigertnavn": "NAV IKT", + "navnelinje1": "NAV IKT", + "bruksperiode": { + "fom": "2015-02-23T08:04:53.2" + }, + "gyldighetsperiode": { + "fom": "2010-04-09" + } + }, + "organisasjonDetaljer": { + "registreringsdato": "2007-03-05T00:00:00", + "opphoersdato": "2018-11-06", + "enhetstyper": [ + { + "enhetstype": "BEDR", + "bruksperiode": { + "fom": "2018-11-07T04:02:27.436" + }, + "gyldighetsperiode": { + "fom": "2007-03-05" + } + } + ], + "naeringer": [ + { + "naeringskode": "84.300", + "hjelpeenhet": false, + "bruksperiode": { + "fom": "2014-05-22T01:18:10.661" + }, + "gyldighetsperiode": { + "fom": "2006-07-01" + } + } + ], + "navn": [ + { + "redigertnavn": "NAV IKT", + "navnelinje1": "NAV IKT", + "bruksperiode": { + "fom": "2015-02-23T08:04:53.2" + }, + "gyldighetsperiode": { + "fom": "2010-04-09" + } + } + ], + "forretningsadresser": [ + { + "type": "Forretningsadresse", + "adresselinje1": "Sannergata 2", + "postnummer": "0557", + "landkode": "NO", + "kommunenummer": "0301", + "bruksperiode": { + "fom": "2015-02-23T10:38:34.403" + }, + "gyldighetsperiode": { + "fom": "2007-08-23" + } + } + ], + "postadresser": [ + { + "type": "Postadresse", + "adresselinje1": "Postboks 5 St Olavs plass", + "postnummer": "0130", + "landkode": "NO", + "kommunenummer": "0301", + "bruksperiode": { + "fom": "2015-02-23T10:38:34.403" + }, + "gyldighetsperiode": { + "fom": "2010-10-08" + } + } + ], + "sistEndret": "2014-02-17" + }, + "virksomhetDetaljer": { + "enhetstype": "BEDR", + "oppstartsdato": "2006-07-01" + } + } + """; @Test void mapping_organisasjon() throws IOException { diff --git a/integrasjon/medl-klient/src/main/java/no/nav/vedtak/felles/integrasjon/medl2/MedlemsunntakNativeRestKlient.java b/integrasjon/medl-klient/src/main/java/no/nav/vedtak/felles/integrasjon/medl2/MedlemsunntakNativeRestKlient.java new file mode 100644 index 000000000..b12f1c7e3 --- /dev/null +++ b/integrasjon/medl-klient/src/main/java/no/nav/vedtak/felles/integrasjon/medl2/MedlemsunntakNativeRestKlient.java @@ -0,0 +1,77 @@ +package no.nav.vedtak.felles.integrasjon.medl2; + +import java.net.URI; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.core.UriBuilder; + +import no.nav.foreldrepenger.konfig.KonfigVerdi; +import no.nav.vedtak.felles.integrasjon.rest.NativeKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestKlient; +import no.nav.vedtak.log.mdc.MDCOperations; +import no.nav.vedtak.sikkerhet.oidc.token.SikkerhetContext; + +/** + * Dokumentasjon https://confluence.adeo.no/display/TREG/MEDL+-+Medlemskap+Rest + * Swagger: ukjent + */ +@NativeKlient +@ApplicationScoped +public class MedlemsunntakNativeRestKlient implements Medlemskap { + + private static final String ENDPOINT_KEY = "medl2.rs.url"; + private static final String DEFAULT_URI = "https://app.adeo.no/medl2/api/v1/medlemskapsunntak"; + + public static final String HEADER_NAV_CALL_ID = "Nav-Call-Id"; + public static final String HEADER_NAV_PERSONIDENT = "Nav-Personident"; + public static final String PARAM_FRA_OG_MED = "fraOgMed"; + public static final String PARAM_TIL_OG_MED = "tilOgMed"; + public static final String PARAM_STATUSER = "statuser"; + public static final String PARAM_INKLUDER_SPORINGSINFO = "inkluderSporingsinfo"; + + // Fra kodeverk PeriodestatusMedl + public static final String KODE_PERIODESTATUS_GYLD = "GYLD"; + public static final String KODE_PERIODESTATUS_UAVK = "UAVK"; + + private RestKlient restKlient; + private URI endpoint; + + MedlemsunntakNativeRestKlient() { + // CDI proxyable + } + + @Inject + public MedlemsunntakNativeRestKlient(RestKlient restKlient, + @KonfigVerdi(value = ENDPOINT_KEY, defaultVerdi = DEFAULT_URI) URI endpoint) { + this.restKlient = restKlient; + this.endpoint = endpoint; + } + + @Override + public List finnMedlemsunntak(String aktørId, LocalDate fom, LocalDate tom) throws Exception { + var uri = UriBuilder.fromUri(endpoint) + .queryParam(PARAM_INKLUDER_SPORINGSINFO, String.valueOf(true)) + .queryParam(PARAM_FRA_OG_MED, d2s(fom)) + .queryParam(PARAM_TIL_OG_MED, d2s(tom)) + .queryParam(PARAM_STATUSER, KODE_PERIODESTATUS_GYLD) + .queryParam(PARAM_STATUSER, KODE_PERIODESTATUS_UAVK); + var request = restKlient.request().builder(SikkerhetContext.BRUKER) + .header(HEADER_NAV_CALL_ID, MDCOperations.getCallId()) + .header(HEADER_NAV_PERSONIDENT, aktørId) + .uri(uri.build()) + .GET() + .build(); + var match = restKlient.send(request, Medlemskapsunntak[].class); + return Arrays.asList(match); + } + + private static String d2s(LocalDate dato) { + return DateTimeFormatter.ISO_LOCAL_DATE.format(dato); + } + +} diff --git a/integrasjon/oppgave-rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/oppgave/v1/OppgaveNativeKlient.java b/integrasjon/oppgave-rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/oppgave/v1/OppgaveNativeKlient.java new file mode 100644 index 000000000..49e4316f4 --- /dev/null +++ b/integrasjon/oppgave-rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/oppgave/v1/OppgaveNativeKlient.java @@ -0,0 +1,102 @@ +package no.nav.vedtak.felles.integrasjon.oppgave.v1; + +import java.net.URI; +import java.net.http.HttpRequest; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.core.UriBuilder; + +import no.nav.foreldrepenger.konfig.KonfigVerdi; +import no.nav.vedtak.felles.integrasjon.rest.NativeKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestRequest; +import no.nav.vedtak.log.mdc.MDCOperations; +import no.nav.vedtak.sikkerhet.oidc.token.SikkerhetContext; + +@NativeKlient +@ApplicationScoped +public class OppgaveNativeKlient implements Oppgaver { + + private static final String ENDPOINT_KEY = "oppgave.rs.uri"; + private static final String DEFAULT_URI = "http://oppgave.default/api/v1/oppgaver"; + private static final String HEADER_CORRELATION_ID = "X-Correlation-ID"; + private static final String STATUSKATEGORI_AAPEN = "AAPEN"; + + private RestKlient restKlient; + private URI endpoint; + + OppgaveNativeKlient() { + // CDI proxyable + } + + @Inject + public OppgaveNativeKlient(RestKlient restKlient, + @KonfigVerdi(value = ENDPOINT_KEY, defaultVerdi = DEFAULT_URI) URI endpoint) { + this.restKlient = restKlient; + this.endpoint = endpoint; + } + + @Override + public Oppgave opprettetOppgave(OpprettOppgave oppgave) { + var request = lagRequest().uri(endpoint).POST(RestRequest.serialiser(oppgave)); + return restKlient.send(request.build(), Oppgave.class); + } + + @Override + public List finnAlleOppgaver(String aktørId, String tema, List oppgaveTyper) throws Exception { + var builder = UriBuilder.fromUri(endpoint).queryParam("aktoerId", aktørId); + if (tema != null) + builder.queryParam("tema", tema); + oppgaveTyper.forEach(ot -> builder.queryParam("oppgavetype", ot)); + var request = lagRequest().uri(builder.build()).GET(); + return restKlient.send(request.build(), FinnOppgaveResponse.class).oppgaver(); + } + + @Override + public List finnÅpneOppgaver(String aktørId, String tema, List oppgaveTyper) throws Exception { + var builder = UriBuilder.fromUri(endpoint) + .queryParam("aktoerId", aktørId) + .queryParam("statuskategori", STATUSKATEGORI_AAPEN); + if (tema != null) + builder.queryParam("tema", tema); + oppgaveTyper.forEach(ot -> builder.queryParam("oppgavetype", ot)); + var request = lagRequest().uri(builder.build()).GET(); + return restKlient.send(request.build(), FinnOppgaveResponse.class).oppgaver(); + } + + @Override + public void ferdigstillOppgave(String oppgaveId) { + var oppgave = hentOppgave(oppgaveId); + var patch = new PatchOppgave(oppgave.getId(), oppgave.getVersjon(), Oppgavestatus.FERDIGSTILT); + var request = lagRequest().uri(getEndpointForOppgaveId(oppgaveId)); + RestRequest.patch(request, patch); + restKlient.sendPermitConflict(request.build()); + } + + @Override + public void feilregistrerOppgave(String oppgaveId) { + var oppgave = hentOppgave(oppgaveId); + var patch = new PatchOppgave(oppgave.getId(), oppgave.getVersjon(), Oppgavestatus.FEILREGISTRERT); + var request = lagRequest().uri(getEndpointForOppgaveId(oppgaveId)); + RestRequest.patch(request, patch); + restKlient.sendPermitConflict(request.build()); + } + + @Override + public Oppgave hentOppgave(String oppgaveId) { + var request = lagRequest().uri(getEndpointForOppgaveId(oppgaveId)).GET(); + return restKlient.send(request.build(), Oppgave.class); + } + + private URI getEndpointForOppgaveId(String oppgaveId) { + return UriBuilder.fromUri(endpoint).path(oppgaveId).build(); + } + + private HttpRequest.Builder lagRequest() { + return restKlient.request().builder(SikkerhetContext.BRUKER) + .header(HEADER_CORRELATION_ID, MDCOperations.getCallId()); + } + +} diff --git a/integrasjon/pdl-klient/src/main/java/no/nav/vedtak/felles/integrasjon/pdl/NativePdlKlient.java b/integrasjon/pdl-klient/src/main/java/no/nav/vedtak/felles/integrasjon/pdl/NativePdlKlient.java new file mode 100644 index 000000000..4894dd68e --- /dev/null +++ b/integrasjon/pdl-klient/src/main/java/no/nav/vedtak/felles/integrasjon/pdl/NativePdlKlient.java @@ -0,0 +1,137 @@ +package no.nav.vedtak.felles.integrasjon.pdl; + +import static org.apache.http.HttpStatus.SC_NOT_FOUND; + +import java.net.URI; +import java.net.http.HttpRequest; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLOperationRequest; +import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLRequest; +import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLResponseProjection; +import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLResult; + +import no.nav.foreldrepenger.konfig.KonfigVerdi; +import no.nav.pdl.GeografiskTilknytning; +import no.nav.pdl.GeografiskTilknytningResponseProjection; +import no.nav.pdl.HentGeografiskTilknytningQueryRequest; +import no.nav.pdl.HentGeografiskTilknytningQueryResponse; +import no.nav.pdl.HentIdenterBolkQueryRequest; +import no.nav.pdl.HentIdenterBolkQueryResponse; +import no.nav.pdl.HentIdenterBolkResult; +import no.nav.pdl.HentIdenterBolkResultResponseProjection; +import no.nav.pdl.HentIdenterQueryRequest; +import no.nav.pdl.HentIdenterQueryResponse; +import no.nav.pdl.HentPersonQueryRequest; +import no.nav.pdl.HentPersonQueryResponse; +import no.nav.pdl.Identliste; +import no.nav.pdl.IdentlisteResponseProjection; +import no.nav.pdl.Person; +import no.nav.pdl.PersonResponseProjection; +import no.nav.vedtak.felles.integrasjon.graphql.GraphQLErrorHandler; +import no.nav.vedtak.felles.integrasjon.rest.NativeKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestRequest; +import no.nav.vedtak.sikkerhet.oidc.token.SikkerhetContext; + +@NativeKlient +@ApplicationScoped +public class NativePdlKlient implements Pdl { + + private static final Logger LOG = LoggerFactory.getLogger(NativePdlKlient.class); + static final String HTTP_PDL_API_DEFAULT_GRAPHQL = "http://pdl-api.pdl/graphql"; + public static final String FOR = "FOR"; + + private URI endpoint; + private GraphQLErrorHandler errorHandler; + private String tema; + private RestKlient restKlient; + private RestRequest restRequest; + + /** + * TODO: Utvide med ulike varianter ifm azure - Bruker, System, OBO, etc. Evt deleger til TokenProvider/OidcRequest + * Inntil videre brukes token fra kontekst (bruker eller system) + consumertoken + */ + @Inject + public NativePdlKlient(RestKlient restKlient, + @KonfigVerdi(value = "pdl.base.url", defaultVerdi = HTTP_PDL_API_DEFAULT_GRAPHQL) URI endpoint, + @KonfigVerdi(value = "pdl.tema", defaultVerdi = FOR) String tema) { + this(restKlient, restKlient.request(), endpoint, tema); + } + + NativePdlKlient(RestKlient restKlient, RestRequest restRequest, URI endpoint, String tema) { + this.restKlient = restKlient; + this.restRequest = restRequest; + this.endpoint = endpoint; + this.tema = tema; + this.errorHandler = new PdlDefaultErrorHandler(); + } + + NativePdlKlient() { + // CDI proxyable + } + + @Override + public GeografiskTilknytning hentGT(HentGeografiskTilknytningQueryRequest q, GeografiskTilknytningResponseProjection p) { + return query(q, p, HentGeografiskTilknytningQueryResponse.class).hentGeografiskTilknytning(); + } + + @Override + public Person hentPerson(HentPersonQueryRequest q, PersonResponseProjection p) { + return query(q, p, HentPersonQueryResponse.class).hentPerson(); + } + + @Override + public Person hentPerson(HentPersonQueryRequest q, PersonResponseProjection p, boolean ignoreNotFound) { + try { + return hentPerson(q, p); + } catch (PdlException e) { + if (e.getStatus() == SC_NOT_FOUND && ignoreNotFound) { + return null; + } + throw e; + } + } + + @Override + public Identliste hentIdenter(HentIdenterQueryRequest q, IdentlisteResponseProjection p) { + return query(q, p, HentIdenterQueryResponse.class).hentIdenter(); + } + + @Override + public List hentIdenterBolkResults(HentIdenterBolkQueryRequest q, HentIdenterBolkResultResponseProjection p) { + return query(q, p, HentIdenterBolkQueryResponse.class).hentIdenterBolk(); + } + + @Override + public > T query(GraphQLOperationRequest q, GraphQLResponseProjection p, Class clazz) { + return query(new GraphQLRequest(q, p), clazz); + } + + private > T query(GraphQLRequest req, Class clazz) { + LOG.trace("Henter resultat for {} fra {}", clazz.getName(), endpoint); + var request = restRequest.builderConsumerToken(SikkerhetContext.BRUKER) + .header("TEMA", tema) + .uri(endpoint) + .POST(HttpRequest.BodyPublishers.ofString(req.toHttpJsonBody())) + .build(); + var res = restKlient.send(request, clazz); + if (res.hasErrors()) { + return errorHandler.handleError(res.getErrors(), endpoint, PDL_ERROR_RESPONSE); + } + LOG.trace("Hentet resultat for {} fra {} OK", clazz.getName(), endpoint); + return res; + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [endpoint=" + endpoint + ", errorHandler=" + errorHandler + "]"; + } + +} diff --git a/integrasjon/pdl-klient/src/main/java/no/nav/vedtak/felles/integrasjon/pdl/PdlException.java b/integrasjon/pdl-klient/src/main/java/no/nav/vedtak/felles/integrasjon/pdl/PdlException.java index 85bec8dd3..5c276ea3d 100644 --- a/integrasjon/pdl-klient/src/main/java/no/nav/vedtak/felles/integrasjon/pdl/PdlException.java +++ b/integrasjon/pdl-klient/src/main/java/no/nav/vedtak/felles/integrasjon/pdl/PdlException.java @@ -29,6 +29,10 @@ public PDLExceptionDetails getDetails() { return extension.details(); } + public String getCode() { + return extension.code(); + } + public int getStatus() { return status; } diff --git a/integrasjon/pdl-klient/src/test/java/no/nav/vedtak/felles/integrasjon/pdl/PdlNativeKlientTest.java b/integrasjon/pdl-klient/src/test/java/no/nav/vedtak/felles/integrasjon/pdl/PdlNativeKlientTest.java new file mode 100644 index 000000000..565cae231 --- /dev/null +++ b/integrasjon/pdl-klient/src/test/java/no/nav/vedtak/felles/integrasjon/pdl/PdlNativeKlientTest.java @@ -0,0 +1,168 @@ +package no.nav.vedtak.felles.integrasjon.pdl; + +import static java.util.List.of; +import static no.nav.vedtak.felles.integrasjon.pdl.PdlDefaultErrorHandler.FORBUDT; +import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpRequest; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLError; + +import no.nav.pdl.HentIdenterBolkQueryRequest; +import no.nav.pdl.HentIdenterBolkQueryResponse; +import no.nav.pdl.HentIdenterBolkResultResponseProjection; +import no.nav.pdl.HentIdenterQueryRequest; +import no.nav.pdl.HentIdenterQueryResponse; +import no.nav.pdl.HentPersonQueryRequest; +import no.nav.pdl.HentPersonQueryResponse; +import no.nav.pdl.IdentInformasjon; +import no.nav.pdl.IdentInformasjonResponseProjection; +import no.nav.pdl.IdentlisteResponseProjection; +import no.nav.pdl.NavnResponseProjection; +import no.nav.pdl.PersonResponseProjection; +import no.nav.vedtak.felles.integrasjon.rest.RestKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestRequest; +import no.nav.vedtak.log.mdc.MDCOperations; +import no.nav.vedtak.mapper.json.DefaultJsonMapper; + +@ExtendWith(MockitoExtension.class) +class PdlNativeKlientTest { + + private Pdl pdlKlient; + private URI endpoint = URI.create("http://dummyendpoint/graphql"); + + @Mock + private RestKlient restClient; + + private static class PdlRequest extends RestRequest { + private PdlRequest() { + super(new TestContextSupplier()); + } + } + + @BeforeEach + void setUp() throws IOException { + // Service setup + MDCOperations.putCallId(); + pdlKlient = new NativePdlKlient(restClient, new PdlRequest(), endpoint, Tema.FOR.name()); + } + + @Test + void skal_returnere_person() throws IOException { + // query-eksempel: dokumentoversiktFagsak(fagsak: {fagsakId: "2019186111", + // fagsaksystem: "AO01"}, foerste: 5) + var resource = getClass().getClassLoader().getResource("pdl/personResponse.json"); + var response = DefaultJsonMapper.fromJson(resource, HentPersonQueryResponse.class); + var captor = ArgumentCaptor.forClass(HttpRequest.class); + + when(restClient.send(captor.capture(), any())).thenReturn(response); + + var query = new HentPersonQueryRequest(); + query.setIdent("12345678901"); + var projection = new PersonResponseProjection() + .navn(new NavnResponseProjection() + .fornavn()); + + var person = pdlKlient.hentPerson(query, projection); + + assertThat(person.getNavn().get(0).getFornavn()).isNotEmpty(); + var rq = captor.getValue(); + assertThat(rq.headers().map().get("Authorization")).isNotEmpty(); + assertThat(rq.headers().map().get("Nav-Consumer-Token")).isNotEmpty(); + assertThat(rq.headers().map().get("Nav-Consumer-Id")).contains("user"); + assertThat(rq.headers().map().get("TEMA")).contains("FOR"); + } + + @Test + void skal_returnere_ident() throws IOException { + var resource = getClass().getClassLoader().getResource("pdl/identerResponse.json"); + var response = DefaultJsonMapper.fromJson(resource, HentIdenterQueryResponse.class); + when(restClient.send(any(HttpRequest.class), any())).thenReturn(response); + + var queryRequest = new HentIdenterQueryRequest(); + queryRequest.setIdent("12345678901"); + var projection = new IdentlisteResponseProjection() + .identer( + new IdentInformasjonResponseProjection() + .ident() + .gruppe()); + + var identer = pdlKlient.hentIdenter(queryRequest, projection); + + assertThat(identer.getIdenter()).hasSizeGreaterThan(0); + } + + @Test + void skal_returnere_bolk_med_identer() throws IOException { + var resource = getClass().getClassLoader().getResource("pdl/identerBolkResponse.json"); + var response = DefaultJsonMapper.fromJson(resource, HentIdenterBolkQueryResponse.class); + when(restClient.send(any(HttpRequest.class), any())).thenReturn(response); + + var queryRequest = new HentIdenterBolkQueryRequest(); + queryRequest.setIdenter(of("12345678901")); + + var projection = new HentIdenterBolkResultResponseProjection() + .ident() + .identer(new IdentInformasjonResponseProjection() + .ident() + .gruppe()); + var identer = pdlKlient.hentIdenterBolkResults(queryRequest, projection); + + assertThat( + identer.stream() + .flatMap(r -> r.getIdenter().stream()) + .map(IdentInformasjon::getIdent)) + .containsExactlyInAnyOrder("16047439276", "9916047439276", "25017312345", "9925017312345"); + } + + @Test + void skal_returnere_ikke_funnet() throws IOException { + var resource = getClass().getClassLoader().getResource("pdl/errorResponse.json"); + var response = DefaultJsonMapper.fromJson(resource, HentIdenterQueryResponse.class); + when(restClient.send(any(HttpRequest.class), any())).thenReturn(response); + + + var queryRequest = new HentIdenterQueryRequest(); + queryRequest.setIdent("12345678901"); + var projection = new IdentlisteResponseProjection() + .identer( + new IdentInformasjonResponseProjection() + .ident() + .gruppe()); + + assertThrows(PdlException.class, () -> pdlKlient.hentIdenter(queryRequest, projection)); + } + + @Test + @DisplayName("Test error handler") + void testErrorHandler() { + var handler = new PdlDefaultErrorHandler(); + var error = new GraphQLError(); + error.setExtensions(Map.of("code", FORBUDT, "details", + Map.of("cause", "a cause", "type", "a type", "policy", "a policy"))); + var e = assertThrows(PdlException.class, () -> handler.handleError(List.of(error), endpoint, "KODE")); + assertNotNull(e.getDetails()); + assertEquals(FORBUDT, e.getCode()); + assertEquals("a policy", e.getDetails().policy()); + assertEquals(SC_UNAUTHORIZED, e.getStatus()); + } + +} diff --git a/integrasjon/pdl-klient/src/test/java/no/nav/vedtak/felles/integrasjon/pdl/TestContextSupplier.java b/integrasjon/pdl-klient/src/test/java/no/nav/vedtak/felles/integrasjon/pdl/TestContextSupplier.java new file mode 100644 index 000000000..3ac0750ce --- /dev/null +++ b/integrasjon/pdl-klient/src/test/java/no/nav/vedtak/felles/integrasjon/pdl/TestContextSupplier.java @@ -0,0 +1,48 @@ +package no.nav.vedtak.felles.integrasjon.pdl; + +import java.util.function.Supplier; + +import no.nav.vedtak.felles.integrasjon.rest.RequestContextSupplier; +import no.nav.vedtak.sikkerhet.oidc.config.OpenIDProvider; +import no.nav.vedtak.sikkerhet.oidc.token.OpenIDToken; +import no.nav.vedtak.sikkerhet.oidc.token.SikkerhetContext; +import no.nav.vedtak.sikkerhet.oidc.token.TokenString; + +public final class TestContextSupplier implements RequestContextSupplier { + + private static final String DUMMY_TOKEN_OPENAM = "eyAidHlwIjogIkpXVCIsICJraWQiOiAiU0gxSWVSU2sxT1VGSDNzd1orRXVVcTE5VHZRPSIsICJhbGciOiAiUlMyNTYiIH0.eyAiYXRfaGFzaCI6ICIyb2c1RGk5ZW9LeFhOa3VPd0dvVUdBIiwgInN1YiI6ICJzMTQyNDQzIiwgImF1ZGl0VHJhY2tpbmdJZCI6ICI1NTM0ZmQ4ZS03MmE2LTRhMWQtOWU5YS1iZmEzYThhMTljMDUtNjE2NjA2NyIsICJpc3MiOiAiaHR0cHM6Ly9pc3NvLXQuYWRlby5ubzo0NDMvaXNzby9vYXV0aDIiLCAidG9rZW5OYW1lIjogImlkX3Rva2VuIiwgImF1ZCI6ICJPSURDIiwgImNfaGFzaCI6ICJiVWYzcU5CN3dTdi0wVlN0bjhXLURnIiwgIm9yZy5mb3JnZXJvY2sub3BlbmlkY29ubmVjdC5vcHMiOiAiMTdhOGZiMzYtMGI0Ny00YzRkLWE4YWYtZWM4Nzc3Y2MyZmIyIiwgImF6cCI6ICJPSURDIiwgImF1dGhfdGltZSI6IDE0OTgwMzk5MTQsICJyZWFsbSI6ICIvIiwgImV4cCI6IDE0OTgwNDM1MTUsICJ0b2tlblR5cGUiOiAiSldUVG9rZW4iLCAiaWF0IjogMTQ5ODAzOTkxNSB9.S2DKQweQWZIfjaAT2UP9_dxrK5zqpXj8IgtjDLt5PVfLYfZqpWGaX-ckXG0GlztDVBlRK4ylmIYacTmEAUV_bRa_qWKRNxF83SlQRgHDSiE82SGv5WHOGEcAxf2w_d50XsgA2KDBCyv0bFIp9bCiKzP11uWPW0v4uIkyw2xVxMVPMCuiMUtYFh80sMDf9T4FuQcFd0LxoYcSFDEDlwCdRiF3ufw73qtMYBlNIMbTGHx-DZWkZV7CgukmCee79gwQIvGwdLrgaDrHFCJUDCbB1FFEaE3p3_BZbj0T54fCvL69aHyWm1zEd9Pys15yZdSh3oSSr4yVNIxhoF-nQ7gY-g;"; + private static final String DUMMY_TOKEN_TOKENX = "eyJraWQiOiI3Mzk2ZGIyZC1hN2MyLTQ1OGEtYjkzNC02ODNiNDgzYzUyNDIiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiRzJ1Zl83OW1TTUhHSWFfNjFxTnJfUSIsInN1YiI6IjA5MDg4NDIwNjcyIiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6XC9cL3Rva2VuZGluZ3MuZGV2LWdjcC5uYWlzLmlvIiwibm9uY2UiOiJWR1dyS1Zsa3RXZ3hCdTlMZnNnMHliMmdMUVhoOHRaZHRaVTJBdWdPZVl3IiwiY2xpZW50X2lkIjoiZGV2LWZzczp0ZWFtZm9yZWxkcmVwZW5nZXI6ZnBzb2tuYWQtbW90dGFrIiwiYXVkIjoiZGV2LWZzczp0ZWFtZm9yZWxkcmVwZW5nZXI6ZnBpbmZvIiwiYWNyIjoiTGV2ZWw0IiwibmJmIjoxNjE2Njg1NDA0LCJpZHAiOiJodHRwczpcL1wvbmF2dGVzdGIyYy5iMmNsb2dpbi5jb21cL2QzOGYyNWFhLWVhYjgtNGM1MC05ZjI4LWViZjkyYzEyNTZmMlwvdjIuMFwvIiwiYXV0aF90aW1lIjoxNjE2Njg1NDAyLCJleHAiOjE2MTY2ODU3MDQsImlhdCI6MTYxNjY4NTQwNCwianRpIjoiNGMwNzBmMGUtNzI0Ny00ZTdjLWE1OWEtYzk2Yjk0NWMxZWZhIn0.OvzjuabvPHG9nlRVc_KlCUTHOdfeT9GtBkASUGIoMayWGeIBDkr4-jc9gu6uT_WQqi9IJnvPkWgP3veqYHcOHpapD1yVNaQpxlrJQ04yP6N3gvkn-DcrBRDb3II_6qSaPQ_us2PJBDPq2VD5TGrNOL6EFwr8FK3zglYr-PgjW016ULTcmx_7gdHmbiC5PEn1_OtGNxzoUhSGKoD3YtUWP0qdsXzoKyeFL5FG9uZMSrDHHiJBZQFXGL9OzBU49Zb2K-iEPqa9m91O2JZGkhebfLjCAIPLPN4J68GFyfTvtNkZO71znorjo-e1nWxz53Wkj---RDY3JlIqNqzqHTfJgQ"; + + public TestContextSupplier() { + } + + @Override + public Supplier consumerIdFor(SikkerhetContext context) { + return () -> "user"; + } + + @Override + public Supplier tokenFor(SikkerhetContext context) { + return () -> new OpenIDToken(OpenIDProvider.TOKENX, new TokenString(DUMMY_TOKEN_TOKENX)); + } + + @Override + public Supplier stsSystemToken() { + return () -> new OpenIDToken(OpenIDProvider.ISSO, new TokenString(DUMMY_TOKEN_OPENAM)); + } + + @Override + public Supplier azureSystemToken(String scope) { + return () -> new OpenIDToken(OpenIDProvider.ISSO, new TokenString(DUMMY_TOKEN_OPENAM)); + } + + @Override + public Supplier consumerId() { + return () -> "system"; + } + + @Override + public Supplier consumerToken() { + return () -> new OpenIDToken(OpenIDProvider.ISSO, new TokenString(DUMMY_TOKEN_OPENAM)); + } +} diff --git a/integrasjon/pom.xml b/integrasjon/pom.xml index 32446b305..92a2b646c 100644 --- a/integrasjon/pom.xml +++ b/integrasjon/pom.xml @@ -73,6 +73,11 @@ felles-sikkerhet ${project.version} + + no.nav.foreldrepenger.felles + felles-klient + ${project.version} + no.nav.foreldrepenger.felles felles-mapper diff --git a/integrasjon/rest-klient/pom.xml b/integrasjon/rest-klient/pom.xml index cef640c3e..9d8545570 100644 --- a/integrasjon/rest-klient/pom.xml +++ b/integrasjon/rest-klient/pom.xml @@ -58,6 +58,10 @@ no.nav.foreldrepenger.felles felles-oidc + + no.nav.foreldrepenger.felles + felles-klient + no.nav.foreldrepenger.felles.sikkerhet felles-sikkerhet diff --git a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/NativeKlient.java b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/NativeKlient.java new file mode 100644 index 000000000..7c7d578a6 --- /dev/null +++ b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/NativeKlient.java @@ -0,0 +1,19 @@ +package no.nav.vedtak.felles.integrasjon.rest; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RUNTIME) +@Target({ METHOD, FIELD, PARAMETER, TYPE }) +public @interface NativeKlient { + String value() default "nativeklient"; +} diff --git a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/OidcContextSupplier.java b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/OidcContextSupplier.java new file mode 100644 index 000000000..06b46576b --- /dev/null +++ b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/OidcContextSupplier.java @@ -0,0 +1,40 @@ +package no.nav.vedtak.felles.integrasjon.rest; + +import java.util.function.Supplier; + +import no.nav.vedtak.sikkerhet.oidc.token.OpenIDToken; +import no.nav.vedtak.sikkerhet.oidc.token.SikkerhetContext; +import no.nav.vedtak.sikkerhet.oidc.token.TokenProvider; + +public final class OidcContextSupplier implements RequestContextSupplier { + + @Override + public Supplier consumerIdFor(SikkerhetContext context) { + return () -> TokenProvider.getUserIdFor(context); + } + + @Override + public Supplier tokenFor(SikkerhetContext context) { + return () -> TokenProvider.getTokenFor(context); + } + + @Override + public Supplier stsSystemToken() { + return () -> TokenProvider.getStsSystemToken(); + } + + @Override + public Supplier azureSystemToken(String scope) { + return () -> TokenProvider.getAzureSystemToken(scope); + } + + @Override + public Supplier consumerId() { + return () -> TokenProvider.getUserIdFor(SikkerhetContext.SYSTEM); + } + + @Override + public Supplier consumerToken() { + return () -> TokenProvider.getTokenFor(SikkerhetContext.SYSTEM); + } +} diff --git a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RequestContextSupplier.java b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RequestContextSupplier.java new file mode 100644 index 000000000..9537231ca --- /dev/null +++ b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RequestContextSupplier.java @@ -0,0 +1,22 @@ +package no.nav.vedtak.felles.integrasjon.rest; + +import java.util.function.Supplier; + +import no.nav.vedtak.sikkerhet.oidc.token.OpenIDToken; +import no.nav.vedtak.sikkerhet.oidc.token.SikkerhetContext; + +public interface RequestContextSupplier { + + Supplier consumerIdFor(SikkerhetContext context); + + Supplier tokenFor(SikkerhetContext context); + + Supplier stsSystemToken(); + + Supplier azureSystemToken(String scope); + + Supplier consumerId(); + + Supplier consumerToken(); + +} diff --git a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestKlient.java b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestKlient.java new file mode 100644 index 000000000..60cb5a60b --- /dev/null +++ b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestKlient.java @@ -0,0 +1,68 @@ +package no.nav.vedtak.felles.integrasjon.rest; + +import java.net.HttpURLConnection; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Optional; +import java.util.Set; + +import no.nav.vedtak.klient.http.DefaultHttpKlient; +import no.nav.vedtak.mapper.json.DefaultJsonMapper; + +public final class RestKlient { + + private static RestKlient CLIENT; + + private final DefaultHttpKlient httpClient; + + private RestKlient() { + this.httpClient = DefaultHttpKlient.client(); + } + + public static synchronized RestKlient client() { + var inst= CLIENT; + if (inst == null) { + inst = new RestKlient(); + CLIENT = inst; + } + return inst; + } + + public RestRequest request() { + return RestRequest.request(); + } + + public T send(HttpRequest request, Class clazz) { + RestRequest.validateRestHeaders(request); + var response = httpClient.send(request); + return DefaultJsonMapper.fromJson(response, clazz); + } + + public T sendPermitConflict(HttpRequest request, Class clazz) { + RestRequest.validateRestHeaders(request); + var response= httpClient.send(request, Set.of(HttpURLConnection.HTTP_CONFLICT)); + return DefaultJsonMapper.fromJson(response, clazz); + } + + public String send(HttpRequest request) { + RestRequest.validateRestHeaders(request); + return httpClient.send(request); + } + + public String sendPermitConflict(HttpRequest request) { + RestRequest.validateRestHeaders(request); + return httpClient.send(request, Set.of(HttpURLConnection.HTTP_CONFLICT)); + } + + public Optional sendHandleResponse(HttpRequest request) { + RestRequest.validateRestHeaders(request); + return httpClient.sendHandleResponse(request); + } + + // For de som vil håndtere statusCode() og body() selv - men husk å kaste exception der det ikke skal ignoreres. + public HttpResponse sendNoResponseHandler(HttpRequest request) { + RestRequest.validateRestHeaders(request); + return httpClient.sendNoResponseHandler(request); + } + +} diff --git a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestRequest.java b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestRequest.java new file mode 100644 index 000000000..56a2c4759 --- /dev/null +++ b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestRequest.java @@ -0,0 +1,87 @@ +package no.nav.vedtak.felles.integrasjon.rest; + +import java.net.http.HttpRequest; +import java.util.Set; +import java.util.function.Supplier; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; + +import no.nav.vedtak.klient.http.DefaultRequest; +import no.nav.vedtak.mapper.json.DefaultJsonMapper; +import no.nav.vedtak.sikkerhet.oidc.token.OpenIDToken; +import no.nav.vedtak.sikkerhet.oidc.token.SikkerhetContext; + +public class RestRequest { + + private static final String HEADER_NAV_CONSUMER_ID = "Nav-Consumer-Id"; + private static final String HEADER_NAV_CONSUMER_TOKEN = "Nav-Consumer-Token"; + + private static final String OIDC_AUTH_HEADER_PREFIX = "Bearer "; + + private static final Set REST_HEADERS = Set.of(HEADER_NAV_CONSUMER_ID, HttpHeaders.AUTHORIZATION); + + private static RestRequest REQUEST; + + private final RequestContextSupplier supplier; + + // Local test purposes + protected RestRequest(RequestContextSupplier supplier) { + this.supplier = supplier; + } + + private RestRequest() { + this(new OidcContextSupplier()); + } + + public static synchronized RestRequest request() { + var inst= REQUEST; + if (inst == null) { + inst = new RestRequest(); + REQUEST = inst; + } + return inst; + } + + public HttpRequest.Builder builder(SikkerhetContext context) { + return builder(supplier.tokenFor(context), supplier.consumerIdFor(context)); + } + + public HttpRequest.Builder builderConsumerToken(SikkerhetContext context) { + return builder(supplier.tokenFor(context), supplier.consumerIdFor(context)) + .header(HEADER_NAV_CONSUMER_TOKEN, supplier.consumerToken().get().token()); + } + + public HttpRequest.Builder builderSystemSTS() { + return builder(supplier.stsSystemToken(), supplier.consumerId()); + } + + public HttpRequest.Builder builderSystemAzure(String scope) { + return builder(supplier.azureSystemToken(scope), supplier.consumerId()); + } + + public static void patch(HttpRequest.Builder builder, Object o) { + var payload = DefaultJsonMapper.toJson(o); + builder.method("PATCH", HttpRequest.BodyPublishers.ofString(payload)); + } + + public static HttpRequest.BodyPublisher serialiser(Object object) { + return HttpRequest.BodyPublishers.ofString(DefaultJsonMapper.toJson(object)); + } + + private static HttpRequest.Builder builder(Supplier authToken, Supplier consumerId) { + return DefaultRequest.builder() + .header(HEADER_NAV_CONSUMER_ID, consumerId.get()) + .header(HttpHeaders.AUTHORIZATION, OIDC_AUTH_HEADER_PREFIX + authToken.get().token()) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); + } + + + static void validateRestHeaders(HttpRequest request) { + if (!request.headers().map().keySet().containsAll(REST_HEADERS)) { + throw new IllegalArgumentException("Utviklerfeil: mangler headere, fant " + request.headers().map().keySet()); + } + } + +} diff --git a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestSupportKlientProdusent.java b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestSupportKlientProdusent.java new file mode 100644 index 000000000..1a0664e06 --- /dev/null +++ b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestSupportKlientProdusent.java @@ -0,0 +1,19 @@ +package no.nav.vedtak.felles.integrasjon.rest; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +@ApplicationScoped +public class RestSupportKlientProdusent { + + @Produces + public RestKlient getRestKlient() { + return RestKlient.client(); + } + + @Produces + public RestRequest getRestRequest() { + return RestRequest.request(); + } + +} diff --git a/integrasjon/saf-klient/src/main/java/no/nav/vedtak/felles/integrasjon/saf/SafNativeTjeneste.java b/integrasjon/saf-klient/src/main/java/no/nav/vedtak/felles/integrasjon/saf/SafNativeTjeneste.java new file mode 100644 index 000000000..49b032689 --- /dev/null +++ b/integrasjon/saf-klient/src/main/java/no/nav/vedtak/felles/integrasjon/saf/SafNativeTjeneste.java @@ -0,0 +1,121 @@ +package no.nav.vedtak.felles.integrasjon.saf; + +import java.net.URI; +import java.net.http.HttpRequest; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.core.UriBuilder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLOperationRequest; +import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLRequest; +import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLResponseProjection; +import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLResult; + +import no.nav.foreldrepenger.konfig.KonfigVerdi; +import no.nav.saf.Dokumentoversikt; +import no.nav.saf.DokumentoversiktFagsakQueryRequest; +import no.nav.saf.DokumentoversiktFagsakQueryResponse; +import no.nav.saf.DokumentoversiktResponseProjection; +import no.nav.saf.Journalpost; +import no.nav.saf.JournalpostQueryRequest; +import no.nav.saf.JournalpostQueryResponse; +import no.nav.saf.JournalpostResponseProjection; +import no.nav.saf.TilknyttedeJournalposterQueryRequest; +import no.nav.saf.TilknyttedeJournalposterQueryResponse; +import no.nav.vedtak.felles.integrasjon.graphql.GraphQLErrorHandler; +import no.nav.vedtak.felles.integrasjon.rest.NativeKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestRequest; +import no.nav.vedtak.felles.integrasjon.rest.jersey.AbstractJerseyOidcRestClient; +import no.nav.vedtak.sikkerhet.oidc.token.SikkerhetContext; + +@NativeKlient +@ApplicationScoped +public class SafNativeTjeneste extends AbstractJerseyOidcRestClient implements Saf { + private static final String HENTDOKUMENT = "/rest/hentdokument/{journalpostId}/{dokumentInfoId}/{variantFormat}"; + private static final String F_240613 = "F-240613"; + private static final String DEFAULT_BASE = "https://saf.nais.adeo.no"; + private static final String GRAPHQL = "/graphql"; + + private static final Logger LOG = LoggerFactory.getLogger(SafNativeTjeneste.class); + + private RestKlient restKlient; + private RestRequest restRequest; + private URI base; + private GraphQLErrorHandler errorHandler; + + @Inject + public SafNativeTjeneste(RestKlient restKlient, @KonfigVerdi(value = "saf.base.url", defaultVerdi = DEFAULT_BASE) URI base) { + this(restKlient, restKlient.request(), base); + } + + SafNativeTjeneste(RestKlient restKlient, RestRequest restRequest, URI base) { + this.restKlient = restKlient; + this.restRequest = restRequest; + this.base = base; + this.errorHandler = new SafErrorHandler(); + } + + SafNativeTjeneste() { + // CDI proxyable + } + + @Override + public Dokumentoversikt dokumentoversiktFagsak(DokumentoversiktFagsakQueryRequest q, DokumentoversiktResponseProjection p) { + return query(q, p, DokumentoversiktFagsakQueryResponse.class).dokumentoversiktFagsak(); + } + + @Override + public Journalpost hentJournalpostInfo(JournalpostQueryRequest q, JournalpostResponseProjection p) { + return query(q, p, JournalpostQueryResponse.class).journalpost(); + } + + @Override + public List hentTilknyttedeJournalposter(TilknyttedeJournalposterQueryRequest q, JournalpostResponseProjection p) { + return query(q, p, TilknyttedeJournalposterQueryResponse.class).tilknyttedeJournalposter(); + } + + @Override + public byte[] hentDokument(HentDokumentQuery q) { + LOG.trace("Henter dokument"); + var path = UriBuilder.fromUri(base).path(HENTDOKUMENT) + .resolveTemplate("journalpostId", q.journalpostId()) + .resolveTemplate("dokumentInfoId", q.dokumentId()) + .resolveTemplate("variantFormat", q.variantFormat()) + .build(); + var request = restRequest.builder(SikkerhetContext.BRUKER) + .uri(path) + .GET(); + var doc = restKlient.sendHandleResponse(request.build()); + LOG.info("Hentet dokument OK"); + return doc.orElse(null); + } + + @Override + public > T query(GraphQLOperationRequest req, GraphQLResponseProjection p, Class clazz) { + return query(new GraphQLRequest(req, p), clazz); + } + + private > T query(GraphQLRequest req, Class clazz) { + LOG.trace("Eksekverer GraphQL query {}", req.getClass().getSimpleName()); + var request = restRequest.builder(SikkerhetContext.BRUKER) + .uri(UriBuilder.fromUri(base).path(GRAPHQL).build()) + .POST(HttpRequest.BodyPublishers.ofString(req.toHttpJsonBody())); + var res = restKlient.send(request.build(), clazz); + if (res.hasErrors()) { + return errorHandler.handleError(res.getErrors(), base, F_240613); + } + LOG.info("Eksekvert GraphQL query OK"); + return res; + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [base=" + base + "]"; + } +} diff --git a/integrasjon/saf-klient/src/test/java/no/nav/vedtak/felles/integrasjon/saf/SafNativeKlientTest.java b/integrasjon/saf-klient/src/test/java/no/nav/vedtak/felles/integrasjon/saf/SafNativeKlientTest.java new file mode 100644 index 000000000..99b144290 --- /dev/null +++ b/integrasjon/saf-klient/src/test/java/no/nav/vedtak/felles/integrasjon/saf/SafNativeKlientTest.java @@ -0,0 +1,243 @@ +package no.nav.vedtak.felles.integrasjon.saf; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpRequest; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import no.nav.saf.AvsenderMottakerResponseProjection; +import no.nav.saf.BrukerResponseProjection; +import no.nav.saf.DokumentInfoResponseProjection; +import no.nav.saf.Dokumentoversikt; +import no.nav.saf.DokumentoversiktFagsakQueryRequest; +import no.nav.saf.DokumentoversiktFagsakQueryResponse; +import no.nav.saf.DokumentoversiktResponseProjection; +import no.nav.saf.DokumentvariantResponseProjection; +import no.nav.saf.FagsakInput; +import no.nav.saf.Journalpost; +import no.nav.saf.JournalpostQueryRequest; +import no.nav.saf.JournalpostQueryResponse; +import no.nav.saf.JournalpostResponseProjection; +import no.nav.saf.LogiskVedleggResponseProjection; +import no.nav.saf.RelevantDatoResponseProjection; +import no.nav.saf.SakResponseProjection; +import no.nav.saf.Tilknytning; +import no.nav.saf.TilknyttedeJournalposterQueryRequest; +import no.nav.saf.TilknyttedeJournalposterQueryResponse; +import no.nav.vedtak.exception.TekniskException; +import no.nav.vedtak.felles.integrasjon.rest.RestKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestRequest; +import no.nav.vedtak.log.mdc.MDCOperations; +import no.nav.vedtak.mapper.json.DefaultJsonMapper; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.WARN) +class SafNativeKlientTest { + + private SafNativeTjeneste safTjeneste; + + @Mock + private RestKlient restKlient; + + URI endpoint = URI.create("http://dummyendpoint/graphql"); + + private static class SafRequest extends RestRequest { + private SafRequest() { + super(new TestContextSupplier()); + } + } + + @BeforeEach + void setUp() throws IOException { + // Service setup + MDCOperations.putCallId(); + safTjeneste = new SafNativeTjeneste(restKlient, new SafRequest(), endpoint); + } + + @SuppressWarnings("resource") + @Test + void skal_returnere_dokumentoversikt_fagsak() throws IOException { + // query-eksempel: dokumentoversiktFagsak(fagsak: {fagsakId: "2019186111", + // fagsaksystem: "AO01"}, foerste: 5) + var resource = getClass().getClassLoader().getResource("saf/documentResponse.json"); + var response = DefaultJsonMapper.fromJson(resource, DokumentoversiktFagsakQueryResponse.class); + var captor = ArgumentCaptor.forClass(HttpRequest.class); + when(restKlient.send(captor.capture(), any())).thenReturn(response); + + var query = new DokumentoversiktFagsakQueryRequest(); + query.setFagsak(new FagsakInput("fagsakId", "fagsaksystem")); + query.setFoerste(1000); + DokumentoversiktResponseProjection projection = byggDokumentoversiktResponseProjection(); + + Dokumentoversikt dokumentoversiktFagsak = safTjeneste.dokumentoversiktFagsak(query, projection); + + assertThat(dokumentoversiktFagsak.getJournalposter()).isNotEmpty(); + var rq = captor.getValue(); + assertThat(rq.headers().map().get("Authorization")).isNotEmpty(); + assertThat(rq.headers().map().get("Nav-Consumer-Id")).contains("user"); + } + + @SuppressWarnings("resource") + @Test + void skal_returnere_journalpost() throws IOException { + // query-eksempel: journalpost(journalpostId: "439560100") + var resource = getClass().getClassLoader().getResource("saf/journalpostResponse.json"); + var response = DefaultJsonMapper.fromJson(resource, JournalpostQueryResponse.class); + when(restKlient.send(any(HttpRequest.class), any())).thenReturn(response); + + var query = new JournalpostQueryRequest(); + query.setJournalpostId("journalpostId"); + var projection = byggJournalpostResponseProjection(); + + var journalpost = safTjeneste.hentJournalpostInfo(query, projection); + + assertThat(journalpost.getJournalpostId()).isNotEmpty(); + } + + @SuppressWarnings("resource") + @Test + void skal_returnere_tilknyttet_journalpost() throws IOException { + // query-eksempel: tilknyttedeJournalposter(dokumentInfoId:"469211538", + // tilknytning:GJENBRUK) + var resource = getClass().getClassLoader().getResource("saf/tilknyttetResponse.json"); + var response = DefaultJsonMapper.fromJson(resource, TilknyttedeJournalposterQueryResponse.class); + when(restKlient.send(any(HttpRequest.class), any())).thenReturn(response); + + var query = new TilknyttedeJournalposterQueryRequest(); + query.setDokumentInfoId("dokumentInfoId"); + query.setTilknytning(Tilknytning.GJENBRUK); + var projection = new JournalpostResponseProjection() + .journalpostId() + .eksternReferanseId(); + + List journalposter = safTjeneste.hentTilknyttedeJournalposter(query, projection); + + assertThat(journalposter).hasSize(2); + assertThat(journalposter.stream().map(Journalpost::getEksternReferanseId).filter(Objects::nonNull).findFirst()).isPresent(); + } + + @SuppressWarnings("resource") + @Test + void skal_konvertere_feilmelding_til_feil() throws IOException { + // query-eksempel: journalpost(journalpostId: "439560100") + var resource = getClass().getClassLoader().getResource("saf/errorResponse.json"); + var response = DefaultJsonMapper.fromJson(resource, JournalpostQueryResponse.class); + when(restKlient.send(any(HttpRequest.class), any())).thenReturn(response); + + var query = new JournalpostQueryRequest(); + query.setJournalpostId("journalpostId"); + var projection = byggJournalpostResponseProjection(); + + assertThrows(TekniskException.class, () -> safTjeneste.hentJournalpostInfo(query, projection)); + } + + @Test + void skal_returnere_dokument() throws IOException { + // GET-eksempel: hentdokument/{journalpostId}/{dokumentInfoId}/{variantFormat} + Optional respons = Optional.of("".getBytes()); + var captor = ArgumentCaptor.forClass(HttpRequest.class); + when(restKlient.sendHandleResponse(captor.capture())).thenReturn(respons); + HentDokumentQuery query = new HentDokumentQuery("journalpostId", "dokumentInfoId", "ARKIVF"); + + byte[] dokument = safTjeneste.hentDokument(query); + + assertThat(dokument).isEqualTo("".getBytes()); + var rq = captor.getValue(); + assertThat(rq.uri().toString()).contains("hentdokument/journalpostId/dokumentInfoId/ARKIVF"); + } + + private DokumentoversiktResponseProjection byggDokumentoversiktResponseProjection() { + return new DokumentoversiktResponseProjection() + .journalposter(new JournalpostResponseProjection() + .journalpostId() + .tittel() + .journalposttype() + .journalstatus() + .kanal() + .tema() + .behandlingstema() + .sak(new SakResponseProjection() + .arkivsaksnummer() + .arkivsaksystem() + .fagsaksystem() + .fagsakId()) + .bruker(new BrukerResponseProjection() + .id() + .type()) + .avsenderMottaker(new AvsenderMottakerResponseProjection() + .id() + .type() + .navn()) + .journalfoerendeEnhet() + .dokumenter(new DokumentInfoResponseProjection() + .dokumentInfoId() + .tittel() + .brevkode() + .dokumentvarianter(new DokumentvariantResponseProjection() + .variantformat() + .filnavn() + .filtype() + .saksbehandlerHarTilgang()) + .logiskeVedlegg(new LogiskVedleggResponseProjection() + .tittel())) + .datoOpprettet() + .relevanteDatoer(new RelevantDatoResponseProjection() + .dato() + .datotype()) + .eksternReferanseId()); + } + + private JournalpostResponseProjection byggJournalpostResponseProjection() { + return new JournalpostResponseProjection() + .journalpostId() + .tittel() + .journalposttype() + .journalstatus() + .kanal() + .tema() + .behandlingstema() + .sak(new SakResponseProjection() + .arkivsaksnummer() + .arkivsaksystem() + .fagsaksystem() + .fagsakId()) + .bruker(new BrukerResponseProjection() + .id() + .type()) + .avsenderMottaker(new AvsenderMottakerResponseProjection() + .id() + .type() + .navn()) + .journalfoerendeEnhet() + .dokumenter(new DokumentInfoResponseProjection() + .dokumentInfoId() + .tittel() + .brevkode() + .dokumentvarianter(new DokumentvariantResponseProjection() + .variantformat() + .filnavn()) + .logiskeVedlegg(new LogiskVedleggResponseProjection() + .tittel())) + .datoOpprettet() + .relevanteDatoer(new RelevantDatoResponseProjection() + .dato() + .datotype()) + .eksternReferanseId(); + } +} diff --git a/integrasjon/saf-klient/src/test/java/no/nav/vedtak/felles/integrasjon/saf/TestContextSupplier.java b/integrasjon/saf-klient/src/test/java/no/nav/vedtak/felles/integrasjon/saf/TestContextSupplier.java new file mode 100644 index 000000000..4acf10f2b --- /dev/null +++ b/integrasjon/saf-klient/src/test/java/no/nav/vedtak/felles/integrasjon/saf/TestContextSupplier.java @@ -0,0 +1,47 @@ +package no.nav.vedtak.felles.integrasjon.saf; + +import java.util.function.Supplier; + +import no.nav.vedtak.felles.integrasjon.rest.RequestContextSupplier; +import no.nav.vedtak.sikkerhet.oidc.config.OpenIDProvider; +import no.nav.vedtak.sikkerhet.oidc.token.OpenIDToken; +import no.nav.vedtak.sikkerhet.oidc.token.SikkerhetContext; +import no.nav.vedtak.sikkerhet.oidc.token.TokenString; + +public final class TestContextSupplier implements RequestContextSupplier { + + private static final String DUMMY_TOKEN = "TOKEN"; + + public TestContextSupplier() { + } + + @Override + public Supplier consumerIdFor(SikkerhetContext context) { + return () -> "user"; + } + + @Override + public Supplier tokenFor(SikkerhetContext context) { + return () -> new OpenIDToken(OpenIDProvider.TOKENX, new TokenString(DUMMY_TOKEN)); + } + + @Override + public Supplier stsSystemToken() { + return () -> new OpenIDToken(OpenIDProvider.ISSO, new TokenString(DUMMY_TOKEN)); + } + + @Override + public Supplier azureSystemToken(String scope) { + return () -> new OpenIDToken(OpenIDProvider.ISSO, new TokenString(DUMMY_TOKEN)); + } + + @Override + public Supplier consumerId() { + return () -> "system"; + } + + @Override + public Supplier consumerToken() { + return () -> new OpenIDToken(OpenIDProvider.ISSO, new TokenString(DUMMY_TOKEN)); + } +} diff --git "a/integrasjon/spokelse-klient/src/main/java/no/nav/vedtak/felles/integrasjon/spokelse/Sp\303\270kelseNativeKlient.java" "b/integrasjon/spokelse-klient/src/main/java/no/nav/vedtak/felles/integrasjon/spokelse/Sp\303\270kelseNativeKlient.java" new file mode 100644 index 000000000..8c8c8bd73 --- /dev/null +++ "b/integrasjon/spokelse-klient/src/main/java/no/nav/vedtak/felles/integrasjon/spokelse/Sp\303\270kelseNativeKlient.java" @@ -0,0 +1,68 @@ +package no.nav.vedtak.felles.integrasjon.spokelse; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.core.UriBuilder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import no.nav.foreldrepenger.konfig.KonfigVerdi; +import no.nav.vedtak.exception.TekniskException; +import no.nav.vedtak.felles.integrasjon.rest.NativeKlient; +import no.nav.vedtak.felles.integrasjon.rest.RestKlient; + +@NativeKlient +@ApplicationScoped +public class SpøkelseNativeKlient implements Spøkelse { + + private static final Logger LOG = LoggerFactory.getLogger(SpøkelseNativeKlient.class); + + private RestKlient restKlient; + private URI uri; + private String scope; + + @Inject + public SpøkelseNativeKlient(RestKlient restKlient, + @KonfigVerdi(value = "SPOKELSE_GRUNNLAG_URL", defaultVerdi = "http://spokelse.tbd/grunnlag") URI uri, + @KonfigVerdi(value = "SPOKELSE_GRUNNLAG_SCOPES", defaultVerdi = "api://prod-fss.tbd.spokelse/.default") String scope) { + this.restKlient = restKlient; + this.uri = uri; + this.scope = scope; + } + + SpøkelseNativeKlient() { + // CDI + } + + @Override + public List hentGrunnlag(String fnr) { + try { + var path = UriBuilder.fromUri(uri) + .queryParam("fodselsnummer", fnr) + .build(); + var request = restKlient.request().builderSystemAzure(scope) + .uri(path) + .GET() + .build(); + var grunnlag = restKlient.send(request, SykepengeVedtak[].class); + return Arrays.asList(grunnlag); + } catch (Exception e) { + throw new TekniskException( "FP-180126", String.format("SPokelse %s gir feil, ta opp med team sykepenger.", uri.toString()), e); + } + } + + @Override + public List hentGrunnlagFailSoft(String fnr) { + try { + return hentGrunnlag(fnr); + } catch (Exception e) { + LOG.info("SPokelse felles: feil ved oppslag mot {}, returnerer ingen grunnlag", uri, e); + return List.of(); + } + } +}