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]

Reply via email to