gmunozfe commented on code in PR #3928:
URL:
https://github.com/apache/incubator-kie-kogito-runtimes/pull/3928#discussion_r2167431159
##########
quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/KeycloakServiceMock.java:
##########
@@ -43,16 +44,25 @@ public class KeycloakServiceMock implements
QuarkusTestResourceLifecycleManager
public static final String KEY_CLOAK_SERVICE_URL =
"keycloak.mock.service.url";
public static final String KEY_CLOAK_SERVICE_TOKEN_PATH =
"keycloak.mock.service.token-path";
+
+ public static final String KEY_CLOAK_EXCHANGE_SERVICE_TOKEN_PATH =
"keycloak.mock.exchange-service.token-path";
public static final String REALM = "kogito-tests";
+ public static final String EXCHANGE_REALM = "kogito-exchange-tests";
public static final String KEY_CLOAK_SERVICE_TOKEN_PATH_VALUE = "/realms/"
+ REALM + "/protocol/openid-connect/token";
+ public static final String KEY_CLOAK_EXCHANGE_SERVICE_TOKEN_PATH_VALUE =
"/realms/" + EXCHANGE_REALM + "/protocol/openid-connect/token";
Review Comment:
it seems like a typo to split KEY CLOAK
##########
quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow/src/main/java/org/kie/kogito/serverless/workflow/openapi/OpenApiCustomCredentialProvider.java:
##########
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.serverless.workflow.openapi;
+
+import java.util.Collections;
+import java.util.Optional;
+
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider;
+import io.quarkiverse.openapi.generator.providers.CredentialsContext;
+import io.quarkus.arc.Arc;
+import io.quarkus.oidc.client.OidcClient;
+import io.quarkus.oidc.client.OidcClientConfig;
+import io.quarkus.oidc.client.OidcClientException;
+import io.quarkus.oidc.client.OidcClients;
+import io.quarkus.oidc.client.Tokens;
+import io.quarkus.runtime.configuration.ConfigurationException;
+
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.enterprise.inject.Alternative;
+import jakarta.enterprise.inject.Specializes;
+import jakarta.ws.rs.core.HttpHeaders;
+
+import static
io.quarkiverse.openapi.generator.providers.AbstractAuthProvider.getHeaderName;
+
+@RequestScoped
+@Alternative
+@Specializes
+@Priority(200)
+public class OpenApiCustomCredentialProvider extends ConfigCredentialsProvider
{
+ private static final String CANONICAL_EXCHANGE_TOKEN_PROPERTY_NAME =
"sonataflow.security.%s.exchange-token";
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(OpenApiCustomCredentialProvider.class);
+
+ @Override
+ public Optional<String> getOauth2BearerToken(CredentialsContext input) {
+ LOGGER.debug("Calling
OpenApiCustomCredentialProvider.getOauth2BearerToken for {}",
input.getAuthName());
+ String authorizationHeaderName =
getHeaderName(input.getOpenApiSpecId(), input.getAuthName()) != null ?
getHeaderName(input.getOpenApiSpecId(), input.getAuthName())
Review Comment:
If you want, it could be simplified as:
```suggestion
String authorizationHeaderName =
Optional.ofNullable(getHeaderName(input.getOpenApiSpecId(),
input.getAuthName())).orElse(HttpHeaders.AUTHORIZATION);
```
##########
quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow/src/main/java/org/kie/kogito/serverless/workflow/openapi/OpenApiCustomCredentialProvider.java:
##########
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.serverless.workflow.openapi;
+
+import java.util.Collections;
+import java.util.Optional;
+
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider;
+import io.quarkiverse.openapi.generator.providers.CredentialsContext;
+import io.quarkus.arc.Arc;
+import io.quarkus.oidc.client.OidcClient;
+import io.quarkus.oidc.client.OidcClientConfig;
+import io.quarkus.oidc.client.OidcClientException;
+import io.quarkus.oidc.client.OidcClients;
+import io.quarkus.oidc.client.Tokens;
+import io.quarkus.runtime.configuration.ConfigurationException;
+
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.enterprise.inject.Alternative;
+import jakarta.enterprise.inject.Specializes;
+import jakarta.ws.rs.core.HttpHeaders;
+
+import static
io.quarkiverse.openapi.generator.providers.AbstractAuthProvider.getHeaderName;
+
+@RequestScoped
+@Alternative
+@Specializes
+@Priority(200)
+public class OpenApiCustomCredentialProvider extends ConfigCredentialsProvider
{
+ private static final String CANONICAL_EXCHANGE_TOKEN_PROPERTY_NAME =
"sonataflow.security.%s.exchange-token";
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(OpenApiCustomCredentialProvider.class);
+
+ @Override
+ public Optional<String> getOauth2BearerToken(CredentialsContext input) {
+ LOGGER.debug("Calling
OpenApiCustomCredentialProvider.getOauth2BearerToken for {}",
input.getAuthName());
+ String authorizationHeaderName =
getHeaderName(input.getOpenApiSpecId(), input.getAuthName()) != null ?
getHeaderName(input.getOpenApiSpecId(), input.getAuthName())
+ : HttpHeaders.AUTHORIZATION;
+ Optional<Boolean> exchangeToken =
ConfigProvider.getConfig().getOptionalValue(getCanonicalExchangeTokenConfigPropertyName(input.getAuthName()),
Boolean.class);
+ String accessToken = null;
+
+ if (exchangeToken.isPresent() && exchangeToken.get()) {
+ accessToken =
input.getRequestContext().getHeaderString(authorizationHeaderName);
+
+ if (accessToken == null || accessToken.isBlank()) {
+ throw new ConfigurationException("An access token is required
in the header %s (default is %s) but none was
provided".formatted(authorizationHeaderName, HttpHeaders.AUTHORIZATION));
+ }
+
+ LOGGER.info("Oauth2 token exchange enabled for {}, will generate a
tokens...", input.getAuthName());
+ OidcClients clients =
Arc.container().instance(OidcClients.class).get();
+ OidcClient exchangeTokenClient =
clients.getClient(input.getAuthName());
Review Comment:
`clients` could be potentially null? Then we need to check this to avoid a
NPE
##########
quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/TokenExchangeExternalServicesMock.java:
##########
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.quarkus.workflows;
+
+import java.util.Collections;
+import java.util.Map;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+
+import jakarta.ws.rs.core.HttpHeaders;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static
com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static jakarta.ws.rs.core.HttpHeaders.CONTENT_TYPE;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static
org.kie.kogito.quarkus.workflows.KeycloakServiceMock.KEYCLOAK_ACCESS_TOKEN;
+import static
org.kie.kogito.quarkus.workflows.KeycloakServiceMock.KEYCLOAK_EXCHANGED_ACCESS_TOKEN;
+
+public class TokenExchangeExternalServicesMock implements
QuarkusTestResourceLifecycleManager {
+
+ public static final String BASE_AND_PROPAGATED_AUTHORIZATION_TOKEN =
"BASE_AND_PROPAGATED_AUTHORIZATION_TOKEN";
+
+ private static final String BEARER = "Bearer ";
+
+ public static final String TOKEN_PROPAGATION_EXTERNAL_SERVICE_MOCK_URL =
"exchange-external-service-mock.url";
+
+ private WireMockServer wireMockServer;
+
+ @Override
+ public Map<String, String> start() {
+ wireMockServer = new WireMockServer(options().dynamicPort());
+ wireMockServer.start();
+ configureFor(wireMockServer.port());
+
+ // stub the /token-exchange-external-service/withExchange invocation
with the expected token
+
stubForExternalService("/token-exchange-external-service/withExchange",
KEYCLOAK_EXCHANGED_ACCESS_TOKEN);
+
+ // stub the
/token-exchange-external-service/withExchangeAndPropagation invocation with the
expected token
+
stubForExternalService("/token-exchange-external-service/withExchangeAndPropagation",
BASE_AND_PROPAGATED_AUTHORIZATION_TOKEN);
+
+ // stub token-exchange-external-service/withoutExchange invocation
with the expected token, no propagation nor
+ // exchange are produced in this case but the service must receive
the token provided by Keycloak since it has
+ // oauth2 security configured.
+
stubForExternalService("/token-exchange-external-service/withoutExchange",
KEYCLOAK_ACCESS_TOKEN);
+
+ return
Collections.singletonMap(TOKEN_PROPAGATION_EXTERNAL_SERVICE_MOCK_URL,
wireMockServer.baseUrl());
+ }
+
+ private static void stubForExternalService(String
tokenPropagationExternalServiceUrl, String authorizationToken) {
+ stubFor(post(tokenPropagationExternalServiceUrl)
+ .withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON))
+ .withHeader(HttpHeaders.AUTHORIZATION, equalTo(BEARER +
authorizationToken))
+ .willReturn(aResponse()
+ .withHeader(CONTENT_TYPE, APPLICATION_JSON)
+ .withBody("{}")));
+ }
+
+ @Override
+ public void stop() {
+ if (wireMockServer != null) {
Review Comment:
You could also invoke here `wireMockServer.resetAll() f`or parallel tests or
reusing the mock.
##########
quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/TokenExchangeIT.java:
##########
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.quarkus.workflows;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.restassured.path.json.JsonPath;
+
+import jakarta.ws.rs.core.HttpHeaders;
+
+import static
org.kie.kogito.quarkus.workflows.ExternalServiceMock.SUCCESSFUL_QUERY;
+import static
org.kie.kogito.quarkus.workflows.TokenExchangeExternalServicesMock.BASE_AND_PROPAGATED_AUTHORIZATION_TOKEN;
+import static
org.kie.kogito.test.utils.ProcessInstancesRESTTestUtils.newProcessInstance;
+
+@QuarkusTestResource(TokenExchangeExternalServicesMock.class)
+@QuarkusTestResource(KeycloakServiceMock.class)
+@QuarkusIntegrationTest
+class TokenExchangeIT {
+
+ @Test
+ void tokenExchange() {
+ // start a new process instance by sending the post query and collect
the process instance id.
+ String processInput = buildProcessInput(SUCCESSFUL_QUERY);
+ Map<String, String> headers = new HashMap<>();
+ // prepare the headers to pass to the token_propagation SW.
+ // service token-propagation-external-service1 and
token-propagation-external-service2 will receive the AUTHORIZATION_TOKEN
+ headers.put(HttpHeaders.AUTHORIZATION,
BASE_AND_PROPAGATED_AUTHORIZATION_TOKEN);
+
+ JsonPath jsonPath = newProcessInstance("/token_exchange",
processInput, headers);
+ Assertions.assertThat(jsonPath.getString("id")).isNotBlank();
+ }
+
Review Comment:
You could also consider other rainy tests for scenarios like
- Token missing in headers
- Token exchange fails
- etc.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]