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();
+ }
+ }
+}