From 8f58ce8c928d061070ff5e848c6bcd6749ea08bb Mon Sep 17 00:00:00 2001 From: Enrico Risa Date: Wed, 25 Oct 2023 16:31:27 +0200 Subject: [PATCH] feat: Standalone STS (#3556) * feat: Standalone STS * feat: STS client configuration extension + launcher * pr remarks * chore: update dependencies file * pr remarks --- DEPENDENCIES | 10 +- .../SecureTokenServiceApiController.java | 38 ----- .../api/sts/model/StsTokenRequest.java | 33 ---- .../service/StsClientServiceImpl.java | 4 +- .../StsClientTokenGeneratorServiceImpl.java | 13 +- ...tsClientTokenGeneratorServiceImplTest.java | 8 +- .../identity-trust-sts/build.gradle.kts | 24 +++ .../identity-trust-sts-api/build.gradle.kts | 3 + .../api/sts/SecureTokenServiceApi.java | 1 + .../sts/SecureTokenServiceApiExtension.java | 60 +++++++ .../api/sts/StsApiConfigurationExtension.java | 61 +++++++ .../configuration/StsApiConfiguration.java | 29 ++++ .../SecureTokenServiceApiController.java | 76 +++++++++ .../api/sts/exception/StsTokenException.java | 55 ++++++ .../exception/StsTokenExceptionMapper.java | 71 ++++++++ .../api/sts/model/StsTokenErrorResponse.java | 4 +- .../api/sts/model/StsTokenRequest.java | 122 ++++++++++++++ .../api/sts/model/StsTokenResponse.java | 4 +- .../validation/StsTokenRequestValidator.java | 53 ++++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 16 ++ .../SecureTokenServiceApiExtensionTest.java | 58 +++++++ .../sts/StsApiConfigurationExtensionTest.java | 71 ++++++++ .../SecureServiceTokenApiControllerTest.java | 156 ++++++++++++++++++ .../sts/model/StsTokenErrorResponseTest.java | 2 +- .../api/sts/model/StsTokenResponseTest.java | 0 .../StsTokenRequestValidatorTest.java | 72 ++++++++ .../build.gradle.kts | 25 +++ .../StsClientConfigurationExtension.java | 79 +++++++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 15 ++ .../StsClientConfigurationExtensionTest.java | 88 ++++++++++ launchers/README.md | 1 + launchers/sts-server/README.md | 70 ++++++++ launchers/sts-server/build.gradle.kts | 47 ++++++ launchers/sts-server/config.properties | 22 +++ launchers/sts-server/sts-vault.properties | 14 ++ settings.gradle.kts | 9 +- .../edc/spi/iam/TokenRepresentation.java | 13 ++ .../edc/jwt/spi/JwtRegisteredClaimNames.java | 19 ++- .../sts-api-test-runner/build.gradle.kts | 37 +++++ .../test/e2e/stsapi/StsApiEndToEndTest.java | 151 +++++++++++++++++ .../src/test/resources/ec-privatekey.pem | 5 + .../sts-api-test-runtime/build.gradle.kts | 28 ++++ 42 files changed, 1568 insertions(+), 99 deletions(-) delete mode 100644 extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/controller/SecureTokenServiceApiController.java delete mode 100644 extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenRequest.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/build.gradle.kts rename extensions/common/iam/identity-trust/{ => identity-trust-sts}/identity-trust-sts-api/build.gradle.kts (83%) rename extensions/common/iam/identity-trust/{ => identity-trust-sts}/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApi.java (99%) create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtension.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtension.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/configuration/StsApiConfiguration.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/controller/SecureTokenServiceApiController.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/exception/StsTokenException.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/exception/StsTokenExceptionMapper.java rename extensions/common/iam/identity-trust/{ => identity-trust-sts}/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponse.java (84%) create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenRequest.java rename extensions/common/iam/identity-trust/{ => identity-trust-sts}/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenResponse.java (88%) create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidator.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtensionTest.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtensionTest.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/controller/SecureServiceTokenApiControllerTest.java rename extensions/common/iam/identity-trust/{ => identity-trust-sts}/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponseTest.java (98%) rename extensions/common/iam/identity-trust/{ => identity-trust-sts}/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/model/StsTokenResponseTest.java (100%) create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidatorTest.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/build.gradle.kts create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/src/main/java/org/eclipse/edc/connector/api/sts/client/configuration/StsClientConfigurationExtension.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/src/test/java/org/eclipse/edc/connector/api/sts/client/configuration/StsClientConfigurationExtensionTest.java create mode 100644 launchers/sts-server/README.md create mode 100644 launchers/sts-server/build.gradle.kts create mode 100644 launchers/sts-server/config.properties create mode 100644 launchers/sts-server/sts-vault.properties create mode 100644 system-tests/sts-api/sts-api-test-runner/build.gradle.kts create mode 100644 system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/stsapi/StsApiEndToEndTest.java create mode 100644 system-tests/sts-api/sts-api-test-runner/src/test/resources/ec-privatekey.pem create mode 100644 system-tests/sts-api/sts-api-test-runtime/build.gradle.kts diff --git a/DEPENDENCIES b/DEPENDENCIES index 15c9ce4a25d..014de076129 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,5 +1,5 @@ maven/mavencentral/com.apicatalog/carbon-did/0.0.2, Apache-2.0, approved, #9239 -maven/mavencentral/com.apicatalog/iron-ed25519-cryptosuite-2020/0.8.1, , restricted, clearlydefined +maven/mavencentral/com.apicatalog/iron-ed25519-cryptosuite-2020/0.8.1, Apache-2.0, approved, #11157 maven/mavencentral/com.apicatalog/iron-verifiable-credentials/0.8.1, Apache-2.0, approved, #9234 maven/mavencentral/com.apicatalog/titanium-json-ld/1.0.0, Apache-2.0, approved, clearlydefined maven/mavencentral/com.apicatalog/titanium-json-ld/1.3.1, Apache-2.0, approved, #8912 @@ -83,11 +83,11 @@ maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.28, Apache-2.0, approved, clea maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.37, Apache-2.0, approved, #11086 maven/mavencentral/com.puppycrawl.tools/checkstyle/10.0, LGPL-2.1-or-later, approved, #7936 maven/mavencentral/com.samskivert/jmustache/1.15, BSD-2-Clause, approved, clearlydefined -maven/mavencentral/com.squareup.okhttp3/okhttp-dnsoverhttps/4.12.0, , restricted, clearlydefined -maven/mavencentral/com.squareup.okhttp3/okhttp/4.12.0, , restricted, clearlydefined +maven/mavencentral/com.squareup.okhttp3/okhttp-dnsoverhttps/4.12.0, Apache-2.0, approved, #11159 +maven/mavencentral/com.squareup.okhttp3/okhttp/4.12.0, Apache-2.0, approved, #11156 maven/mavencentral/com.squareup.okhttp3/okhttp/4.9.3, Apache-2.0 AND MPL-2.0, approved, #3225 -maven/mavencentral/com.squareup.okio/okio-jvm/3.6.0, , restricted, clearlydefined -maven/mavencentral/com.squareup.okio/okio/3.6.0, , restricted, clearlydefined +maven/mavencentral/com.squareup.okio/okio-jvm/3.6.0, Apache-2.0, approved, #11158 +maven/mavencentral/com.squareup.okio/okio/3.6.0, Apache-2.0, approved, #11155 maven/mavencentral/com.sun.activation/jakarta.activation/2.0.0, EPL-2.0 OR BSD-3-Clause OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jaf maven/mavencentral/com.sun.activation/jakarta.activation/2.0.1, EPL-2.0 OR BSD-3-Clause OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jaf maven/mavencentral/com.sun.mail/mailapi/1.6.2, CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0, approved, clearlydefined diff --git a/extensions/common/iam/identity-trust/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-api/src/main/java/org/eclipse/edc/connector/api/sts/controller/SecureTokenServiceApiController.java deleted file mode 100644 index 5d2b5d14082..00000000000 --- a/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/controller/SecureTokenServiceApiController.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.connector.api.sts.controller; - -import jakarta.ws.rs.BeanParam; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import org.eclipse.edc.connector.api.sts.SecureTokenServiceApi; -import org.eclipse.edc.connector.api.sts.model.StsTokenRequest; -import org.eclipse.edc.connector.api.sts.model.StsTokenResponse; - -@Path("/") -public class SecureTokenServiceApiController implements SecureTokenServiceApi { - - @Consumes({ MediaType.APPLICATION_FORM_URLENCODED }) - @Produces({ MediaType.APPLICATION_JSON }) - @Path("token") - @POST - @Override - public StsTokenResponse token(@BeanParam StsTokenRequest request) { - return null; - } -} diff --git a/extensions/common/iam/identity-trust/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-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenRequest.java deleted file mode 100644 index fcda01293dd..00000000000 --- a/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenRequest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.connector.api.sts.model; - -import jakarta.ws.rs.FormParam; - -/** - * OAuth2 Client Credentials Access Token Request - * - * @param grantType Type of grant. Must be client_credentials. - * @param clientId Client ID identifier. - * @param clientSecret Authorization secret for the client. - * @param bearerAccessScope Space-delimited scopes to be included in the access_token claim. - * @param accessToken VP/VC Access Token to be included as access_token claim. - */ -public record StsTokenRequest(@FormParam("grant_type") String grantType, - @FormParam("client_id") String clientId, - @FormParam("client_secret") String clientSecret, - @FormParam("bearer_access_scope") String bearerAccessScope, - @FormParam("access_token") String accessToken) { -} diff --git a/extensions/common/iam/identity-trust/identity-trust-sts-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientServiceImpl.java b/extensions/common/iam/identity-trust/identity-trust-sts-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientServiceImpl.java index 48f8bb7defd..1bb872f9896 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientServiceImpl.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientServiceImpl.java @@ -30,7 +30,7 @@ public class StsClientServiceImpl implements StsClientService { private final StsClientStore stsClientStore; private final TransactionContext transactionContext; private final Vault vault; - + public StsClientServiceImpl(StsClientStore stsClientStore, Vault vault, TransactionContext transactionContext) { this.stsClientStore = stsClientStore; this.vault = vault; @@ -52,6 +52,6 @@ public ServiceResult authenticate(StsClient client, String secret) { return Optional.ofNullable(vault.resolveSecret(client.getSecretAlias())) .filter(vaultSecret -> vaultSecret.equals(secret)) .map(s -> ServiceResult.success(client)) - .orElseGet(() -> ServiceResult.badRequest(format("Failed to authenticate client with id %s", client.getId()))); + .orElseGet(() -> ServiceResult.unauthorized(format("Failed to authenticate client with id %s", client.getId()))); } } diff --git a/extensions/common/iam/identity-trust/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-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientTokenGeneratorServiceImpl.java index 5d925fcc6b2..6f5109f1874 100644 --- a/extensions/common/iam/identity-trust/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-core/src/main/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientTokenGeneratorServiceImpl.java @@ -26,12 +26,12 @@ import java.util.Map; 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; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SUBJECT; public class StsClientTokenGeneratorServiceImpl implements StsClientTokenGeneratorService { - public static final String CLIENT_ID = "client_id"; private final long tokenExpiration; private final StsTokenGenerationProvider tokenGenerationProvider; private final Clock clock; @@ -53,7 +53,8 @@ public ServiceResult tokenFor(StsClient client, StsClientTo AUDIENCE, additionalParams.getAudience(), CLIENT_ID, client.getClientId()); - var tokenResult = embeddedTokenGenerator.createToken(claims, additionalParams.getBearerAccessScope()); + var tokenResult = embeddedTokenGenerator.createToken(claims, additionalParams.getBearerAccessScope()) + .map(this::enrichWithExpiration); if (tokenResult.failed()) { return ServiceResult.badRequest(tokenResult.getFailureDetail()); @@ -61,4 +62,12 @@ public ServiceResult tokenFor(StsClient client, StsClientTo return ServiceResult.success(tokenResult.getContent()); } + private TokenRepresentation enrichWithExpiration(TokenRepresentation tokenRepresentation) { + return TokenRepresentation.Builder.newInstance() + .token(tokenRepresentation.getToken()) + .additional(tokenRepresentation.getAdditional()) + .expiresIn(tokenExpiration) + .build(); + } + } diff --git a/extensions/common/iam/identity-trust/identity-trust-sts-core/src/test/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientTokenGeneratorServiceImplTest.java b/extensions/common/iam/identity-trust/identity-trust-sts-core/src/test/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientTokenGeneratorServiceImplTest.java index 08d6d68c3db..cab0afcaa65 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts-core/src/test/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientTokenGeneratorServiceImplTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts-core/src/test/java/org/eclipse/edc/iam/identitytrust/sts/core/defaults/service/StsClientTokenGeneratorServiceImplTest.java @@ -35,25 +35,27 @@ public class StsClientTokenGeneratorServiceImplTest { + public static final long TOKEN_EXPIRATION = 60 * 5; private final StsTokenGenerationProvider tokenGenerationProvider = mock(); private final TokenGenerationService tokenGenerator = mock(); private StsClientTokenGeneratorServiceImpl clientTokenService; + @BeforeEach void setup() { - clientTokenService = new StsClientTokenGeneratorServiceImpl(tokenGenerationProvider, Clock.systemUTC(), 60 * 5); + clientTokenService = new StsClientTokenGeneratorServiceImpl(tokenGenerationProvider, Clock.systemUTC(), TOKEN_EXPIRATION); } @Test void tokenFor() { var client = createClient("clientId"); - var token = TokenRepresentation.Builder.newInstance().token("token").build(); + var token = TokenRepresentation.Builder.newInstance().token("token").expiresIn(TOKEN_EXPIRATION).build(); when(tokenGenerationProvider.tokenGeneratorFor(client)).thenReturn(tokenGenerator); when(tokenGenerator.generate(any())).thenReturn(Result.success(token)); var inserted = clientTokenService.tokenFor(client, StsClientTokenAdditionalParams.Builder.newInstance().audience("aud").build()); - assertThat(inserted).isSucceeded().isEqualTo(token); + assertThat(inserted).isSucceeded().usingRecursiveComparison().isEqualTo(token); } @Test diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/build.gradle.kts b/extensions/common/iam/identity-trust/identity-trust-sts/build.gradle.kts new file mode 100644 index 00000000000..9012018b79f --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/build.gradle.kts @@ -0,0 +1,24 @@ +/* + * 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 + * + */ + +plugins { + `java-library` + `maven-publish` +} + +dependencies { + api(project(":extensions:common:iam:identity-trust:identity-trust-sts:identity-trust-sts-api")) + api(project(":extensions:common:iam:identity-trust:identity-trust-sts:identity-trust-sts-client-configuration")) +} + diff --git a/extensions/common/iam/identity-trust/identity-trust-sts-api/build.gradle.kts b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/build.gradle.kts similarity index 83% rename from extensions/common/iam/identity-trust/identity-trust-sts-api/build.gradle.kts rename to extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/build.gradle.kts index a49ffed4fbb..288f0670d16 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts-api/build.gradle.kts +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/build.gradle.kts @@ -20,6 +20,7 @@ plugins { dependencies { api(project(":spi:common:web-spi")) + api(project(":spi:common:identity-trust-sts-spi")) implementation(libs.jakarta.rsApi) implementation(libs.swagger.annotations.jakarta) @@ -29,5 +30,7 @@ dependencies { testImplementation(project(":core:common:junit")) testImplementation(testFixtures(project(":extensions:common:http:jersey-core"))) + testImplementation(testFixtures(project(":spi:common:identity-trust-sts-spi"))) + testImplementation(libs.restAssured) } diff --git a/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApi.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApi.java similarity index 99% rename from extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApi.java rename to extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApi.java index c3fc0c5c7f7..ffcfdb6418d 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApi.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApi.java @@ -39,4 +39,5 @@ public interface SecureTokenServiceApi { content = @Content(array = @ArraySchema(schema = @Schema(implementation = StsTokenErrorResponse.class)))) }) StsTokenResponse token(@BeanParam StsTokenRequest request); + } diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtension.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtension.java new file mode 100644 index 00000000000..0aaa6518bf4 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtension.java @@ -0,0 +1,60 @@ +/* + * 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.connector.api.sts; + +import org.eclipse.edc.connector.api.sts.configuration.StsApiConfiguration; +import org.eclipse.edc.connector.api.sts.controller.SecureTokenServiceApiController; +import org.eclipse.edc.connector.api.sts.exception.StsTokenExceptionMapper; +import org.eclipse.edc.connector.api.sts.validation.StsTokenRequestValidator; +import org.eclipse.edc.iam.identitytrust.sts.service.StsClientService; +import org.eclipse.edc.iam.identitytrust.sts.service.StsClientTokenGeneratorService; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.web.spi.WebService; + +@Extension(SecureTokenServiceApiExtension.NAME) +public class SecureTokenServiceApiExtension implements ServiceExtension { + + public static final String NAME = "Secure Token Service API"; + + @Inject + private StsApiConfiguration stsApiConfiguration; + + @Inject + private StsClientService clientService; + + @Inject + private StsClientTokenGeneratorService tokenService; + + @Inject + private Monitor monitor; + + @Inject + private WebService webService; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + webService.registerResource(stsApiConfiguration.getContextAlias(), new SecureTokenServiceApiController(clientService, tokenService, new StsTokenRequestValidator())); + webService.registerResource(stsApiConfiguration.getContextAlias(), new StsTokenExceptionMapper()); + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtension.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtension.java new file mode 100644 index 00000000000..f97bbef57c9 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtension.java @@ -0,0 +1,61 @@ +/* + * 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.connector.api.sts; + +import org.eclipse.edc.connector.api.sts.configuration.StsApiConfiguration; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.web.spi.WebServer; +import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; +import org.eclipse.edc.web.spi.configuration.WebServiceSettings; + +@Extension(value = StsApiConfigurationExtension.NAME) +@Provides({ StsApiConfiguration.class }) +public class StsApiConfigurationExtension implements ServiceExtension { + + public static final String NAME = "Secure Token Service API configuration"; + public static final String STS_CONTEXT_ALIAS = "sts"; + private static final String WEB_SERVICE_NAME = "STS API"; + private static final int DEFAULT_STS_API_PORT = 9292; + private static final String DEFAULT_STS_API_CONTEXT_PATH = "/api/v1/sts"; + + public static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance() + .apiConfigKey("web.http." + STS_CONTEXT_ALIAS) + .contextAlias(STS_CONTEXT_ALIAS) + .defaultPath(DEFAULT_STS_API_CONTEXT_PATH) + .defaultPort(DEFAULT_STS_API_PORT) + .useDefaultContext(true) + .name(WEB_SERVICE_NAME) + .build(); + + @Inject + private WebServer webServer; + @Inject + private WebServiceConfigurer configurator; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + var config = configurator.configure(context, webServer, SETTINGS); + context.registerService(StsApiConfiguration.class, new StsApiConfiguration(config)); + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/configuration/StsApiConfiguration.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/configuration/StsApiConfiguration.java new file mode 100644 index 00000000000..0d173c14646 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/configuration/StsApiConfiguration.java @@ -0,0 +1,29 @@ +/* + * 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.connector.api.sts.configuration; + +import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration; + +/** + * Configuration for the STS API + */ +public class StsApiConfiguration extends WebServiceConfiguration { + + public StsApiConfiguration(WebServiceConfiguration webServiceConfiguration) { + this.contextAlias = webServiceConfiguration.getContextAlias(); + this.path = webServiceConfiguration.getPath(); + this.port = webServiceConfiguration.getPort(); + } +} 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 new file mode 100644 index 00000000000..db5be52e480 --- /dev/null +++ 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 @@ -0,0 +1,76 @@ +/* + * 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.connector.api.sts.controller; + +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.edc.connector.api.sts.SecureTokenServiceApi; +import org.eclipse.edc.connector.api.sts.model.StsTokenRequest; +import org.eclipse.edc.connector.api.sts.model.StsTokenResponse; +import org.eclipse.edc.iam.identitytrust.sts.model.StsClientTokenAdditionalParams; +import org.eclipse.edc.iam.identitytrust.sts.service.StsClientService; +import org.eclipse.edc.iam.identitytrust.sts.service.StsClientTokenGeneratorService; +import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.validator.spi.Validator; + +import static org.eclipse.edc.connector.api.sts.exception.StsTokenException.tokenException; +import static org.eclipse.edc.connector.api.sts.exception.StsTokenException.validationException; + +@Path("/") +public class SecureTokenServiceApiController implements SecureTokenServiceApi { + + private final StsClientService clientService; + + private final StsClientTokenGeneratorService tokenService; + + private final Validator tokenRequestValidator; + + public SecureTokenServiceApiController(StsClientService clientService, StsClientTokenGeneratorService tokenService, Validator tokenRequestValidator) { + this.clientService = clientService; + this.tokenService = tokenService; + this.tokenRequestValidator = tokenRequestValidator; + } + + @Consumes({ MediaType.APPLICATION_FORM_URLENCODED }) + @Produces({ MediaType.APPLICATION_JSON }) + @Path("token") + @POST + @Override + public StsTokenResponse token(@BeanParam StsTokenRequest request) { + tokenRequestValidator.validate(request).orElseThrow(validationException(request.getClientId())); + return clientService.findById(request.getClientId()) + .compose(client -> clientService.authenticate(client, request.getClientSecret())) + .compose(client -> tokenService.tokenFor(client, additionalParams(request))) + .map(this::mapToken) + .orElseThrow(tokenException(request.getClientId())); + + } + + private StsClientTokenAdditionalParams additionalParams(StsTokenRequest request) { + return StsClientTokenAdditionalParams.Builder.newInstance() + .audience(request.getAudience()) + .accessToken(request.getAccessToken()) + .bearerAccessScope(request.getBearerAccessScope()) + .build(); + } + + private StsTokenResponse mapToken(TokenRepresentation tokenRepresentation) { + return new StsTokenResponse(tokenRepresentation.getToken(), tokenRepresentation.getExpiresIn()); + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/exception/StsTokenException.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/exception/StsTokenException.java new file mode 100644 index 00000000000..1807f0c9613 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/exception/StsTokenException.java @@ -0,0 +1,55 @@ +/* + * 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.connector.api.sts.exception; + +import org.eclipse.edc.connector.api.sts.model.StsTokenErrorResponse; +import org.eclipse.edc.service.spi.result.ServiceFailure; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.validator.spi.ValidationFailure; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; + +/** + * Custom exception and a mapper {@link StsTokenExceptionMapper} in order to be compliant with the OAuth2 Error Response + * The {@link ServiceFailure} is translated to a {@link StsTokenErrorResponse} in the mapper. + */ +public class StsTokenException extends EdcException { + + private final ServiceFailure serviceFailure; + private final String clientId; + + public StsTokenException(ServiceFailure serviceFailure, String clientId) { + super(serviceFailure.getFailureDetail()); + this.serviceFailure = serviceFailure; + this.clientId = clientId; + } + + public static Function tokenException(@Nullable String clientId) { + return (serviceFailure -> new StsTokenException(serviceFailure, clientId)); + } + + public static Function validationException(@Nullable String clientId) { + return (failure -> new StsTokenException(new ServiceFailure(failure.getMessages(), ServiceFailure.Reason.BAD_REQUEST), clientId)); + } + + public ServiceFailure getServiceFailure() { + return serviceFailure; + } + + public String getClientId() { + return clientId; + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/exception/StsTokenExceptionMapper.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/exception/StsTokenExceptionMapper.java new file mode 100644 index 00000000000..594d6d0ac11 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/exception/StsTokenExceptionMapper.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020 - 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - improvements + * + */ + +package org.eclipse.edc.connector.api.sts.exception; + +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import org.eclipse.edc.connector.api.sts.model.StsTokenErrorResponse; +import org.eclipse.edc.service.spi.result.ServiceFailure; + +import java.util.Map; + +import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; +import static org.eclipse.edc.service.spi.result.ServiceFailure.Reason.BAD_REQUEST; +import static org.eclipse.edc.service.spi.result.ServiceFailure.Reason.CONFLICT; +import static org.eclipse.edc.service.spi.result.ServiceFailure.Reason.NOT_FOUND; +import static org.eclipse.edc.service.spi.result.ServiceFailure.Reason.UNAUTHORIZED; + +/** + * Exception mapper that catches the `StsTokenException` exception, map it to a response code with a detailed response body + * The {@link StsTokenExceptionMapper} is translated into a {@link StsTokenErrorResponse} + */ +public class StsTokenExceptionMapper implements ExceptionMapper { + + private static final Map STATUS_MAP = Map.of( + UNAUTHORIZED, Response.Status.UNAUTHORIZED, + NOT_FOUND, Response.Status.UNAUTHORIZED, + BAD_REQUEST, Response.Status.BAD_REQUEST, + CONFLICT, Response.Status.BAD_REQUEST + ); + + private static final Map ERRORS_MAP = Map.of( + UNAUTHORIZED, "invalid_client", + NOT_FOUND, "invalid_client", + BAD_REQUEST, "invalid_request", + CONFLICT, "invalid_request" + ); + + private static final Map ERROR_DETAILS_MAP = Map.of( + UNAUTHORIZED, "Invalid client or Invalid client credentials", + NOT_FOUND, "Invalid client or Invalid client credentials"); + + + public StsTokenExceptionMapper() { + } + + @Override + public Response toResponse(StsTokenException exception) { + var failure = exception.getServiceFailure(); + var status = STATUS_MAP.getOrDefault(failure.getReason(), INTERNAL_SERVER_ERROR); + var errorDescription = ERROR_DETAILS_MAP.getOrDefault(failure.getReason(), exception.getMessage()); + var errorCode = ERRORS_MAP.getOrDefault(failure.getReason(), "invalid_request"); + var error = new StsTokenErrorResponse(errorCode, errorDescription); + return Response.status(status) + .entity(error) + .build(); + } + +} diff --git a/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponse.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponse.java similarity index 84% rename from extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponse.java rename to extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponse.java index 96fe422bda3..6caccf88f06 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponse.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponse.java @@ -22,10 +22,8 @@ * * @param error Error code. * @param errorDescription Human-readable description. - * @param errorUri URI of the error page. */ public record StsTokenErrorResponse(@JsonProperty String error, - @JsonProperty("error_description") String errorDescription, - @JsonProperty("error_uri") String errorUri) { + @JsonProperty("error_description") String errorDescription) { } 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 new file mode 100644 index 00000000000..120d71d5a83 --- /dev/null +++ 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 @@ -0,0 +1,122 @@ +/* + * 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.connector.api.sts.model; + +import jakarta.ws.rs.FormParam; + +/** + * OAuth2 Client Credentials Access Token Request + * + *
    + *
  • grantType: Type of grant. Must be client_credentials.
  • + *
  • clientId: Client ID identifier.
  • + *
  • clientSecret: Authorization secret for the client/
  • + *
  • audience: Audience according to the spec.
  • + *
  • bearerAccessScope: Space-delimited scopes to be included in the access_token claim.
  • + *
  • accessToken: VP/VC Access Token to be included as access_token claim.
  • + *
  • grantType: Type of grant. Must be client_credentials.
  • + *
+ */ + +public final class StsTokenRequest { + + @FormParam("grant_type") + private String grantType; + @FormParam("client_id") + private String clientId; + @FormParam("audience") + private String audience; + @FormParam("bearer_access_scope") + private String bearerAccessScope; + @FormParam("access_token") + private String accessToken; + @FormParam("client_secret") + private String clientSecret; + + public StsTokenRequest() { + + } + + public String getGrantType() { + return grantType; + } + + public String getClientId() { + return clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public String getAudience() { + return audience; + } + + public String getBearerAccessScope() { + return bearerAccessScope; + } + + public String getAccessToken() { + return accessToken; + } + + public static class Builder { + + private final StsTokenRequest request; + + protected Builder(StsTokenRequest request) { + this.request = request; + } + + public static Builder newInstance() { + return new Builder(new StsTokenRequest()); + } + + public Builder grantType(String grantType) { + this.request.grantType = grantType; + return this; + } + + public Builder clientId(String clientId) { + this.request.clientId = clientId; + return this; + } + + public Builder audience(String audience) { + this.request.audience = audience; + return this; + } + + public Builder bearerAccessScope(String bearerAccessScope) { + this.request.bearerAccessScope = bearerAccessScope; + return this; + } + + public Builder accessToken(String accessToken) { + this.request.accessToken = accessToken; + return this; + } + + public Builder clientSecret(String clientSecret) { + this.request.clientSecret = clientSecret; + return this; + } + + public StsTokenRequest build() { + return request; + } + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenResponse.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenResponse.java similarity index 88% rename from extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenResponse.java rename to extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenResponse.java index 9514a4d887c..ef1bd48beb1 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenResponse.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/model/StsTokenResponse.java @@ -24,10 +24,10 @@ * @param tokenType Token type. */ public record StsTokenResponse(@JsonProperty("access_token") String accessToken, - @JsonProperty("expires_in") long expiresIn, + @JsonProperty("expires_in") Long expiresIn, @JsonProperty("token_type") String tokenType) { - public StsTokenResponse(String accessToken, long expiresIn) { + public StsTokenResponse(String accessToken, Long expiresIn) { this(accessToken, expiresIn, "Bearer"); } diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidator.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidator.java new file mode 100644 index 00000000000..a1b8181bf33 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidator.java @@ -0,0 +1,53 @@ +/* + * 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.connector.api.sts.validation; + +import org.eclipse.edc.connector.api.sts.model.StsTokenRequest; +import org.eclipse.edc.validator.spi.ValidationResult; +import org.eclipse.edc.validator.spi.Validator; +import org.eclipse.edc.validator.spi.Violation; + +import java.util.ArrayList; +import java.util.Map; +import java.util.function.Function; + +public class StsTokenRequestValidator implements Validator { + + public static final String GRANT_TYPE = "grant_type"; + public static final String CLIENT_ID = "client_id"; + public static final String CLIENT_SECRET = "client_secret"; + public static final String AUDIENCE = "audience"; + private static final Map> FIELDS_NOT_NULL = Map.of( + GRANT_TYPE, StsTokenRequest::getGrantType, + CLIENT_ID, StsTokenRequest::getClientId, + CLIENT_SECRET, StsTokenRequest::getClientSecret, + AUDIENCE, StsTokenRequest::getAudience + ); + + @Override + public ValidationResult validate(StsTokenRequest request) { + var violations = new ArrayList(); + FIELDS_NOT_NULL.forEach((fieldName, supplier) -> { + if (supplier.apply(request) == null) { + violations.add(Violation.violation(fieldName + " cannot be null", fieldName)); + } + }); + if (violations.isEmpty()) { + return ValidationResult.success(); + } else { + return ValidationResult.failure(violations); + } + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 00000000000..a940e2a95e6 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,16 @@ +# +# Copyright (c) 2020 - 2022 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 +# +# + +org.eclipse.edc.connector.api.sts.StsApiConfigurationExtension +org.eclipse.edc.connector.api.sts.SecureTokenServiceApiExtension \ No newline at end of file diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtensionTest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtensionTest.java new file mode 100644 index 00000000000..c490f5f1eab --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtensionTest.java @@ -0,0 +1,58 @@ +/* + * 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.connector.api.sts; + +import org.eclipse.edc.connector.api.sts.configuration.StsApiConfiguration; +import org.eclipse.edc.connector.api.sts.controller.SecureTokenServiceApiController; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.eclipse.edc.connector.api.sts.StsApiConfigurationExtension.SETTINGS; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@ExtendWith(DependencyInjectionExtension.class) +public class SecureTokenServiceApiExtensionTest { + + private final WebService webService = mock(); + + private final WebServiceConfiguration configuration = WebServiceConfiguration.Builder.newInstance() + .contextAlias(SETTINGS.getContextAlias()) + .path(SETTINGS.getDefaultPath()) + .port(SETTINGS.getDefaultPort()) + .build(); + + @BeforeEach + void setUp(ServiceExtensionContext context) { + context.registerService(WebService.class, webService); + context.registerService(StsApiConfiguration.class, new StsApiConfiguration(configuration)); + } + + @Test + void initialize(ServiceExtensionContext context, SecureTokenServiceApiExtension extension) { + + extension.initialize(context); + + verify(webService).registerResource(eq(configuration.getContextAlias()), isA(SecureTokenServiceApiController.class)); + } + +} diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtensionTest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtensionTest.java new file mode 100644 index 00000000000..ae149fa14e2 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtensionTest.java @@ -0,0 +1,71 @@ +/* + * 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.connector.api.sts; + +import org.eclipse.edc.boot.system.DefaultServiceExtensionContext; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.Config; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration; +import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.util.List; + +import static org.eclipse.edc.connector.api.sts.StsApiConfigurationExtension.SETTINGS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(DependencyInjectionExtension.class) +public class StsApiConfigurationExtensionTest { + + private final WebServiceConfigurer configurer = mock(); + private final Monitor monitor = mock(Monitor.class); + private final WebService webService = mock(WebService.class); + private StsApiConfigurationExtension extension; + + @BeforeEach + void setUp(ServiceExtensionContext context) { + context.registerService(WebService.class, webService); + context.registerService(WebServiceConfigurer.class, configurer); + } + + @Test + void initialize_shouldConfigureAndRegisterResource(StsApiConfigurationExtension extension) { + var context = contextWithConfig(ConfigFactory.empty()); + var configuration = WebServiceConfiguration.Builder.newInstance().contextAlias("alias").path("/path").port(1234).build(); + when(configurer.configure(any(), any(), any())).thenReturn(configuration); + + extension.initialize(context); + + verify(configurer).configure(any(), any(), eq(SETTINGS)); + } + + @NotNull + private DefaultServiceExtensionContext contextWithConfig(Config config) { + var context = new DefaultServiceExtensionContext(monitor, List.of(() -> config)); + context.initialize(); + return context; + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/controller/SecureServiceTokenApiControllerTest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/controller/SecureServiceTokenApiControllerTest.java new file mode 100644 index 00000000000..2849afa50c9 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/controller/SecureServiceTokenApiControllerTest.java @@ -0,0 +1,156 @@ +/* + * 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.connector.api.sts.controller; + +import io.restassured.specification.RequestSpecification; +import org.eclipse.edc.connector.api.sts.exception.StsTokenExceptionMapper; +import org.eclipse.edc.connector.api.sts.model.StsTokenRequest; +import org.eclipse.edc.iam.identitytrust.sts.model.StsClient; +import org.eclipse.edc.iam.identitytrust.sts.service.StsClientService; +import org.eclipse.edc.iam.identitytrust.sts.service.StsClientTokenGeneratorService; +import org.eclipse.edc.junit.annotations.ApiTest; +import org.eclipse.edc.service.spi.result.ServiceResult; +import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.validator.spi.ValidationResult; +import org.eclipse.edc.validator.spi.Validator; +import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static io.restassured.http.ContentType.JSON; +import static org.eclipse.edc.iam.identitytrust.sts.store.fixtures.TestFunctions.createClient; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ApiTest +class SecureServiceTokenApiControllerTest extends RestControllerTestBase { + + private static final String GRANT_TYPE = "client_credentials"; + private final StsClientService clientService = mock(); + private final StsClientTokenGeneratorService tokenService = mock(); + private final Validator validator = mock(); + + @Test + void token() { + var id = "id"; + var clientSecret = "client_secret"; + var clientKeyAlias = "secretAlias"; + var privateKeyAlias = "secretAlias"; + var audience = "audience"; + var token = "token"; + var expiresIn = 3600; + + var client = StsClient.Builder.newInstance() + .id(id) + .clientId(id) + .name("Name") + .secretAlias(clientKeyAlias) + .privateKeyAlias(privateKeyAlias) + .build(); + + when(validator.validate(any())).thenReturn(ValidationResult.success()); + when(clientService.findById(eq(id))).thenReturn(ServiceResult.success(client)); + when(clientService.authenticate(client, clientSecret)).thenReturn(ServiceResult.success(client)); + when(tokenService.tokenFor(eq(client), any())).thenReturn(ServiceResult.success(TokenRepresentation.Builder.newInstance() + .token(token) + .expiresIn((long) expiresIn) + .build())); + + baseRequest() + .contentType("application/x-www-form-urlencoded") + .formParam("grant_type", GRANT_TYPE) + .formParam("client_id", id) + .formParam("client_secret", clientSecret) + .formParam("audience", audience) + .post("/token") + .then() + .log().all(true) + .statusCode(200) + .contentType(JSON) + .body("access_token", is(token)) + .body("expires_in", is(expiresIn)); + } + + @Test + void token_invalidClient_whenNotFound() { + var id = "id"; + var clientSecret = "client_secret"; + var audience = "audience"; + var errorCode = "invalid_client"; + + + when(validator.validate(any())).thenReturn(ValidationResult.success()); + when(clientService.findById(eq(id))).thenReturn(ServiceResult.notFound("Not found")); + + baseRequest() + .contentType("application/x-www-form-urlencoded") + .formParam("grant_type", GRANT_TYPE) + .formParam("client_id", id) + .formParam("client_secret", clientSecret) + .formParam("audience", audience) + .post("/token") + .then() + .log().all(true) + .statusCode(401) + .contentType(JSON) + .body("error", is(errorCode)); + } + + @Test + void token_invalidClient_authenticationFails() { + var id = "id"; + var clientSecret = "client_secret"; + var audience = "audience"; + var errorCode = "invalid_client"; + var client = createClient(id); + + when(validator.validate(any())).thenReturn(ValidationResult.success()); + when(clientService.findById(eq(id))).thenReturn(ServiceResult.success(client)); + when(clientService.authenticate(client, clientSecret)).thenReturn(ServiceResult.unauthorized("failure")); + + + baseRequest() + .contentType("application/x-www-form-urlencoded") + .formParam("grant_type", GRANT_TYPE) + .formParam("client_id", id) + .formParam("client_secret", clientSecret) + .formParam("audience", audience) + .post("/token") + .then() + .log().all(true) + .statusCode(401) + .contentType(JSON) + .body("error", is(errorCode)); + } + + @Override + protected Object controller() { + return new SecureTokenServiceApiController(clientService, tokenService, validator); + } + + @Override + protected Object additionalResource() { + return new StsTokenExceptionMapper(); + } + + private RequestSpecification baseRequest() { + return given() + .baseUri("http://localhost:" + port) + .when(); + } +} \ No newline at end of file diff --git a/extensions/common/iam/identity-trust/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponseTest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponseTest.java similarity index 98% rename from extensions/common/iam/identity-trust/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponseTest.java rename to extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponseTest.java index 46b21083f74..c62f9ff569c 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponseTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/model/StsTokenErrorResponseTest.java @@ -29,7 +29,7 @@ public class StsTokenErrorResponseTest { void verifyDeserialize() throws IOException { var mapper = new TypeManager().getMapper(); - var response = new StsTokenErrorResponse("error", "description", "uri"); + var response = new StsTokenErrorResponse("error", "description"); StringWriter writer = new StringWriter(); mapper.writeValue(writer, response); diff --git a/extensions/common/iam/identity-trust/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/model/StsTokenResponseTest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/model/StsTokenResponseTest.java similarity index 100% rename from extensions/common/iam/identity-trust/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/model/StsTokenResponseTest.java rename to extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/model/StsTokenResponseTest.java diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidatorTest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidatorTest.java new file mode 100644 index 00000000000..079701697a5 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidatorTest.java @@ -0,0 +1,72 @@ +/* + * 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.connector.api.sts.validation; + +import org.eclipse.edc.connector.api.sts.model.StsTokenRequest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.util.stream.Stream; + +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; + + +public class StsTokenRequestValidatorTest { + + public static final String AUDIENCE = "aud"; + public static final String CLIENT_SECRET = "secret"; + public static final String TOKEN = "token"; + public static final String SCOPES = "scopes"; + public static final String CLIENT_ID = "clientId"; + public static final String CLIENT_CREDENTIALS = "client_credentials"; + private final StsTokenRequestValidator validator = new StsTokenRequestValidator(); + + @ParameterizedTest + @ArgumentsSource(StsTokenRequestArgumentProvider.class) + void validate_failure_withMissingParameters(StsTokenRequest request) { + assertThat(validator.validate(request)).isFailed(); + } + + @Test + void validate() { + + var request = StsTokenRequest.Builder.newInstance() + .audience(AUDIENCE) + .clientSecret(CLIENT_SECRET) + .clientId("client_id") + .grantType(CLIENT_CREDENTIALS) + .build(); + assertThat(validator.validate(request)).isSucceeded(); + } + + static class StsTokenRequestArgumentProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + + return Stream.of( + Arguments.of(StsTokenRequest.Builder.newInstance().accessToken(TOKEN).bearerAccessScope(SCOPES).build()), + Arguments.of(StsTokenRequest.Builder.newInstance().audience(AUDIENCE).clientSecret(CLIENT_SECRET).grantType(CLIENT_CREDENTIALS).build()), + Arguments.of(StsTokenRequest.Builder.newInstance().clientId(CLIENT_ID).clientSecret(CLIENT_SECRET).grantType(CLIENT_CREDENTIALS).build()), + Arguments.of(StsTokenRequest.Builder.newInstance().audience(AUDIENCE).clientId(CLIENT_ID).grantType(CLIENT_CREDENTIALS).build()), + Arguments.of(StsTokenRequest.Builder.newInstance().audience(AUDIENCE).clientId(CLIENT_ID).clientSecret(CLIENT_SECRET).build()) + ); + } + } + +} diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/build.gradle.kts b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/build.gradle.kts new file mode 100644 index 00000000000..fc6781c8be8 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * 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 + * + */ + +plugins { + `java-library` + `maven-publish` +} + +dependencies { + api(project(":spi:common:identity-trust-sts-spi")) + + testImplementation(project(":core:common:junit")) +} + diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/src/main/java/org/eclipse/edc/connector/api/sts/client/configuration/StsClientConfigurationExtension.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/src/main/java/org/eclipse/edc/connector/api/sts/client/configuration/StsClientConfigurationExtension.java new file mode 100644 index 00000000000..4138bbb2292 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/src/main/java/org/eclipse/edc/connector/api/sts/client/configuration/StsClientConfigurationExtension.java @@ -0,0 +1,79 @@ +/* + * 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.connector.api.sts.client.configuration; + +import org.eclipse.edc.iam.identitytrust.sts.model.StsClient; +import org.eclipse.edc.iam.identitytrust.sts.store.StsClientStore; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.Config; + +import static java.lang.String.format; + +@Extension(StsClientConfigurationExtension.NAME) +public class StsClientConfigurationExtension implements ServiceExtension { + + public static final String CONFIG_PREFIX = "edc.iam.sts.clients"; + public static final String CLIENT_ID = "client_id"; + public static final String ID = "id"; + public static final String CLIENT_NAME = "name"; + public static final String CLIENT_SECRET_ALIAS = "secret.alias"; + public static final String CLIENT_PRIVATE_KEY_ALIAS = "private-key.alias"; + + public static final String NAME = "STS Client Configuration extension"; + + @Inject + private Monitor monitor; + + @Inject + private StsClientStore clientStore; + + + @Override + public void initialize(ServiceExtensionContext context) { + + var config = context.getConfig(CONFIG_PREFIX); + + config.partition().forEach(this::configureClient); + } + + @Override + public String name() { + return NAME; + } + + private void configureClient(Config config) { + var id = config.getString(ID); + var name = config.getString(CLIENT_NAME); + var clientId = config.getString(CLIENT_ID); + var clientSecretAlias = config.getString(CLIENT_SECRET_ALIAS); + var clientPrivateKeyAlias = config.getString(CLIENT_PRIVATE_KEY_ALIAS); + + var client = StsClient.Builder.newInstance() + .id(id) + .clientId(clientId) + .secretAlias(clientSecretAlias) + .privateKeyAlias(clientPrivateKeyAlias) + .name(name) + .build(); + + monitor.debug(format("Configuring STS client with id:%s and name:%s", client.getId(), client.getName())); + + clientStore.create(client); + } +} \ No newline at end of file diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 00000000000..8518e20ba79 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2020 - 2022 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 +# +# + +org.eclipse.edc.connector.api.sts.client.configuration.StsClientConfigurationExtension \ No newline at end of file diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/src/test/java/org/eclipse/edc/connector/api/sts/client/configuration/StsClientConfigurationExtensionTest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/src/test/java/org/eclipse/edc/connector/api/sts/client/configuration/StsClientConfigurationExtensionTest.java new file mode 100644 index 00000000000..a3b00ec854b --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-client-configuration/src/test/java/org/eclipse/edc/connector/api/sts/client/configuration/StsClientConfigurationExtensionTest.java @@ -0,0 +1,88 @@ +/* + * 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.connector.api.sts.client.configuration; + +import org.eclipse.edc.iam.identitytrust.sts.model.StsClient; +import org.eclipse.edc.iam.identitytrust.sts.store.StsClientStore; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.connector.api.sts.client.configuration.StsClientConfigurationExtension.CLIENT_ID; +import static org.eclipse.edc.connector.api.sts.client.configuration.StsClientConfigurationExtension.CLIENT_NAME; +import static org.eclipse.edc.connector.api.sts.client.configuration.StsClientConfigurationExtension.CLIENT_PRIVATE_KEY_ALIAS; +import static org.eclipse.edc.connector.api.sts.client.configuration.StsClientConfigurationExtension.CLIENT_SECRET_ALIAS; +import static org.eclipse.edc.connector.api.sts.client.configuration.StsClientConfigurationExtension.CONFIG_PREFIX; +import static org.eclipse.edc.connector.api.sts.client.configuration.StsClientConfigurationExtension.ID; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(DependencyInjectionExtension.class) +public class StsClientConfigurationExtensionTest implements ServiceExtension { + + private final StsClientStore clientStore = mock(); + + @BeforeEach + void setUp(ServiceExtensionContext context) { + context.registerService(StsClientStore.class, clientStore); + } + + @Test + void initialize_noClients(ServiceExtensionContext context, StsClientConfigurationExtension extension) { + extension.initialize(context); + verifyNoInteractions(clientStore); + } + + @Test + void initialize_withClient(ServiceExtensionContext context, StsClientConfigurationExtension extension) { + var client = StsClient.Builder.newInstance() + .id("id") + .name("name") + .clientId("client_id") + .privateKeyAlias("pAlias") + .secretAlias("sAlias") + .build(); + var clientAlias = "client"; + var config = ConfigFactory.fromMap(clientConfig(client, clientAlias)); + + when(context.getConfig(CONFIG_PREFIX)).thenReturn(config); + extension.initialize(context); + var capture = ArgumentCaptor.forClass(StsClient.class); + verify(clientStore).create(capture.capture()); + + assertThat(capture.getValue()).usingRecursiveComparison().isEqualTo(client); + } + + private Map clientConfig(StsClient client, String clientAlias) { + return Map.of( + clientAlias + "." + ID, client.getId(), + clientAlias + "." + CLIENT_NAME, client.getName(), + clientAlias + "." + CLIENT_ID, client.getClientId(), + clientAlias + "." + CLIENT_SECRET_ALIAS, client.getSecretAlias(), + clientAlias + "." + CLIENT_PRIVATE_KEY_ALIAS, client.getPrivateKeyAlias() + ); + } + +} \ No newline at end of file diff --git a/launchers/README.md b/launchers/README.md index 8e2152c1dd1..5cc6ed84998 100644 --- a/launchers/README.md +++ b/launchers/README.md @@ -4,3 +4,4 @@ - [DPF Selector](dpf-selector/) - [Generic](generic/) - [IDS Connector](ids-connector/) +- [STS server](sts-server/) diff --git a/launchers/sts-server/README.md b/launchers/sts-server/README.md new file mode 100644 index 00000000000..98be1c2ecc1 --- /dev/null +++ b/launchers/sts-server/README.md @@ -0,0 +1,70 @@ +# Simple STS standalone launcher + +This is a simple launcher demonstrating how to assemble and run an STS server. This is a minimal STS +running as a separated process with only one client configured via extension. + +## How to build the STS + +To build the STS just run this command in the root of the connector project: + +```shell +./gradlew launchers:sts-server:build +``` + +Once the build end, the STS jar should be available in the build directory `aunchers/sts-server/build/libs/sts-server.jar` + +### How to run the STS + +Before running the STS we need to generate the local keystore for cert and private key. + +```shell +mkdir launchers/sts-server/certs +openssl genrsa 2048 > launchers/sts-server/certs/key.pem +openssl req -x509 -new -key launchers/sts-server/certs/key.pem -out launchers/sts-server/certs/cert.pem +openssl pkcs12 -export -in launchers/sts-server/certs/cert.pem -inkey launchers/sts-server/certs/key.pem -out launchers/sts-server/certs/cert.pfx +``` + +When exporting in `pkcs12` use the password `123456`. + +To run the STS, just run the following command: + +```shell +java -Dedc.keystore=launchers/sts-server/certs/cert.pfx -Dedc.keystore.password=123456 \ + -Dedc.vault=launchers/sts-server/sts-vault.properties \ + -Dedc.fs.config=launchers/sts-server/config.properties \ + -jar launchers/sts-server/build/libs/sts-server.jar + ``` + +The STS will be available on `9292` port. + +### Fetching tokens + +The only client configured is `testClient` one via `config.properties` file. The STS for now supports only +OAuth2 client credentials with some custom parameters. + +To fetch a token run this curl command: + +```shell +curl --request POST \ + --url http://localhost:9292/api/v1/sts/token \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data grant_type=client_credentials \ + --data client_id=testClient \ + --data client_secret=clientSecret \ + --data audience=test10 +``` + +For attaching the `bearer_access_scope` as described in the IATP [spec](https://github.com/eclipse-tractusx/identity-trust/blob/main/specifications/M1/identity.protocol.base.md#6-using-the-oauth-2-client-credential-grant-to-obtain-access-tokens-from-an-sts) : + +```shell +curl --request POST \ + --url http://localhost:9292/api/v1/sts/token \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data grant_type=client_credentials \ + --data client_id=testClient \ + --data client_secret=clientSecret \ + --data audience=test10 \ + --data bearer_access_scope=test +``` + +It will generate an additional claim `access_token` with the scopes inside. \ No newline at end of file diff --git a/launchers/sts-server/build.gradle.kts b/launchers/sts-server/build.gradle.kts new file mode 100644 index 00000000000..def5fc17b4a --- /dev/null +++ b/launchers/sts-server/build.gradle.kts @@ -0,0 +1,47 @@ +/* + * 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 + * + */ + + +plugins { + `java-library` + id("application") + alias(libs.plugins.shadow) +} + +dependencies { + implementation(project(":core:common:boot")) + implementation(project(":core:common:connector-core")) + implementation(project(":core:common:jwt-core")) + implementation(project(":extensions:common:http")) + implementation(project(":extensions:common:iam:identity-trust:identity-trust-sts-core")) + implementation(project(":extensions:common:iam:identity-trust:identity-trust-sts:identity-trust-sts-api")) + api(project(":extensions:common:iam:identity-trust:identity-trust-sts:identity-trust-sts-client-configuration")) + implementation(project(":extensions:common:configuration:configuration-filesystem")) + implementation(project(":extensions:common:vault:vault-filesystem")) + +} + +application { + mainClass.set("org.eclipse.edc.boot.system.runtime.BaseRuntime") +} + +tasks.withType { + exclude("**/pom.properties", "**/pom.xm", "jndi.properties", "jetty-dir.css", "META-INF/maven/**") + mergeServiceFiles() + archiveFileName.set("sts-server.jar") +} + +edcBuild { + publish.set(false) +} diff --git a/launchers/sts-server/config.properties b/launchers/sts-server/config.properties new file mode 100644 index 00000000000..45e5e7eb7b6 --- /dev/null +++ b/launchers/sts-server/config.properties @@ -0,0 +1,22 @@ +# +# Copyright (c) 2020 - 2022 Microsoft Corporation +# +# 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: +# Microsoft Corporation - initial API and implementation +# +# +web.http.port=8181 +web.http.path=/api +web.http.sts.port=9292 +web.http.sts.path=/api/v1/sts +edc.iam.sts.clients.first.name=Test Client +edc.iam.sts.clients.first.id=testClient +edc.iam.sts.clients.first.client_id=testClient +edc.iam.sts.clients.first.secret.alias=secretAlias +edc.iam.sts.clients.first.private-key.alias=1 \ No newline at end of file diff --git a/launchers/sts-server/sts-vault.properties b/launchers/sts-server/sts-vault.properties new file mode 100644 index 00000000000..90219518531 --- /dev/null +++ b/launchers/sts-server/sts-vault.properties @@ -0,0 +1,14 @@ +# +# 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 +# +# +secretAlias=clientSecret \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 0ee952d76e2..9d1f4f677ef 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -117,8 +117,10 @@ include(":extensions:common:iam:identity-trust:identity-trust-transform") include(":extensions:common:iam:identity-trust:identity-trust-service") include(":extensions:common:iam:identity-trust:identity-trust-core") include(":extensions:common:iam:identity-trust:identity-trust-sts-embedded") -include(":extensions:common:iam:identity-trust:identity-trust-sts-api") include(":extensions:common:iam:identity-trust:identity-trust-sts-core") +include(":extensions:common:iam:identity-trust:identity-trust-sts") +include(":extensions:common:iam:identity-trust:identity-trust-sts:identity-trust-sts-api") +include(":extensions:common:iam:identity-trust:identity-trust-sts:identity-trust-sts-client-configuration") include(":extensions:common:json-ld") include(":extensions:common:metrics:micrometer-core") @@ -183,6 +185,7 @@ include(":extensions:policy-monitor:store:sql:policy-monitor-store-sql") // modules for launchers, i.e. runnable compositions of the app ------------------------------------ include(":launchers:data-plane-server") include(":launchers:dpf-selector") +include(":launchers:sts-server") // extension points for a connector ---------------------------------------------------------------- include(":spi:common:aggregate-service-spi") @@ -228,8 +231,10 @@ include(":system-tests:e2e-transfer-test:data-plane-postgresql") include(":system-tests:e2e-transfer-test:runner") include(":system-tests:management-api:management-api-test-runner") include(":system-tests:management-api:management-api-test-runtime") +include(":system-tests:sts-api:sts-api-test-runner") +include(":system-tests:sts-api:sts-api-test-runtime") include(":system-tests:telemetry:telemetry-test-runner") include(":system-tests:telemetry:telemetry-test-runtime") -include(":version-catalog") +include(":version-catalog") \ No newline at end of file diff --git a/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/TokenRepresentation.java b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/TokenRepresentation.java index db6c153e539..58fd3a61f57 100644 --- a/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/TokenRepresentation.java +++ b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/iam/TokenRepresentation.java @@ -24,6 +24,7 @@ */ public class TokenRepresentation { private String token; + private Long expiresIn; private Map additional; private TokenRepresentation() { @@ -37,6 +38,13 @@ public String getToken() { return token; } + /** + * Returns the lifetime of the token in seconds + */ + public Long getExpiresIn() { + return expiresIn; + } + public Map getAdditional() { return additional; } @@ -57,6 +65,11 @@ public Builder token(String token) { return this; } + public Builder expiresIn(Long expiresIn) { + result.expiresIn = expiresIn; + return this; + } + public Builder additional(Map additional) { result.additional = additional; 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 31295497c2f..3b8b751ed42 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 @@ -20,8 +20,6 @@ */ public final class JwtRegisteredClaimNames { - private JwtRegisteredClaimNames() { } - /** * "iss" (Issuer) Claim * @@ -29,7 +27,6 @@ private JwtRegisteredClaimNames() { } */ public static final String ISSUER = "iss"; - /** * "sub" (Subject) Claim * @@ -37,7 +34,6 @@ private JwtRegisteredClaimNames() { } */ public static final String SUBJECT = "sub"; - /** * "aud" (Audience) Claim * @@ -45,7 +41,6 @@ private JwtRegisteredClaimNames() { } */ public static final String AUDIENCE = "aud"; - /** * "exp" (Expiration Time) Claim * @@ -53,7 +48,6 @@ private JwtRegisteredClaimNames() { } */ public static final String EXPIRATION_TIME = "exp"; - /** * "nbf" (Not Before) Claim * @@ -61,7 +55,6 @@ private JwtRegisteredClaimNames() { } */ public static final String NOT_BEFORE = "nbf"; - /** * "iat" (Issued At) Claim * @@ -69,11 +62,21 @@ private JwtRegisteredClaimNames() { } */ public static final String ISSUED_AT = "iat"; - /** * "jti" (JWT ID) Claim * * @see RFC 7519 "jti" (JWT ID) Claim */ public static final String JWT_ID = "jti"; + + /** + * "client_id" (Client Identifier) Claim + * + * @see RFC 8693 "client_id" (Client Identifier) Claim + */ + + public static final String CLIENT_ID = "client_id"; + + private JwtRegisteredClaimNames() { + } } diff --git a/system-tests/sts-api/sts-api-test-runner/build.gradle.kts b/system-tests/sts-api/sts-api-test-runner/build.gradle.kts new file mode 100644 index 00000000000..c1f9372e90b --- /dev/null +++ b/system-tests/sts-api/sts-api-test-runner/build.gradle.kts @@ -0,0 +1,37 @@ +/* + * 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 + * + */ + +plugins { + java +} + +dependencies { + testImplementation(project(":core:common:junit")) + testImplementation(project(":spi:common:identity-trust-sts-spi")) + + testImplementation(libs.restAssured) + testImplementation(libs.assertj) + testImplementation(libs.awaitility) + testImplementation(libs.junit.jupiter.api) + testImplementation(libs.jakartaJson) + + testCompileOnly(project(":system-tests:sts-api:sts-api-test-runtime")) + testImplementation(testFixtures(project(":spi:common:identity-trust-sts-spi"))) + testImplementation(libs.nimbus.jwt) + +} + +edcBuild { + publish.set(false) +} diff --git a/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/stsapi/StsApiEndToEndTest.java b/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/stsapi/StsApiEndToEndTest.java new file mode 100644 index 00000000000..8f3d53b404f --- /dev/null +++ b/system-tests/sts-api/sts-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/stsapi/StsApiEndToEndTest.java @@ -0,0 +1,151 @@ +/* + * 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.stsapi; + +import com.nimbusds.jwt.SignedJWT; +import io.restassured.specification.RequestSpecification; +import org.eclipse.edc.iam.identitytrust.sts.store.StsClientStore; +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; + +import java.io.IOException; +import java.text.ParseException; +import java.util.HashMap; +import java.util.List; +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.junit.testfixtures.TestUtils.getFreePort; +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; +import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.JWT_ID; +import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SUBJECT; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +public class StsApiEndToEndTest { + + public static final int PORT = getFreePort(); + public static final String BASE_STS = "http://localhost:" + PORT + "/sts"; + private static final String GRANT_TYPE = "client_credentials"; + + @RegisterExtension + static EdcRuntimeExtension sts = new EdcRuntimeExtension( + ":system-tests:sts-api:sts-api-test-runtime", + "sts", + new HashMap<>() { + { + put("web.http.path", "/"); + put("web.http.port", String.valueOf(getFreePort())); + put("web.http.sts.path", "/sts"); + put("web.http.sts.port", String.valueOf(PORT)); + } + } + ); + + @Test + void requestToken() throws IOException, ParseException { + var store = getClientStore(); + var vault = getVault(); + var clientId = "client_id"; + var clientSecret = "client_secret"; + var clientSecretAlias = "client_secret_alias"; + var audience = "audience"; + var expiresIn = 300; + var client = createClient(clientId, clientSecretAlias); + + vault.storeSecret(clientSecretAlias, clientSecret); + vault.storeSecret(client.getPrivateKeyAlias(), loadResourceFile("ec-privatekey.pem")); + store.create(client); + + var token = baseRequest() + .contentType("application/x-www-form-urlencoded") + .formParam("grant_type", GRANT_TYPE) + .formParam("client_id", clientId) + .formParam("client_secret", clientSecret) + .formParam("audience", audience) + .post("/token") + .then() + .statusCode(200) + .contentType(JSON) + .body("access_token", notNullValue()) + .body("expires_in", is(expiresIn)) + .extract() + .body() + .jsonPath().getString("access_token"); + + + var jwt = SignedJWT.parse(token); + + assertThat(jwt.getJWTClaimsSet().getClaims()) + .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); + } + + @Test + void requestToken_shouldReturnError_whenClientNotFound() { + + var clientId = "client_id"; + var clientSecret = "client_secret"; + var audience = "audience"; + + baseRequest() + .contentType("application/x-www-form-urlencoded") + .formParam("grant_type", GRANT_TYPE) + .formParam("client_id", clientId) + .formParam("client_secret", clientSecret) + .formParam("audience", audience) + .post("/token") + .then() + .log().all(true) + .statusCode(401) + .contentType(JSON); + } + + protected RequestSpecification baseRequest() { + return given() + .port(PORT) + .baseUri(BASE_STS) + .when(); + } + + private StsClientStore getClientStore() { + return sts.getContext().getService(StsClientStore.class); + } + + private Vault getVault() { + return sts.getContext().getService(Vault.class); + } + + /** + * Load content from a resource file. + */ + private String loadResourceFile(String file) throws IOException { + try (var resourceAsStream = StsApiEndToEndTest.class.getClassLoader().getResourceAsStream(file)) { + return new String(Objects.requireNonNull(resourceAsStream).readAllBytes()); + } + } +} diff --git a/system-tests/sts-api/sts-api-test-runner/src/test/resources/ec-privatekey.pem b/system-tests/sts-api/sts-api-test-runner/src/test/resources/ec-privatekey.pem new file mode 100644 index 00000000000..cf5f3028922 --- /dev/null +++ b/system-tests/sts-api/sts-api-test-runner/src/test/resources/ec-privatekey.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgcXwv5l0erIFLX6WF +BuSh+OcMrrdjc+3i8O+6py4TGy6hRANCAAQC0ZvCF7DW4c+cXRVf0uv1zeeSNkEX +oA7s2IGg2+UjF295iNRUu/8GaqM/rdbuylvSsq940GKWTNL3RbfwuAmW +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/system-tests/sts-api/sts-api-test-runtime/build.gradle.kts b/system-tests/sts-api/sts-api-test-runtime/build.gradle.kts new file mode 100644 index 00000000000..4c07129cf7a --- /dev/null +++ b/system-tests/sts-api/sts-api-test-runtime/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * 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 + * + */ + +plugins { + `java-library` +} + +dependencies { + implementation(project(":core:common:jwt-core")) + implementation(project(":extensions:common:http")) + implementation(project(":extensions:common:iam:identity-trust:identity-trust-sts-core")) + implementation(project(":extensions:common:iam:identity-trust:identity-trust-sts:identity-trust-sts-api")) +} + +edcBuild { + publish.set(false) +}