Skip to content

Commit

Permalink
Bedre ergonomi request/klient, azure-støtte (#1180)
Browse files Browse the repository at this point in the history
* Bedre ergonomi request/klient, azure-støtte

* Adaptive flow as a dwim for targets accepting all tokens

* Testforbedring

* Reklassifiser norg

* ikke eklassifiser norg
  • Loading branch information
jolarsen authored Sep 19, 2022
1 parent 11bf3f9 commit cb534e9
Show file tree
Hide file tree
Showing 39 changed files with 510 additions and 504 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
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;
Expand Down Expand Up @@ -53,12 +52,15 @@ public String send(HttpClientRequest request, Set<Integer> acceptStatus) {
return ResponseHandler.handleResponse(doSendExpectStringRetry(httpRequest), httpRequest.uri(), acceptStatus);
}

public Optional<byte[]> sendHandleResponse(HttpClientRequest request) {
public byte[] sendReturnByteArray(HttpClientRequest request) {
var httpRequest = request.request();
return Optional.ofNullable(ResponseHandler.handleResponse(doSendExpectBytearrayRetry(httpRequest), httpRequest.uri(), Set.of()));
return ResponseHandler.handleResponse(doSendExpectBytearrayRetry(httpRequest), httpRequest.uri(), Set.of());
}

public HttpResponse<String> sendNoResponseHandler(HttpClientRequest request) {
/**
* Raw response, not checked for status codes 4nn or 5nn - please ensure that any usage avoids "quiet errors"
*/
public HttpResponse<String> sendReturnUnhandled(HttpClientRequest request) {
return doSendExpectStringRetry(request.request());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package no.nav.vedtak.klient.http;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.net.http.HttpRequest;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -14,8 +11,6 @@
import java.util.function.Consumer;
import java.util.function.Supplier;

import no.nav.vedtak.log.mdc.MDCOperations;

/**
* Encapsulation of java.net.http.HttpRequest to supply specific headers and ensure a timeout is set.
* Supports delayed header-setting and request validation + headers for authorization / basic
Expand All @@ -24,86 +19,36 @@
*/
public class HttpClientRequest {

protected 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 static final Map<String, Supplier<String>> DEFAULT_CALLID =
Map.of(HEADER_NAV_CALLID, ensureCallId(), HEADER_NAV_CALL_ID, ensureCallId());

private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(10);

private final HttpRequest.Builder builder;
private Duration timeout;
private final Map<String, Supplier<String>> headers;
private final Map<Supplier<String>, String> dependentHeaders;
private final List<Consumer<HttpRequest>> validators;

private HttpClientRequest() {
this(HttpRequest.newBuilder());
this(HttpRequest.newBuilder(), null);
}

protected HttpClientRequest(HttpRequest.Builder builder) {
this(builder, true);
}

private HttpClientRequest(HttpRequest.Builder builder, boolean medCallId) {
protected HttpClientRequest(HttpRequest.Builder builder, Map<String, Supplier<String>> headers) {
this.builder = builder;
this.headers = medCallId ? new HashMap<>(DEFAULT_CALLID) : new HashMap<>();
this.headers = headers != null ? new HashMap<>(headers) : new HashMap<>();
this.dependentHeaders = new HashMap<>();
this.validators = new ArrayList<>(List.of(HttpClientRequest::validateTimeout));
}

public static HttpClientRequest plain(HttpRequest.Builder builder) {
return new HttpClientRequest(builder, false);
}

public static HttpClientRequest callId(HttpRequest.Builder builder) {
return new HttpClientRequest(builder, true);
}

public HttpClientRequest timeout(Duration timeout) {
validateTimeout(timeout);
this.timeout = timeout;
return this;
}

public HttpClientRequest header(String header, String value) {
builder.header(header, value);
return this;
}

public HttpClientRequest delayedHeader(String header, Supplier<String> value) {
headers.put(header, value);
return this;
}

public HttpClientRequest consumerId(Supplier<String> consumerId) {
headers.put(HEADER_NAV_CONSUMER_ID, consumerId);
return this;
}

public HttpClientRequest consumerId(String consumerId) {
builder.header(HEADER_NAV_CONSUMER_ID, consumerId);
return this;
}

public HttpClientRequest otherCallId(String header) {
headers.put(header, ensureCallId());
return this;
}

public HttpClientRequest basicAuth(String username, String password) {
builder.header(CONTENT_TYPE, APPLICATION_FORM_ENCODED)
.header(AUTHORIZATION, basicCredentials(username, password));
return this;
}

public HttpClientRequest validator(Consumer<HttpRequest> validator) {
validators.add(validator);
return this;
Expand All @@ -112,29 +57,22 @@ public HttpClientRequest validator(Consumer<HttpRequest> validator) {
HttpRequest request() {
builder.timeout(Optional.ofNullable(timeout).orElse(DEFAULT_TIMEOUT));
headers.forEach((key, value) -> builder.header(key, value.get()));
dependentHeaders.forEach((key, value) -> Optional.ofNullable(key.get()).ifPresent(v -> builder.header(value, v)));
var request = builder.build();
validators.forEach(v -> v.accept(request));
return request;
}

protected static Supplier<String> ensureCallId() {
return () -> Optional.ofNullable(MDCOperations.getCallId())
.orElseGet(() -> {
MDCOperations.putCallId();
return MDCOperations.getCallId();
});
}

private static String basicCredentials(String username, String password) {
return BASIC_AUTH_HEADER_PREFIX + Base64.getEncoder().encodeToString(String.format("%s:%s", username, password).getBytes(UTF_8));
protected HttpRequest.Builder getBuilder() {
return builder;
}

private static void validateTimeout(HttpRequest request) {
validateTimeout(request.timeout().orElse(Duration.ZERO));
}

private static void validateTimeout(Duration timeout) {
if (Duration.ZERO.equals(timeout) || timeout == null || timeout.isNegative()) {
if (timeout == null || Duration.ZERO.equals(timeout) || timeout.isNegative()) {
throw new IllegalArgumentException("Utviklerfeil: ulovlig timeout");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ public enum IdentType {
Samhandler, // Annen organisasjon
Sikkerhet, // Ingen kjent bruk - potensielt ifm pip-requests ol.
Prosess // Ingen kjent bruk - foreslås brukt for prosesstasks
;

public boolean erSystem() {
return Systemressurs.equals(this) || Prosess.equals(this);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package no.nav.vedtak.sikkerhet.oidc.token;

import java.net.URI;
import java.util.Optional;

import no.nav.vedtak.exception.TekniskException;
import no.nav.vedtak.sikkerhet.context.containers.IdentType;
import no.nav.vedtak.sikkerhet.oidc.config.ConfigProvider;
import no.nav.vedtak.sikkerhet.oidc.config.OpenIDConfiguration;
Expand All @@ -17,22 +19,30 @@ public final class TokenProvider {

public static OpenIDToken getTokenFor(SikkerhetContext context) {
return switch (context) {
case BRUKER -> getTokenForBrukerMedFallbackDersomSaml(true);
case BRUKER -> getTokenFraContextFor(OpenIDProvider.ISSO, null, true);
case SYSTEM -> getStsSystemToken();
};
}

public static OpenIDToken getTokenUtenSamlFallback(SikkerhetContext context) {
return switch (context) {
case BRUKER -> getTokenForBrukerMedFallbackDersomSaml(false);
case BRUKER -> getTokenFraContextFor(OpenIDProvider.ISSO, null, false);
case SYSTEM -> getStsSystemToken();
};
}

public static OpenIDToken getTokenForScope(SikkerhetContext context, String scopes) {
public static OpenIDToken getTokenFor(SikkerhetContext context, OpenIDProvider provider, String scopes) {
return switch (context) {
case BRUKER -> getAzureTokenForBrukerMedFallbackDersomSaml(true, scopes);
case SYSTEM -> getAzureSystemToken(scopes);
case BRUKER -> getTokenFraContextFor(provider, scopes, true);
case SYSTEM -> OpenIDProvider.AZUREAD.equals(provider) ? getAzureSystemToken(scopes) : getStsSystemToken();
};
}

public static OpenIDToken getTokenFromCurrent(SikkerhetContext context, String scopes) {
var token = BrukerTokenProvider.getToken();
return switch (context) {
case BRUKER -> getTokenFraContextFor(token, scopes);
case SYSTEM -> OpenIDProvider.AZUREAD.equals(getProvider(token)) ? getAzureSystemToken(scopes) : getStsSystemToken();
};
}

Expand All @@ -43,36 +53,83 @@ public static String getUserIdFor(SikkerhetContext context) {
};
}

public static boolean isAzureContext() {
try {
return OpenIDProvider.AZUREAD.equals(getProvider(BrukerTokenProvider.getToken()));
} catch (Exception e) {
return false;
}
}

public static OpenIDToken getStsSystemToken() {
return StsSystemTokenKlient.hentAccessToken();
}

public static OpenIDToken getAzureSystemToken(String scope) {
return AzureSystemTokenKlient.instance().hentAccessToken(scope);
public static OpenIDToken getAzureSystemToken(String scopes) {
return AzureSystemTokenKlient.instance().hentAccessToken(scopes);
}

public static OpenIDToken veksleAzureAccessToken(OpenIDToken incoming, String scopes) {
return AzureBrukerTokenKlient.instance().oboExchangeToken(incoming, scopes);
}

public static OpenIDToken exchangeTokenX(OpenIDToken token, String assertion, URI targetEndpoint) {
// Assertion må være generert av den som skal bytte. Et JWT, RSA-signert, basert på injisert private jwk
return TokenXExchangeKlient.instance().exchangeToken(token, assertion, targetEndpoint);
}

private static OpenIDToken getTokenForBrukerMedFallbackDersomSaml(boolean fallback) {
if (!BrukerTokenProvider.harSattBrukerOidcToken() && BrukerTokenProvider.harSattBrukerSamlToken() && fallback) {
return getStsSystemToken();
private static OpenIDToken getTokenFraContextFor(OpenIDProvider provider, String scopes, boolean samlFallback) {
if (!BrukerTokenProvider.harSattBrukerOidcToken() && BrukerTokenProvider.harSattBrukerSamlToken() && samlFallback) {
return OpenIDProvider.AZUREAD.equals(provider) ? getAzureSystemToken(scopes) : getStsSystemToken();
}
var identType = Optional.ofNullable(BrukerTokenProvider.getIdentType()).orElse(IdentType.InternBruker);
var token = BrukerTokenProvider.getToken();
if (token == null || token.token() == null) {
return token;
//throw new IllegalStateException("Har ikke token i kontekst");
}
if (OpenIDProvider.AZUREAD.equals(provider)) {
if (identType.erSystem()) {
return getAzureSystemToken(scopes);
} else if (OpenIDProvider.AZUREAD.equals(token.provider())) {
return veksleAzureAccessToken(token, scopes);
} else {
throw ugyldigTokenCombo(token, identType, provider);
}
} else {
if (OpenIDProvider.AZUREAD.equals(token.provider()) && identType.erSystem()) {
return getStsSystemToken();
} else if (OpenIDProvider.AZUREAD.equals(token.provider())) {
throw ugyldigTokenCombo(token, identType, provider);
} else {
return token;
}
}
return BrukerTokenProvider.getToken();
}

private static OpenIDToken getAzureTokenForBrukerMedFallbackDersomSaml(boolean fallback, String scopes) {
if (!BrukerTokenProvider.harSattBrukerOidcToken() && BrukerTokenProvider.harSattBrukerSamlToken() && fallback) {
return getAzureSystemToken(scopes);
private static OpenIDToken getTokenFraContextFor(OpenIDToken incoming, String scopes) {
var providerIncoming = getProvider(incoming);
if (!BrukerTokenProvider.harSattBrukerOidcToken() && BrukerTokenProvider.harSattBrukerSamlToken()) {
return OpenIDProvider.AZUREAD.equals(providerIncoming) ? getAzureSystemToken(scopes) : getStsSystemToken();
}
var token = BrukerTokenProvider.getToken();
if (token != null && token.token() != null && OpenIDProvider.AZUREAD.equals(token.provider()) &&
IdentType.InternBruker.equals(BrukerTokenProvider.getIdentType())) {
return AzureBrukerTokenKlient.instance().oboExchangeToken(token, scopes);
if (incoming == null || incoming.token() == null) {
return incoming;
}
var identType = Optional.ofNullable(BrukerTokenProvider.getIdentType()).orElse(IdentType.InternBruker);
if (OpenIDProvider.AZUREAD.equals(providerIncoming)) {
return identType.erSystem() ? getAzureSystemToken(scopes) : veksleAzureAccessToken(incoming, scopes);
} else {
return incoming;
}
return token;
}

private static TekniskException ugyldigTokenCombo(OpenIDToken token, IdentType identType, OpenIDProvider provider) {
return new TekniskException("F-483213", String.format("Ugyldig veksling, har token fra %s for %s. Trenger %s",
token.provider(), identType, provider));
}

private static OpenIDProvider getProvider(OpenIDToken token) {
return Optional.ofNullable(token).map(OpenIDToken::provider).orElse(OpenIDProvider.ISSO);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public OpenIDToken oboExchangeToken(OpenIDToken incomingToken, String scopes) {
private HttpRequest lagRequest(String data) {
return HttpRequest.newBuilder()
.header("Cache-Control", "no-cache")
.header("Content-type", "application/x-www-form-urlencoded")
.header(Headers.CONTENT_TYPE, Headers.APPLICATION_FORM_ENCODED)
.timeout(Duration.ofSeconds(10))
.uri(tokenEndpoint)
.POST(HttpRequest.BodyPublishers.ofString(data, UTF_8))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public synchronized OpenIDToken hentAccessToken(String scope) {
private static OidcTokenResponse hentAccessToken(String clientId, String clientSecret, URI tokenEndpoint, URI proxy, String scope) {
var request = HttpRequest.newBuilder()
.header("Cache-Control", "no-cache")
.header("Content-type", "application/x-www-form-urlencoded")
.header(Headers.CONTENT_TYPE, Headers.APPLICATION_FORM_ENCODED)
.timeout(Duration.ofSeconds(10))
.uri(tokenEndpoint)
.POST(ofFormData(clientId, clientSecret, scope))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package no.nav.vedtak.sikkerhet.oidc.token.impl;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.util.Base64;

/**
* Standard header names for use in clients requesting / refreshing tokens
*/
final class Headers {

static final String CONTENT_TYPE = "Content-type";
static final String APPLICATION_FORM_ENCODED = "application/x-www-form-urlencoded";

static final String AUTHORIZATION = "Authorization";
static final String BASIC_AUTH_HEADER_PREFIX = "Basic ";

static String basicCredentials(String username, String password) {
return BASIC_AUTH_HEADER_PREFIX + Base64.getEncoder().encodeToString(String.format("%s:%s", username, password).getBytes(UTF_8));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ public static Optional<OpenIDToken> refreshIdToken(OpenIDToken expiredToken, Str

private static HttpRequest lagRequest(String clientName, String data) {
return HttpRequest.newBuilder()
.header("Authorization", basicCredentials(clientName, CONFIGURATION.clientSecret()))
.header(Headers.AUTHORIZATION, Headers.basicCredentials(clientName, CONFIGURATION.clientSecret()))
.header("Nav-Consumer-Id", CONFIGURATION.clientId())
.header("Nav-Call-Id", MDCOperations.getCallId())
.header("Cache-Control", "no-cache")
.header("Content-type", "application/x-www-form-urlencoded")
.header(Headers.CONTENT_TYPE, Headers.APPLICATION_FORM_ENCODED)
.timeout(Duration.ofSeconds(10))
.uri(CONFIGURATION.tokenEndpoint())
.POST(HttpRequest.BodyPublishers.ofString(data, UTF_8))
Expand Down
Loading

0 comments on commit cb534e9

Please sign in to comment.