From 7e05e6148a09b18662538895a0d2960456495cab Mon Sep 17 00:00:00 2001 From: Enrico Risa Date: Fri, 20 Oct 2023 10:17:31 +0200 Subject: [PATCH] feat: Standalone STS --- .../identity-trust-sts-api/build.gradle.kts | 3 + .../api/sts/SecureTokenServiceApi.java | 1 + .../sts/SecureTokenServiceApiExtension.java | 59 +++++++ .../api/sts/StsApiConfigurationExtension.java | 59 +++++++ .../configuration/StsApiConfiguration.java | 26 +++ .../SecureTokenServiceApiController.java | 41 ++++- .../api/sts/model/StsTokenRequest.java | 113 +++++++++++-- .../api/sts/model/StsTokenResponse.java | 4 +- .../validation/StsTokenRequestValidator.java | 49 ++++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 16 ++ .../SecureTokenServiceApiExtensionTest.java | 59 +++++++ .../sts/StsApiConfigurationExtensionTest.java | 71 +++++++++ .../SecureServiceTokenApiControllerTest.java | 97 +++++++++++ .../StsTokenRequestValidatorTest.java | 69 ++++++++ .../StsClientTokenGeneratorServiceImpl.java | 13 +- settings.gradle.kts | 2 + .../edc/spi/iam/TokenRepresentation.java | 14 ++ .../edc/jwt/spi/JwtRegisteredClaimNames.java | 19 ++- .../sts-api-test-runner/build.gradle.kts | 37 +++++ .../test/e2e/stsapi/StsApiEndToEndTest.java | 150 ++++++++++++++++++ .../src/test/resources/ec-privatekey.pem | 5 + .../sts-api-test-runtime/build.gradle.kts | 28 ++++ 22 files changed, 912 insertions(+), 23 deletions(-) create mode 100644 extensions/common/iam/identity-trust/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-api/src/main/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtension.java create mode 100644 extensions/common/iam/identity-trust/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-api/src/main/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidator.java create mode 100644 extensions/common/iam/identity-trust/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-api/src/test/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtensionTest.java create mode 100644 extensions/common/iam/identity-trust/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-api/src/test/java/org/eclipse/edc/connector/api/sts/controller/SecureServiceTokenApiControllerTest.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidatorTest.java 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/extensions/common/iam/identity-trust/identity-trust-sts-api/build.gradle.kts b/extensions/common/iam/identity-trust/identity-trust-sts-api/build.gradle.kts index a49ffed4fbb..5bff4066802 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts-api/build.gradle.kts +++ b/extensions/common/iam/identity-trust/identity-trust-sts-api/build.gradle.kts @@ -20,6 +20,8 @@ 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 +31,6 @@ dependencies { testImplementation(project(":core:common:junit")) testImplementation(testFixtures(project(":extensions:common:http:jersey-core"))) + 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-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-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-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtension.java b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtension.java new file mode 100644 index 00000000000..e739688de8a --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtension.java @@ -0,0 +1,59 @@ +/* + * 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.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())); + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtension.java b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtension.java new file mode 100644 index 00000000000..cc51cb293f0 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtension.java @@ -0,0 +1,59 @@ +/* + * 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-api/src/main/java/org/eclipse/edc/connector/api/sts/configuration/StsApiConfiguration.java b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/configuration/StsApiConfiguration.java new file mode 100644 index 00000000000..a0d6d48c339 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/configuration/StsApiConfiguration.java @@ -0,0 +1,26 @@ +/* + * 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; + +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-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 index 5d2b5d14082..dc7de84290b 100644 --- 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 @@ -23,16 +23,55 @@ 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.StsClient; +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 org.eclipse.edc.web.spi.exception.ValidationFailureException; + +import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; @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) { - return null; + tokenRequestValidator.validate(request).orElseThrow(ValidationFailureException::new); + return clientService.findById(request.getClientId()) + .compose(client -> clientService.authenticate(client, request.getClientSecret())) + .compose(client -> tokenService.tokenFor(client, additionalParams(request))) + .map(this::mapToken) + .orElseThrow(exceptionMapper(StsClient.class, 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-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 index fcda01293dd..7bec7994178 100644 --- 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 @@ -19,15 +19,108 @@ /** * 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. + *
    + *
  • 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 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) { + +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-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-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-api/src/main/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidator.java b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidator.java new file mode 100644 index 00000000000..427b376ad48 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/main/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidator.java @@ -0,0 +1,49 @@ +/* + * 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 { + + final Map> fieldsNotNull = 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(); + fieldsNotNull.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-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/common/iam/identity-trust/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-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-api/src/test/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtensionTest.java b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtensionTest.java new file mode 100644 index 00000000000..70895e1917d --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/SecureTokenServiceApiExtensionTest.java @@ -0,0 +1,59 @@ +/* + * 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-api/src/test/java/org/eclipse/edc/connector/api/sts/StsApiConfigurationExtensionTest.java b/extensions/common/iam/identity-trust/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-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-api/src/test/java/org/eclipse/edc/connector/api/sts/controller/SecureServiceTokenApiControllerTest.java b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/controller/SecureServiceTokenApiControllerTest.java new file mode 100644 index 00000000000..d06e902d308 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/controller/SecureServiceTokenApiControllerTest.java @@ -0,0 +1,97 @@ +/* + * 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.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.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)); + } + + @Override + protected Object controller() { + return new SecureTokenServiceApiController(clientService, tokenService, validator); + } + + 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/validation/StsTokenRequestValidatorTest.java b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidatorTest.java new file mode 100644 index 00000000000..8b6b381f0d8 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-sts-api/src/test/java/org/eclipse/edc/connector/api/sts/validation/StsTokenRequestValidatorTest.java @@ -0,0 +1,69 @@ +/* + * 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"; + 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("clientId").clientSecret(CLIENT_SECRET).grantType("client_credentials").build()), + Arguments.of(StsTokenRequest.Builder.newInstance().audience(AUDIENCE).clientId("clientId").grantType("client_credentials").build()), + Arguments.of(StsTokenRequest.Builder.newInstance().audience(AUDIENCE).clientId("clientId").clientSecret(CLIENT_SECRET).build()) + ); + } + } + +} 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/settings.gradle.kts b/settings.gradle.kts index f9bc513ca36..f9d4916efa7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -226,6 +226,8 @@ 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") 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..9292d03e2d0 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,8 @@ */ public class TokenRepresentation { private String token; + + private Long expiresIn; private Map additional; private TokenRepresentation() { @@ -37,6 +39,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 +66,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..b75e8bdf23a --- /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,150 @@ +/* + * 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 int PROTOCOL_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() + .statusCode(404) + .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..88f8702cce3 --- /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-api")) +} + +edcBuild { + publish.set(false) +}