From b3e225e6b5f4f32a8e993c2757aa79fc090ca908 Mon Sep 17 00:00:00 2001 From: Enrico Risa Date: Fri, 27 Oct 2023 15:04:57 +0200 Subject: [PATCH] feat: Secure Token Service remote implementation --- .../SecureTokenServiceApiController.java | 1 + .../api/sts/model/StsTokenRequest.java | 2 +- .../StsClientTokenGeneratorServiceImpl.java | 24 +++--- ...StsClientTokenIssuanceIntegrationTest.java | 12 +-- .../identity-trust-sts-embedded/README.md | 37 +++++++++ .../embedded/EmbeddedSecureTokenService.java | 12 +-- ...ddedSecureTokenServiceIntegrationTest.java | 21 +++-- ...StsRemoteClientConfigurationExtension.java | 3 +- .../remote/core/StsRemoteClientExtension.java | 4 +- .../sts/remote/RemoteSecureTokenService.java | 20 +++-- .../remote/StsRemoteClientConfiguration.java | 12 +-- .../remote/RemoteSecureTokenServiceTest.java | 19 ++--- .../SelfIssuedTokenConstants.java | 40 ++++++++++ .../model/StsClientTokenAdditionalParams.java | 12 +++ .../edc/jwt/spi/JwtRegisteredClaimNames.java | 7 ++ .../e2e/sts/api/RemoteStsEndToEndTest.java | 73 ++++------------- .../test/e2e/sts/api/StsApiEndToEndTest.java | 78 ++++-------------- .../test/e2e/sts/api/StsEndToEndTestBase.java | 80 +++++++++++++++++++ 18 files changed, 269 insertions(+), 188 deletions(-) create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-embedded/README.md create mode 100644 spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/SelfIssuedTokenConstants.java create mode 100644 system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/sts/api/StsEndToEndTestBase.java diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/controller/SecureTokenServiceApiController.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/controller/SecureTokenServiceApiController.java index db5be52e480..1594d2d6c1e 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/controller/SecureTokenServiceApiController.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/controller/SecureTokenServiceApiController.java @@ -67,6 +67,7 @@ private StsClientTokenAdditionalParams additionalParams(StsTokenRequest request) .audience(request.getAudience()) .accessToken(request.getAccessToken()) .bearerAccessScope(request.getBearerAccessScope()) + .bearerAccessAlias(request.getBearerAccessAlias()) .build(); } diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenRequest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenRequest.java index 0a2ccecee4d..88f8e546f09 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenRequest.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenRequest.java @@ -27,7 +27,7 @@ *
  • clientSecret: Authorization secret for the client/
  • *
  • audience: Audience according to the spec.
  • *
  • bearerAccessScope: Space-delimited scopes to be included in the access_token claim.
  • - *
  • bearerAccessAlias: Alias to be use in the sub of the VP access token (default is audience).
  • + *
  • bearerAccessAlias: Alias to be used in the sub of the VP access token (default is audience).
  • *
  • accessToken: VP/VC Access Token to be included as access_token claim.
  • *
  • grantType: Type of grant. Must be client_credentials.
  • * diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientTokenGeneratorServiceImpl.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientTokenGeneratorServiceImpl.java index 8b73048354c..f5148a15903 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientTokenGeneratorServiceImpl.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientTokenGeneratorServiceImpl.java @@ -29,6 +29,8 @@ import java.util.Optional; import java.util.function.Function; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.ACCESS_TOKEN; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.BEARER_ACCESS_ALIAS; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.CLIENT_ID; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER; @@ -36,8 +38,10 @@ public class StsClientTokenGeneratorServiceImpl implements StsClientTokenGeneratorService { - public static final String ACCESS_TOKEN_CLAIM = "access_token"; - + private static final Map> CLAIM_MAPPERS = Map.of( + ACCESS_TOKEN, StsClientTokenAdditionalParams::getAccessToken, + BEARER_ACCESS_ALIAS, StsClientTokenAdditionalParams::getBearerAccessAlias); + private final long tokenExpiration; private final StsTokenGenerationProvider tokenGenerationProvider; private final Clock clock; @@ -46,7 +50,6 @@ public StsClientTokenGeneratorServiceImpl(StsTokenGenerationProvider tokenGenera this.tokenGenerationProvider = tokenGenerationProvider; this.clock = clock; this.tokenExpiration = tokenExpiration; - } @Override @@ -59,9 +62,12 @@ public ServiceResult tokenFor(StsClient client, StsClientTo AUDIENCE, additionalParams.getAudience(), CLIENT_ID, client.getClientId()); - var claims = Optional.ofNullable(additionalParams.getAccessToken()) - .map(enrichClaims(initialClaims)) - .orElse(initialClaims); + var claims = CLAIM_MAPPERS.entrySet().stream() + .filter(entry -> entry.getValue().apply(additionalParams) != null) + .reduce(initialClaims, (accumulator, entity) -> + Optional.ofNullable(entity.getValue().apply(additionalParams)) + .map(enrichClaimsWith(accumulator, entity.getKey())) + .orElse(accumulator), (a, b) -> b); var tokenResult = embeddedTokenGenerator.createToken(claims, additionalParams.getBearerAccessScope()) .map(this::enrichWithExpiration); @@ -80,10 +86,10 @@ private TokenRepresentation enrichWithExpiration(TokenRepresentation tokenRepres .build(); } - private Function> enrichClaims(Map claims) { - return (token) -> { + private Function> enrichClaimsWith(Map claims, String claim) { + return (claimValue) -> { var newClaims = new HashMap<>(claims); - newClaims.put(ACCESS_TOKEN_CLAIM, token); + newClaims.put(claim, claimValue); return Collections.unmodifiableMap(newClaims); }; } diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-core/src/test/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/StsClientTokenIssuanceIntegrationTest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-core/src/test/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/StsClientTokenIssuanceIntegrationTest.java index 0e2ee69961c..c3eb718b1c8 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-core/src/test/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/StsClientTokenIssuanceIntegrationTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-core/src/test/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/StsClientTokenIssuanceIntegrationTest.java @@ -38,7 +38,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.iam.identitytrust.sts.store.fixtures.TestFunctions.createClientBuilder; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.ACCESS_TOKEN; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; +import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.CLIENT_ID; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.EXPIRATION_TIME; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUED_AT; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER; @@ -94,7 +96,7 @@ void authenticateAndGenerateToken() throws Exception { .containsEntry(ISSUER, id) .containsEntry(SUBJECT, id) .containsEntry(AUDIENCE, List.of(audience)) - .containsEntry("client_id", clientId) + .containsEntry(CLIENT_ID, clientId) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT); } @@ -127,12 +129,11 @@ void authenticateAndGenerateToken_withBearerAccessScope() throws Exception { .containsEntry(ISSUER, id) .containsEntry(SUBJECT, id) .containsEntry(AUDIENCE, List.of(audience)) - .containsEntry("client_id", clientId) + .containsEntry(CLIENT_ID, clientId) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT, "access_token"); } - @Test void authenticateAndGenerateToken_withAccessToken() throws Exception { var id = "id"; @@ -161,13 +162,12 @@ void authenticateAndGenerateToken_withAccessToken() throws Exception { .containsEntry(ISSUER, id) .containsEntry(SUBJECT, id) .containsEntry(AUDIENCE, List.of(audience)) - .containsEntry("client_id", clientId) - .containsEntry("access_token", accessToken) + .containsEntry(CLIENT_ID, clientId) + .containsEntry(ACCESS_TOKEN, accessToken) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT); } - /** * Load content from a resource file. */ diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-embedded/README.md b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-embedded/README.md new file mode 100644 index 00000000000..cf0af737241 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-embedded/README.md @@ -0,0 +1,37 @@ +# Embedded Secure Token Service (STS) Extension + +## Overview + +This module implements the `SecureTokenService` spi, which will be used for generating the Self-Issued ID Token +in the `IATP` protocol flow. This is an embeddable implementation, which can be used in the same process of +the EDC control-plane runtime. + +## Self-Issued ID Token Contents + +As outlined in the [IATP](https://github.com/eclipse-tractusx/identity-trust/blob/main/specifications/M1/identity.protocol.base.md#41-self-issued-id-token-contents) spec +the token includes the following claims: + +- The `iss` and `sub` claims MUST be equal and set to the bearer's (participant's) DID. +- The `sub_jwk` claim is not used +- The `aud` set to the `participant_id` of the relying party (RP) +- The `client_id` set to the `participant_id` of the consumer +- The `jti` claim that is used to mitigate against replay attacks +- The `exp` expiration time of the token +- The `access_token` VP Access Token (Optional) + +Additionally, when generating the Self-Issued ID Token the `bearerAccessScope` parameter is passed the additional claim +`access_token` claim is added. + +## VP Access Token format + +The `IATP` protocol does not specify the format of the VP Access Token, which it's up to the specific STS implementation. +In this implementation the VP access token is still a JWT token with the following claims: + +- The `iss` is the same of the SI token (participant's DID) +- The `sub` set to the `participant_id`/`alias (DID)` of the relying party (RP) +- The `aud` set to the `participant_id` of the participant +- The `jti` claim that is used to mitigate against replay attacks +- The `exp` expiration time of the token + +`CredentialService` implementors, should verify that the `sub` of the Self-Issued ID token and the `sub` of +the VP access token matches. \ No newline at end of file diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-embedded/src/main/java/org/eclipse/edc/iam/identitytrust/sts/embedded/EmbeddedSecureTokenService.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-embedded/src/main/java/org/eclipse/edc/iam/identitytrust/sts/embedded/EmbeddedSecureTokenService.java index 6886403f3cd..b9f4bcf64e5 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-embedded/src/main/java/org/eclipse/edc/iam/identitytrust/sts/embedded/EmbeddedSecureTokenService.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-embedded/src/main/java/org/eclipse/edc/iam/identitytrust/sts/embedded/EmbeddedSecureTokenService.java @@ -30,8 +30,11 @@ import static java.lang.String.format; import static java.util.Optional.ofNullable; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.ACCESS_TOKEN; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.BEARER_ACCESS_ALIAS; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER; +import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SCOPE; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SUBJECT; import static org.eclipse.edc.spi.result.Result.failure; import static org.eclipse.edc.spi.result.Result.success; @@ -43,9 +46,6 @@ */ public class EmbeddedSecureTokenService implements SecureTokenService { - public static final String SCOPE_CLAIM = "scope"; - public static final String ACCESS_TOKEN_CLAIM = "access_token"; - public static final String BEARER_ACCESS_ALIAS_CLAIM = "bearer_access_alias"; private static final List ACCESS_TOKEN_INHERITED_CLAIMS = List.of(ISSUER); private final TokenGenerationService tokenGenerationService; private final Clock clock; @@ -69,16 +69,16 @@ public Result createToken(Map claims, @Null private Result createAndAcceptAccessToken(Map claims, String scope, BiConsumer consumer) { return createAccessToken(claims, scope) .compose(tokenRepresentation -> success(tokenRepresentation.getToken())) - .onSuccess(withClaim(ACCESS_TOKEN_CLAIM, consumer)) + .onSuccess(withClaim(ACCESS_TOKEN, consumer)) .mapTo(); } private Result createAccessToken(Map claims, String bearerAccessScope) { var accessTokenClaims = new HashMap<>(accessTokenInheritedClaims(claims)); - accessTokenClaims.put(SCOPE_CLAIM, bearerAccessScope); + accessTokenClaims.put(SCOPE, bearerAccessScope); return addClaim(claims, ISSUER, withClaim(AUDIENCE, accessTokenClaims::put)) .compose(v -> addClaim(claims, AUDIENCE, withClaim(SUBJECT, accessTokenClaims::put))) - .compose(v -> addOptionalClaim(claims, BEARER_ACCESS_ALIAS_CLAIM, withClaim(SUBJECT, accessTokenClaims::put))) + .compose(v -> addOptionalClaim(claims, BEARER_ACCESS_ALIAS, withClaim(SUBJECT, accessTokenClaims::put))) .compose(v -> tokenGenerationService.generate(new SelfIssuedTokenDecorator(accessTokenClaims, clock, validity))); } diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-embedded/src/test/java/org/eclipse/edc/iam/identitytrust/sts/embedded/EmbeddedSecureTokenServiceIntegrationTest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-embedded/src/test/java/org/eclipse/edc/iam/identitytrust/sts/embedded/EmbeddedSecureTokenServiceIntegrationTest.java index a0558e32066..38717872f0c 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-embedded/src/test/java/org/eclipse/edc/iam/identitytrust/sts/embedded/EmbeddedSecureTokenServiceIntegrationTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-embedded/src/test/java/org/eclipse/edc/iam/identitytrust/sts/embedded/EmbeddedSecureTokenServiceIntegrationTest.java @@ -40,15 +40,15 @@ import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.InstanceOfAssertFactories.STRING; -import static org.eclipse.edc.iam.identitytrust.sts.embedded.EmbeddedSecureTokenService.ACCESS_TOKEN_CLAIM; -import static org.eclipse.edc.iam.identitytrust.sts.embedded.EmbeddedSecureTokenService.BEARER_ACCESS_ALIAS_CLAIM; -import static org.eclipse.edc.iam.identitytrust.sts.embedded.EmbeddedSecureTokenService.SCOPE_CLAIM; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.ACCESS_TOKEN; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.BEARER_ACCESS_ALIAS; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.EXPIRATION_TIME; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUED_AT; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.JWT_ID; +import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SCOPE; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SUBJECT; @@ -83,7 +83,7 @@ void createToken_withoutBearerAccessScope() { assertThat(jwt.getJWTClaimsSet().getClaims()) .containsEntry(ISSUER, issuer) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT) - .doesNotContainKey(ACCESS_TOKEN_CLAIM); + .doesNotContainKey(ACCESS_TOKEN); }); } @@ -104,7 +104,7 @@ void createToken_withBearerAccessScope() { assertThat(jwt.getJWTClaimsSet().getClaims()) .containsEntry(ISSUER, issuer) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT) - .extractingByKey(ACCESS_TOKEN_CLAIM, as(STRING)) + .extractingByKey(ACCESS_TOKEN, as(STRING)) .satisfies(accessToken -> { var accessTokenJwt = SignedJWT.parse(accessToken); assertThat(accessTokenJwt.verify(createVerifier(accessTokenJwt.getHeader(), keyPair.getPublic()))).isTrue(); @@ -112,7 +112,7 @@ void createToken_withBearerAccessScope() { .containsEntry(ISSUER, issuer) .containsEntry(SUBJECT, audience) .containsEntry(AUDIENCE, List.of(issuer)) - .containsEntry(SCOPE_CLAIM, scopes) + .containsEntry(SCOPE, scopes) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT); }); }); @@ -124,7 +124,7 @@ void createToken_withBearerAccessAlias() { var issuer = "testIssuer"; var audience = "audience"; var bearerAccessAlias = "alias"; - var claims = Map.of(ISSUER, issuer, AUDIENCE, audience, BEARER_ACCESS_ALIAS_CLAIM, bearerAccessAlias); + var claims = Map.of(ISSUER, issuer, AUDIENCE, audience, BEARER_ACCESS_ALIAS, bearerAccessAlias); var tokenResult = secureTokenService.createToken(claims, scopes); assertThat(tokenResult).isSucceeded() @@ -135,7 +135,7 @@ void createToken_withBearerAccessAlias() { assertThat(jwt.getJWTClaimsSet().getClaims()) .containsEntry(ISSUER, issuer) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT) - .extractingByKey(ACCESS_TOKEN_CLAIM, as(STRING)) + .extractingByKey(ACCESS_TOKEN, as(STRING)) .satisfies(accessToken -> { var accessTokenJwt = SignedJWT.parse(accessToken); assertThat(accessTokenJwt.verify(createVerifier(accessTokenJwt.getHeader(), keyPair.getPublic()))).isTrue(); @@ -143,13 +143,12 @@ void createToken_withBearerAccessAlias() { .containsEntry(ISSUER, issuer) .containsEntry(SUBJECT, bearerAccessAlias) .containsEntry(AUDIENCE, List.of(issuer)) - .containsEntry(SCOPE_CLAIM, scopes) + .containsEntry(SCOPE, scopes) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT); }); }); } - - + @ParameterizedTest @ArgumentsSource(ClaimsArguments.class) void createToken_shouldFail_withMissingClaims(Map claims) { diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/core/StsRemoteClientConfigurationExtension.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/core/StsRemoteClientConfigurationExtension.java index e45dd6324e0..a772378929f 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/core/StsRemoteClientConfigurationExtension.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/core/StsRemoteClientConfigurationExtension.java @@ -41,8 +41,7 @@ public class StsRemoteClientConfigurationExtension implements ServiceExtension { public static final String CLIENT_SECRET_ALIAS = "edc.iam.sts.oauth.client.secret.alias"; protected static final String NAME = "Sts remote client configuration extension"; - - + @Inject private Vault vault; diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/core/StsRemoteClientExtension.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/core/StsRemoteClientExtension.java index 5c71d092d2d..223056741c9 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/core/StsRemoteClientExtension.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/core/StsRemoteClientExtension.java @@ -28,11 +28,9 @@ */ @Extension(StsRemoteClientExtension.NAME) public class StsRemoteClientExtension implements ServiceExtension { - - + protected static final String NAME = "Sts remote client configuration extension"; - @Inject private StsRemoteClientConfiguration clientConfiguration; diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/RemoteSecureTokenService.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/RemoteSecureTokenService.java index 848ba46d166..5bfdb943260 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/RemoteSecureTokenService.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/RemoteSecureTokenService.java @@ -26,19 +26,21 @@ import java.util.Map; import java.util.stream.Collectors; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.ACCESS_TOKEN; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.BEARER_ACCESS_ALIAS; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.BEARER_ACCESS_SCOPE; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; public class RemoteSecureTokenService implements SecureTokenService { public static final String GRANT_TYPE = "client_credentials"; - public static final String ACCESS_TOKEN_PARAM = "access_token"; public static final String AUDIENCE_PARAM = "audience"; - public static final String BEARER_ACCESS_ALIAS_PARAM = "bearer_access_alias"; - public static final String BEARER_ACCESS_SCOPE_PARAM = "bearer_access_scope"; + private static final Map CLAIM_MAPPING = Map.of( AUDIENCE, AUDIENCE_PARAM, - BEARER_ACCESS_ALIAS_PARAM, BEARER_ACCESS_ALIAS_PARAM, - ACCESS_TOKEN_PARAM, ACCESS_TOKEN_PARAM); + BEARER_ACCESS_ALIAS, BEARER_ACCESS_ALIAS, + ACCESS_TOKEN, ACCESS_TOKEN); + private final Oauth2Client oauth2Client; private final StsRemoteClientConfiguration configuration; @@ -59,18 +61,14 @@ private Oauth2CredentialsRequest createRequest(Map claims, @Null .clientId(configuration.getClientId()) .clientSecret(configuration.getClientSecret()) .grantType(GRANT_TYPE); - - if (configuration.getScope() != null) { - builder.scope(configuration.getScope()); - } - + var additionalParams = claims.entrySet().stream() .filter(entry -> CLAIM_MAPPING.containsKey(entry.getKey())) .map(entry -> Map.entry(CLAIM_MAPPING.get(entry.getKey()), entry.getValue())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); if (bearerAccessScope != null) { - additionalParams.put(BEARER_ACCESS_SCOPE_PARAM, bearerAccessScope); + additionalParams.put(BEARER_ACCESS_SCOPE, bearerAccessScope); } builder.params(additionalParams); diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/StsRemoteClientConfiguration.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/StsRemoteClientConfiguration.java index bc78b484c2b..c789b903cac 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/StsRemoteClientConfiguration.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/StsRemoteClientConfiguration.java @@ -20,15 +20,10 @@ * Configuration of the OAuth2 client */ public class StsRemoteClientConfiguration { + private String tokenUrl; private String clientId; - private String clientSecret; - private String scope; - - public String getScope() { - return scope; - } public String getClientId() { return clientId; @@ -62,11 +57,6 @@ public Builder clientId(String clientId) { return this; } - public Builder scope(String scope) { - configuration.scope = scope; - return this; - } - public Builder clientSecret(String clientSecret) { configuration.clientSecret = clientSecret; return this; diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote/src/test/java/org/eclipse/edc/iam/identitytrust/sts/remote/RemoteSecureTokenServiceTest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote/src/test/java/org/eclipse/edc/iam/identitytrust/sts/remote/RemoteSecureTokenServiceTest.java index bb8b773704b..29745d6e94e 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote/src/test/java/org/eclipse/edc/iam/identitytrust/sts/remote/RemoteSecureTokenServiceTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote/src/test/java/org/eclipse/edc/iam/identitytrust/sts/remote/RemoteSecureTokenServiceTest.java @@ -25,11 +25,11 @@ import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.iam.identitytrust.sts.remote.RemoteSecureTokenService.ACCESS_TOKEN_PARAM; import static org.eclipse.edc.iam.identitytrust.sts.remote.RemoteSecureTokenService.AUDIENCE_PARAM; -import static org.eclipse.edc.iam.identitytrust.sts.remote.RemoteSecureTokenService.BEARER_ACCESS_ALIAS_PARAM; -import static org.eclipse.edc.iam.identitytrust.sts.remote.RemoteSecureTokenService.BEARER_ACCESS_SCOPE_PARAM; import static org.eclipse.edc.iam.identitytrust.sts.remote.RemoteSecureTokenService.GRANT_TYPE; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.ACCESS_TOKEN; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.BEARER_ACCESS_ALIAS; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.BEARER_ACCESS_SCOPE; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; import static org.mockito.ArgumentMatchers.any; @@ -44,6 +44,7 @@ public class RemoteSecureTokenServiceTest { .clientSecret("secret") .tokenUrl("url") .build(); + private final Oauth2Client oauth2Client = mock(); private RemoteSecureTokenService secureTokenService; @@ -88,7 +89,7 @@ void createToken_withAccessScope() { assertThat(request.getClientSecret()).isEqualTo(configuration.getClientSecret()); assertThat(request.getParams()) .containsEntry(AUDIENCE_PARAM, audience) - .containsEntry(BEARER_ACCESS_SCOPE_PARAM, bearerAccessScope); + .containsEntry(BEARER_ACCESS_SCOPE, bearerAccessScope); }); } @@ -97,7 +98,7 @@ void createToken_withAccessToken() { var audience = "aud"; var accessToken = "accessToken"; when(oauth2Client.requestToken(any())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().build())); - assertThat(secureTokenService.createToken(Map.of(AUDIENCE, audience, ACCESS_TOKEN_PARAM, accessToken), null)).isSucceeded(); + assertThat(secureTokenService.createToken(Map.of(AUDIENCE, audience, ACCESS_TOKEN, accessToken), null)).isSucceeded(); var captor = ArgumentCaptor.forClass(SharedSecretOauth2CredentialsRequest.class); verify(oauth2Client).requestToken(captor.capture()); @@ -109,7 +110,7 @@ void createToken_withAccessToken() { assertThat(request.getClientSecret()).isEqualTo(configuration.getClientSecret()); assertThat(request.getParams()) .containsEntry(AUDIENCE_PARAM, audience) - .containsEntry(ACCESS_TOKEN_PARAM, accessToken); + .containsEntry(ACCESS_TOKEN, accessToken); }); } @@ -123,7 +124,7 @@ void createToken_withBearerAccessTokenAlias() { var claims = Map.of( AUDIENCE, audience, - BEARER_ACCESS_ALIAS_PARAM, bearerAccessAlias); + BEARER_ACCESS_ALIAS, bearerAccessAlias); assertThat(secureTokenService.createToken(claims, bearerAccessScope)).isSucceeded(); @@ -137,8 +138,8 @@ void createToken_withBearerAccessTokenAlias() { assertThat(request.getClientSecret()).isEqualTo(configuration.getClientSecret()); assertThat(request.getParams()) .containsEntry(AUDIENCE_PARAM, audience) - .containsEntry(BEARER_ACCESS_ALIAS_PARAM, bearerAccessAlias) - .containsEntry(BEARER_ACCESS_SCOPE_PARAM, bearerAccessScope); + .containsEntry(BEARER_ACCESS_ALIAS, bearerAccessAlias) + .containsEntry(BEARER_ACCESS_SCOPE, bearerAccessScope); }); } } diff --git a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/SelfIssuedTokenConstants.java b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/SelfIssuedTokenConstants.java new file mode 100644 index 00000000000..7bfc072e31a --- /dev/null +++ b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/SelfIssuedTokenConstants.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.identitytrust; + +/** + * Constants for the Self-Issued ID Token + */ +public final class SelfIssuedTokenConstants { + + /** + * VP access token claim + */ + public static final String ACCESS_TOKEN = "access_token"; + + /** + * Alias to be used in the sub of the VP access token + */ + public static final String BEARER_ACCESS_ALIAS = "bearer_access_alias"; + + /** + * Scopes to be encoded in the VP access token + */ + public static final String BEARER_ACCESS_SCOPE = "bearer_access_scope"; + + private SelfIssuedTokenConstants() { + + } +} diff --git a/spi/common/identity-trust-sts-spi/src/main/java/org/eclipse/edc/iam/identitytrust/sts/model/StsClientTokenAdditionalParams.java b/spi/common/identity-trust-sts-spi/src/main/java/org/eclipse/edc/iam/identitytrust/sts/model/StsClientTokenAdditionalParams.java index 631beef5ee4..255839230a9 100644 --- a/spi/common/identity-trust-sts-spi/src/main/java/org/eclipse/edc/iam/identitytrust/sts/model/StsClientTokenAdditionalParams.java +++ b/spi/common/identity-trust-sts-spi/src/main/java/org/eclipse/edc/iam/identitytrust/sts/model/StsClientTokenAdditionalParams.java @@ -25,6 +25,9 @@ public class StsClientTokenAdditionalParams { private String bearerAccessScope; + + private String bearerAccessAlias; + private String audience; private String accessToken; @@ -44,6 +47,10 @@ public String getAccessToken() { return accessToken; } + public String getBearerAccessAlias() { + return bearerAccessAlias; + } + public static class Builder { private final StsClientTokenAdditionalParams params; @@ -67,6 +74,11 @@ public Builder bearerAccessScope(String bearerAccessScope) { return this; } + public Builder bearerAccessAlias(String bearerAccessAlias) { + params.bearerAccessAlias = bearerAccessAlias; + return this; + } + public Builder accessToken(String accessToken) { params.accessToken = accessToken; return this; diff --git a/spi/common/jwt-spi/src/main/java/org/eclipse/edc/jwt/spi/JwtRegisteredClaimNames.java b/spi/common/jwt-spi/src/main/java/org/eclipse/edc/jwt/spi/JwtRegisteredClaimNames.java index 3b8b751ed42..0d65c2ee3a0 100644 --- a/spi/common/jwt-spi/src/main/java/org/eclipse/edc/jwt/spi/JwtRegisteredClaimNames.java +++ b/spi/common/jwt-spi/src/main/java/org/eclipse/edc/jwt/spi/JwtRegisteredClaimNames.java @@ -76,6 +76,13 @@ public final class JwtRegisteredClaimNames { */ public static final String CLIENT_ID = "client_id"; + + /** + * "scope" (Scopes) Claim + * + * @see RFC 8693 "scope" (Scopes) Claim + */ + public static final String SCOPE = "scope"; private JwtRegisteredClaimNames() { } diff --git a/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/sts/api/RemoteStsEndToEndTest.java b/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/sts/api/RemoteStsEndToEndTest.java index 8c623f0bac3..f0d8a4ab53d 100644 --- a/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/sts/api/RemoteStsEndToEndTest.java +++ b/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/sts/api/RemoteStsEndToEndTest.java @@ -14,30 +14,25 @@ package org.eclipse.edc.test.e2e.sts.api; -import com.nimbusds.jwt.SignedJWT; -import org.eclipse.edc.iam.identitytrust.sts.model.StsClient; import org.eclipse.edc.iam.identitytrust.sts.remote.RemoteSecureTokenService; import org.eclipse.edc.iam.identitytrust.sts.remote.StsRemoteClientConfiguration; -import org.eclipse.edc.iam.identitytrust.sts.store.StsClientStore; import org.eclipse.edc.iam.oauth2.client.Oauth2ClientImpl; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.result.Failure; -import org.eclipse.edc.spi.security.Vault; import org.eclipse.edc.spi.types.TypeManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.text.ParseException; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.iam.identitytrust.sts.store.fixtures.TestFunctions.createClient; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.ACCESS_TOKEN; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.BEARER_ACCESS_ALIAS; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.edc.junit.testfixtures.TestUtils.getFreePort; import static org.eclipse.edc.junit.testfixtures.TestUtils.testHttpClient; @@ -50,7 +45,7 @@ import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SUBJECT; @EndToEndTest -public class RemoteStsEndToEndTest { +public class RemoteStsEndToEndTest extends StsEndToEndTestBase { public static final int PORT = getFreePort(); public static final String STS_TOKEN_PATH = "http://localhost:" + PORT + "/sts/token"; @@ -84,7 +79,7 @@ void setup() { @Test void requestToken() { var audience = "audience"; - var params = Map.of("aud", audience); + var params = Map.of(AUDIENCE, audience); var client = initClient(config.getClientId(), config.getClientSecret()); @@ -105,10 +100,11 @@ void requestToken() { @Test - void requestToken_withBearerScope() { + void requestToken_withBearerScopeAndAlias() { var audience = "audience"; var bearerAccessScope = "org.test.Member:read org.test.GoldMember:read"; - var params = Map.of("aud", audience); + var bearerAccessAlias = "alias"; + var params = Map.of(AUDIENCE, audience, BEARER_ACCESS_ALIAS, bearerAccessAlias); var client = initClient(config.getClientId(), config.getClientSecret()); @@ -123,10 +119,10 @@ void requestToken_withBearerScope() { .containsEntry(AUDIENCE, List.of(audience)) .containsEntry(CLIENT_ID, client.getClientId()) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT) - .hasEntrySatisfying("access_token", (accessToken) -> { + .hasEntrySatisfying(ACCESS_TOKEN, (accessToken) -> { assertThat(parseClaims((String) accessToken)) .containsEntry(ISSUER, client.getId()) - .containsEntry(SUBJECT, audience) + .containsEntry(SUBJECT, bearerAccessAlias) .containsEntry(AUDIENCE, List.of(client.getClientId())) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT); @@ -140,13 +136,11 @@ void requestToken_withAttachedAccessToken() { var audience = "audience"; var accessToken = "test_token"; var params = Map.of( - "aud", audience, - "access_token", accessToken); - + AUDIENCE, audience, + ACCESS_TOKEN, accessToken); var client = initClient(config.getClientId(), config.getClientSecret()); - assertThat(remoteSecureTokenService.createToken(params, null)) .isSucceeded() .extracting(TokenRepresentation::getToken) @@ -156,7 +150,7 @@ void requestToken_withAttachedAccessToken() { .containsEntry(SUBJECT, client.getId()) .containsEntry(AUDIENCE, List.of(audience)) .containsEntry(CLIENT_ID, client.getClientId()) - .containsEntry("access_token", accessToken) + .containsEntry(ACCESS_TOKEN, accessToken) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT); }); } @@ -164,52 +158,17 @@ void requestToken_withAttachedAccessToken() { @Test void requestToken_shouldReturnError_whenClientNotFound() { var audience = "audience"; - var params = Map.of("aud", audience); + var params = Map.of(AUDIENCE, audience); assertThat(remoteSecureTokenService.createToken(params, null)).isFailed() .extracting(Failure::getFailureDetail) .satisfies(failure -> assertThat(failure).contains("Invalid client")); } - - private StsClient initClient(String clientId, String clientSecret) { - var store = getClientStore(); - var vault = getVault(); - var clientSecretAlias = "client_secret_alias"; - var client = createClient(clientId, clientSecretAlias); - - - vault.storeSecret(clientSecretAlias, clientSecret); - vault.storeSecret(client.getPrivateKeyAlias(), loadResourceFile("ec-privatekey.pem")); - store.create(client); - - return client; - } - private StsClientStore getClientStore() { - return sts.getContext().getService(StsClientStore.class); + @Override + protected EdcRuntimeExtension getRuntime() { + return sts; } - private Vault getVault() { - return sts.getContext().getService(Vault.class); - } - - /** - * Load content from a resource file. - */ - private String loadResourceFile(String file) { - try (var resourceAsStream = RemoteStsEndToEndTest.class.getClassLoader().getResourceAsStream(file)) { - return new String(Objects.requireNonNull(resourceAsStream).readAllBytes()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private Map parseClaims(String token) { - try { - return SignedJWT.parse(token).getJWTClaimsSet().getClaims(); - } catch (ParseException e) { - throw new RuntimeException(e); - } - } } diff --git a/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/sts/api/StsApiEndToEndTest.java b/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/sts/api/StsApiEndToEndTest.java index 27582d433d6..229f637dc62 100644 --- a/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/sts/api/StsApiEndToEndTest.java +++ b/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/sts/api/StsApiEndToEndTest.java @@ -14,14 +14,10 @@ package org.eclipse.edc.test.e2e.sts.api; -import com.nimbusds.jwt.SignedJWT; import io.restassured.response.ValidatableResponse; import io.restassured.specification.RequestSpecification; -import org.eclipse.edc.iam.identitytrust.sts.model.StsClient; -import org.eclipse.edc.iam.identitytrust.sts.store.StsClientStore; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; -import org.eclipse.edc.spi.security.Vault; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -30,12 +26,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import static io.restassured.RestAssured.given; import static io.restassured.http.ContentType.JSON; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.iam.identitytrust.sts.store.fixtures.TestFunctions.createClient; +import static org.eclipse.edc.identitytrust.SelfIssuedTokenConstants.ACCESS_TOKEN; import static org.eclipse.edc.junit.testfixtures.TestUtils.getFreePort; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.CLIENT_ID; @@ -48,7 +43,7 @@ import static org.hamcrest.Matchers.notNullValue; @EndToEndTest -public class StsApiEndToEndTest { +public class StsApiEndToEndTest extends StsEndToEndTestBase { public static final int PORT = getFreePort(); public static final String BASE_STS = "http://localhost:" + PORT + "/sts"; @@ -90,9 +85,7 @@ void requestToken() throws ParseException { .body() .jsonPath().getString("access_token"); - var jwt = SignedJWT.parse(token); - - assertThat(jwt.getJWTClaimsSet().getClaims()) + assertThat(parseClaims(token)) .containsEntry(ISSUER, client.getId()) .containsEntry(SUBJECT, client.getId()) .containsEntry(AUDIENCE, List.of(audience)) @@ -127,27 +120,18 @@ void requestToken_withBearerScope() throws ParseException { .jsonPath().getString("access_token"); - var jwt = SignedJWT.parse(token); - - assertThat(jwt.getJWTClaimsSet().getClaims()) + assertThat(parseClaims(token)) .containsEntry(ISSUER, client.getId()) .containsEntry(SUBJECT, client.getId()) .containsEntry(AUDIENCE, List.of(audience)) .containsEntry(CLIENT_ID, client.getClientId()) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT) - .hasEntrySatisfying("access_token", (accessToken) -> { - try { - var accessTokenJwt = SignedJWT.parse(((String) accessToken)); - - assertThat(accessTokenJwt.getJWTClaimsSet().getClaims()) - .containsEntry(ISSUER, client.getId()) - .containsEntry(SUBJECT, audience) - .containsEntry(AUDIENCE, List.of(client.getClientId())) - .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT); - - } catch (ParseException e) { - throw new RuntimeException(e); - } + .hasEntrySatisfying(ACCESS_TOKEN, (accessToken) -> { + assertThat(parseClaims((String) accessToken)) + .containsEntry(ISSUER, client.getId()) + .containsEntry(SUBJECT, audience) + .containsEntry(AUDIENCE, List.of(client.getClientId())) + .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT); }); } @@ -175,15 +159,13 @@ void requestToken_withAttachedAccessScope() throws IOException, ParseException { .body() .jsonPath().getString("access_token"); - - var jwt = SignedJWT.parse(token); - - assertThat(jwt.getJWTClaimsSet().getClaims()) + + assertThat(parseClaims(token)) .containsEntry(ISSUER, client.getId()) .containsEntry(SUBJECT, client.getId()) .containsEntry(AUDIENCE, List.of(audience)) .containsEntry(CLIENT_ID, client.getClientId()) - .containsEntry("access_token", accessToken) + .containsEntry(ACCESS_TOKEN, accessToken) .containsKeys(JWT_ID, EXPIRATION_TIME, ISSUED_AT); } @@ -220,37 +202,9 @@ protected RequestSpecification baseRequest() { .when(); } - private StsClient initClient(String clientSecret) { - var store = getClientStore(); - var vault = getVault(); - var clientId = "client_id"; - var clientSecretAlias = "client_secret_alias"; - var client = createClient(clientId, clientSecretAlias); - - - vault.storeSecret(clientSecretAlias, clientSecret); - vault.storeSecret(client.getPrivateKeyAlias(), loadResourceFile("ec-privatekey.pem")); - store.create(client); - - return client; - } - - private StsClientStore getClientStore() { - return sts.getContext().getService(StsClientStore.class); + @Override + protected EdcRuntimeExtension getRuntime() { + return sts; } - private Vault getVault() { - return sts.getContext().getService(Vault.class); - } - - /** - * Load content from a resource file. - */ - private String loadResourceFile(String file) { - try (var resourceAsStream = StsApiEndToEndTest.class.getClassLoader().getResourceAsStream(file)) { - return new String(Objects.requireNonNull(resourceAsStream).readAllBytes()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } } diff --git a/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/sts/api/StsEndToEndTestBase.java b/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/sts/api/StsEndToEndTestBase.java new file mode 100644 index 00000000000..f1722342e97 --- /dev/null +++ b/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/sts/api/StsEndToEndTestBase.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.test.e2e.sts.api; + +import com.nimbusds.jwt.SignedJWT; +import org.eclipse.edc.iam.identitytrust.sts.model.StsClient; +import org.eclipse.edc.iam.identitytrust.sts.store.StsClientStore; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; +import org.eclipse.edc.spi.security.Vault; + +import java.text.ParseException; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +import static org.eclipse.edc.iam.identitytrust.sts.store.fixtures.TestFunctions.createClient; + +/** + * Base class for STS E2E tests + */ +public abstract class StsEndToEndTestBase { + + protected abstract EdcRuntimeExtension getRuntime(); + + protected StsClient initClient(String clientId, String clientSecret) { + var store = getClientStore(); + var vault = getVault(); + var clientSecretAlias = "client_secret_alias"; + var client = createClient(clientId, clientSecretAlias); + + vault.storeSecret(clientSecretAlias, clientSecret); + vault.storeSecret(client.getPrivateKeyAlias(), loadResourceFile("ec-privatekey.pem")); + store.create(client); + + return client; + } + + protected StsClient initClient(String clientSecret) { + return initClient(UUID.randomUUID().toString(), clientSecret); + } + + protected Map parseClaims(String token) { + try { + return SignedJWT.parse(token).getJWTClaimsSet().getClaims(); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + private StsClientStore getClientStore() { + return getRuntime().getContext().getService(StsClientStore.class); + } + + private Vault getVault() { + return getRuntime().getContext().getService(Vault.class); + } + + /** + * Load content from a resource file. + */ + private String loadResourceFile(String file) { + try (var resourceAsStream = RemoteStsEndToEndTest.class.getClassLoader().getResourceAsStream(file)) { + return new String(Objects.requireNonNull(resourceAsStream).readAllBytes()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +}