Skip to content

Commit

Permalink
Token x (#808)
Browse files Browse the repository at this point in the history
  • Loading branch information
jan-olaveide authored May 17, 2021
1 parent b0b47b0 commit f9ad463
Show file tree
Hide file tree
Showing 16 changed files with 371 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import no.nav.foreldrepenger.konfig.KonfigVerdi;
import no.nav.vedtak.felles.integrasjon.rest.jersey.Jersey;
import no.nav.vedtak.felles.integrasjon.rest.jersey.OnBehalfOfTokenRequestFilter;
import no.nav.vedtak.felles.integrasjon.rest.jersey.tokenx.TokenXRequestFilter;

@Dependent
@Jersey("onbehalf")
Expand All @@ -17,6 +17,11 @@ public class OnBehalfOfJerseyPdlKlient extends AbstractJerseyPdlKlient {
public OnBehalfOfJerseyPdlKlient(
@KonfigVerdi(value = "pdl.base.url", defaultVerdi = HTTP_PDL_API_DEFAULT_GRAPHQL) URI endpoint,
@KonfigVerdi(value = "pdl.tema", defaultVerdi = FOR) String tema) {
super(endpoint, new OnBehalfOfTokenRequestFilter(tema));
this(endpoint, new TokenXRequestFilter(tema));
}

OnBehalfOfJerseyPdlKlient(URI endpoint, TokenXRequestFilter tokenXTokenRequestFilter) {
super(endpoint, tokenXTokenRequestFilter);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
Expand All @@ -68,6 +69,8 @@
import no.nav.vedtak.exception.TekniskException;
import no.nav.vedtak.felles.integrasjon.rest.jersey.StsAccessTokenClientRequestFilter;
import no.nav.vedtak.felles.integrasjon.rest.jersey.StsAccessTokenJerseyClient;
import no.nav.vedtak.felles.integrasjon.rest.jersey.tokenx.TokenXClient;
import no.nav.vedtak.felles.integrasjon.rest.jersey.tokenx.TokenXRequestFilter;
import no.nav.vedtak.sikkerhet.context.SubjectHandler;
import no.nav.vedtak.sikkerhet.domene.SAMLAssertionCredential;

Expand All @@ -84,6 +87,9 @@ class TestJerseyPdlClient {
private Pdl legacyClient;
private Pdl tokenXClient;

@Mock
TokenXClient client;

@Mock
private StsAccessTokenJerseyClient sts;
@Mock
Expand Down Expand Up @@ -112,9 +118,8 @@ static void stopServer() {

@BeforeEach
void beforeEach() throws Exception {
when(sts.getUsername()).thenReturn(USERNAME);
legacyClient = new JerseyPdlKlient(URI, new StsAccessTokenClientRequestFilter(sts, FOR, cache));
tokenXClient = new OnBehalfOfJerseyPdlKlient(URI, FOR);
tokenXClient = new OnBehalfOfJerseyPdlKlient(URI, new TokenXRequestFilter(FOR, client));
}

@Test
Expand All @@ -135,9 +140,9 @@ void testErrorHandler() throws Exception {
@Test
@DisplayName("Test at kun Authorization og Tema blir satt for tokenX token")
void testPersonAuthWithUserToken() throws Exception {
when(sts.accessToken()).thenReturn(SYSTEMTOKEN);
doReturn(TOKENXTOKEN).when(subjectHandler).getInternSsoToken();
try (var s = mockStatic(SubjectHandler.class)) {
when(client.exchange(Mockito.any(), Mockito.any())).thenReturn(TOKENXTOKEN);
s.when(SubjectHandler::getSubjectHandler).thenReturn(subjectHandler);
stubFor(post(urlPathEqualTo(GRAPHQL))
.withHeader(ACCEPT, equalTo(APPLICATION_JSON))
Expand All @@ -152,7 +157,6 @@ void testPersonAuthWithUserToken() throws Exception {
res = legacyClient.hentPerson(pq(), pp());
assertNotNull(res.getNavn());
assertNotNull(res.getNavn().get(0).getFornavn());
verify(sts).accessToken();
}
}

Expand Down
17 changes: 16 additions & 1 deletion integrasjon/rest-klient/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>no.nav.foreldrepenger</groupId>
<artifactId>konfig</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>io.github.kobylynskyi</groupId>
<artifactId>graphql-java-codegen</artifactId>
Expand Down Expand Up @@ -103,5 +108,15 @@
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
</dependency>
<dependency>
<groupId>no.nav.security</groupId>
<artifactId>token-client-core</artifactId>
<version>1.3.7</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.9.3</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package no.nav.vedtak.felles.integrasjon.graphql;

import static java.lang.String.format;
import static java.util.stream.Collectors.joining;

import java.net.URI;
Expand All @@ -12,7 +13,7 @@
public class GraphQLException extends IntegrasjonException {

public GraphQLException(String kode, List<GraphQLError> errors, URI uri) {
super(kode, String.format("Feil %s ved GraphQL oppslag mot %s", errors.stream()
super(kode, format("Feil %s ved GraphQL oppslag mot %s", errors.stream()
.map(GraphQLError::getMessage)
.collect(joining(",")), uri));
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public String accessToken() {
}).get("access_token");
}

public String getUsername() {
return cfg.getUsername();
@Override
public String toString() {
return getClass().getSimpleName() + " [cfg=" + cfg + "]";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package no.nav.vedtak.felles.integrasjon.rest.jersey.tokenx;

import static com.nimbusds.jose.JOSEObjectType.JWT;
import static com.nimbusds.jose.JWSAlgorithm.RS256;

import java.time.Instant;
import java.util.Date;
import java.util.UUID;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

class TokenXAssertionGenerator {

private final TokenXConfig cfg;
private final TokenXConfigMetadata metadata;

TokenXAssertionGenerator(TokenXConfig cfg, TokenXConfigMetadata metadata) {
this.cfg = cfg;
this.metadata = metadata;
}

public String assertion() {
var now = Date.from(Instant.now());
try {
return sign(new JWTClaimsSet.Builder()
.subject(cfg.clientId())
.issuer(cfg.clientId())
.audience(metadata.tokenEndpoint().toString())
.issueTime(now)
.notBeforeTime(now)
.expirationTime(Date.from(Instant.now().plusSeconds(60)))
.jwtID(UUID.randomUUID().toString())
.build(), cfg.rsaKey())
.serialize();
} catch (JOSEException e) {
throw new IllegalArgumentException(e);
}
}

private static SignedJWT sign(JWTClaimsSet claimsSet, RSAKey rsaKey) throws JOSEException {
var signedJWT = new SignedJWT(new JWSHeader.Builder(RS256)
.keyID(rsaKey.getKeyID())
.type(JWT).build(), claimsSet);
signedJWT.sign(new RSASSASigner(rsaKey.toPrivateKey()));
return signedJWT;
}

@Override
public String toString() {
return getClass().getSimpleName() + " [cfg=" + cfg + ", metadata=" + metadata + "]";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package no.nav.vedtak.felles.integrasjon.rest.jersey.tokenx;

import java.util.StringJoiner;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import no.nav.foreldrepenger.konfig.Cluster;
import no.nav.foreldrepenger.konfig.Namespace;

public record TokenXAudience(Cluster cluster, Namespace namespace, String app) {

private static final Logger LOG = LoggerFactory.getLogger(TokenXAudience.class);
public String asAudience() {
var joiner = new StringJoiner(":");
joiner.add(cluster.clusterName());
joiner.add(namespace.getName());
joiner.add(app);
var audience = joiner.toString();
LOG.trace("Audience er {}", audience);
return audience;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package no.nav.vedtak.felles.integrasjon.rest.jersey.tokenx;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import lombok.val;
import no.nav.foreldrepenger.konfig.Cluster;
import no.nav.foreldrepenger.konfig.Environment;
import no.nav.foreldrepenger.konfig.Namespace;

public class TokenXAudienceGenerator {
private static final Logger LOG = LoggerFactory.getLogger(TokenXAudienceGenerator.class);
private static final Environment ENV = Environment.current();

public TokenXAudience audience(URI uri) {
LOG.trace("Utleder audience for {}", uri);
String host = uri.getHost();
val elems = host.split("\\.");

if (elems.length == 1) {
return new TokenXAudience(cluster(host), ENV.getNamespace(), elems[0]);
}
if (elems.length == 2) {
return new TokenXAudience(cluster(host), Namespace.of(elems[1]), elems[0]);
}
throw new IllegalArgumentException("Kan ikke analysere " + host + "(" + elems.length + ")");
}

private Cluster cluster(String key) {
return Optional.ofNullable(ENV.getProperty(key))
.map(Cluster::of)
.orElseGet(() -> ENV.getCluster());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package no.nav.vedtak.felles.integrasjon.rest.jersey.tokenx;

public interface TokenXClient {

String exchange(String token, TokenXAudience audience);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package no.nav.vedtak.felles.integrasjon.rest.jersey.tokenx;

import java.net.URI;
import java.text.ParseException;

import com.nimbusds.jose.jwk.RSAKey;

import no.nav.foreldrepenger.konfig.Environment;

public record TokenXConfig(URI wellKnownUrl, String clientId, String privateJwk) {

private static final Environment ENV = Environment.current();

static TokenXConfig fraEnv() {
return new TokenXConfig(ENV.getRequiredProperty("token.x.well.known.url", URI.class),
ENV.getRequiredProperty("token.x.client.id"),
ENV.getRequiredProperty("token.x.private.jwk"));
}

public RSAKey rsaKey() {
try {
return RSAKey.parse(privateJwk);
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
}

@Override
public String toString() {
return getClass().getSimpleName() + " [wellKnownUrl=" + wellKnownUrl + ",clientId=" + clientId + "]";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package no.nav.vedtak.felles.integrasjon.rest.jersey.tokenx;

import com.fasterxml.jackson.annotation.JsonProperty;

record TokenXConfigMetadata(String issuer,
@JsonProperty("token_endpoint") String tokenEndpoint,
@JsonProperty("jwks_uri") String jwksUri) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package no.nav.vedtak.felles.integrasjon.rest.jersey.tokenx;

import static com.nimbusds.oauth2.sdk.auth.JWTAuthentication.CLIENT_ASSERTION_TYPE;
import static javax.ws.rs.client.Entity.form;
import static javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED_TYPE;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;

import java.net.URI;

import javax.ws.rs.core.Form;

import no.nav.vedtak.felles.integrasjon.rest.jersey.AbstractJerseyRestClient;

public class TokenXJerseyClient extends AbstractJerseyRestClient implements TokenXClient {

private final TokenXAssertionGenerator assertionGenerator;
private final TokenXConfigMetadata metadata;

public TokenXJerseyClient() {
this(TokenXConfig.fraEnv());
}

public TokenXJerseyClient(TokenXConfig cfg) {
this.metadata = metadataFra(cfg.wellKnownUrl());
this.assertionGenerator = new TokenXAssertionGenerator(cfg, metadata);
}

TokenXJerseyClient(TokenXConfigMetadata metdata, TokenXAssertionGenerator assertionGenerator) {
this.metadata = metdata;
this.assertionGenerator = assertionGenerator;
}

@Override
public String exchange(String token, TokenXAudience audience) {
var form = new Form()
.param("subject_token", token)
.param("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")
.param("client_assertion_type", CLIENT_ASSERTION_TYPE)
.param("client_assertion", assertionGenerator.assertion())
.param("subject_token_type", "urn:ietf:params:oauth:token-type:jwt")
.param("audience", audience.asAudience());

return client
.target(metadata.tokenEndpoint())
.request(APPLICATION_FORM_URLENCODED_TYPE)
.post(form(form), TokenXResponse.class).accessToken();
}

private TokenXConfigMetadata metadataFra(URI uri) {
return client
.target(uri)
.request(APPLICATION_JSON_TYPE)
.get(TokenXConfigMetadata.class);

}

@Override
public String toString() {
return getClass().getSimpleName() + " [assertionGenerator=" + assertionGenerator + ", metadata=" + metadata + "]";
}
}
Loading

0 comments on commit f9ad463

Please sign in to comment.