pefernan commented on code in PR #4213: URL: https://github.com/apache/incubator-kie-kogito-runtimes/pull/4213#discussion_r3152952207
########## kogito-workitems/kogito-rest-workitem/src/main/java/org/kogito/workitem/rest/decorators/TokenPropagationDecorator.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.kogito.workitem.rest.decorators; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.auth.AuthTokenProvider; +import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.internal.process.workitem.KogitoWorkItem; +import org.kie.kogito.process.ProcessConfig; +import org.kie.kogito.process.workitems.impl.ConfigResolverHolder; +import org.kogito.workitem.rest.auth.AuthDecorator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.mutiny.ext.web.client.HttpRequest; + +enum AccessTokenAcquisitionStrategy { + NONE("none"), + CONFIGURED("configured"), + PROPAGATED("propagated"); + + private String name; + + AccessTokenAcquisitionStrategy(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static AccessTokenAcquisitionStrategy fromName(String strategyName) { + return Arrays.stream(AccessTokenAcquisitionStrategy.values()) + .filter(strategy -> strategy.getName().equals(strategyName)) + .findFirst() + .orElse(NONE); + } +} + +public class TokenPropagationDecorator implements AuthDecorator { + + private static final Logger logger = LoggerFactory.getLogger(TokenPropagationDecorator.class); + + public static final String ACCESS_TOKEN_ACQUISITION_STRATEGY = "AccessTokenAcquisitionStrategy"; + + private static final String PROPAGATE_TOKEN_PARAM = "propagateToken"; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + @Override + public void decorate(KogitoWorkItem item, Map<String, Object> parameters, HttpRequest<?> request) { + Optional<String> bearerToken = getBearerToken(item, parameters); + + bearerToken.ifPresentOrElse( + token -> { + logger.debug("Rest workItem `{}`: Bearer token available, request will be sent with authentication", item.getNodeInstance().getId()); + request.bearerTokenAuthentication(token); + }, + () -> logger.debug("Rest workItem `{}`: No Bearer Token available, request will be sent without authentication", item.getNodeInstance().getId())); + } + + Optional<String> getBearerToken(KogitoWorkItem item, Map<String, Object> parameters) { + Object strategyParam = parameters.get(ACCESS_TOKEN_ACQUISITION_STRATEGY); + + if (!(strategyParam instanceof String)) { + logger.debug("No token acquisition strategy specified, skipping authentication"); + return Optional.empty(); + } + + String strategyName = (String) strategyParam; + AccessTokenAcquisitionStrategy strategy = AccessTokenAcquisitionStrategy.fromName(strategyName); + + return switch (strategy) { + case PROPAGATED -> getPropagatedTokenFromHeaders(item); + case CONFIGURED -> getConfiguredToken(item, parameters); + default -> { + logger.debug("Strategy {} is NONE or unknown, skipping", strategyName); + yield Optional.empty(); + } + }; + } + + private Optional<String> getPropagatedTokenFromHeaders(KogitoWorkItem item) { + + KogitoProcessInstance processInstance = item.getProcessInstance(); + if (processInstance == null) { + logger.debug("No process instance available, cannot propagate token"); + return Optional.empty(); + } + + try { + // Cast to ProcessInstanceImpl to access getKnowledgeRuntime() + if (!(processInstance instanceof org.jbpm.process.instance.ProcessInstance)) { + logger.debug("Process instance is not a jBPM ProcessInstance, cannot access runtime"); + return Optional.empty(); + } + + org.jbpm.process.instance.ProcessInstance jbpmProcessInstance = + (org.jbpm.process.instance.ProcessInstance) processInstance; + + // Get the AuthTokenProvider from the ProcessConfig via KogitoProcessRuntime + org.kie.kogito.internal.process.runtime.KogitoProcessRuntime processRuntime = + ((org.jbpm.process.instance.InternalProcessRuntime) jbpmProcessInstance.getKnowledgeRuntime().getProcessRuntime()) + .getKogitoProcessRuntime(); + + if (processRuntime == null) { + logger.debug("KogitoProcessRuntime not available, cannot access AuthTokenProvider"); + return Optional.empty(); + } + + ProcessConfig processConfig = processRuntime.getApplication().config().get(ProcessConfig.class); + if (processConfig == null) { + logger.debug("ProcessConfig not available, cannot access AuthTokenProvider"); + return Optional.empty(); + } + + AuthTokenProvider authTokenProvider = processConfig.authTokenProvider(); + if (authTokenProvider == null) { + logger.debug("AuthTokenProvider not available"); + return Optional.empty(); + } + + Optional<String> token = authTokenProvider.getAuthToken(); + if (token.isPresent()) { + logger.debug("Using propagated token from AuthTokenProvider"); + return token; + } else { + logger.debug("No token available from AuthTokenProvider"); + return Optional.empty(); + } + } catch (Exception e) { + logger.warn("Failed to retrieve token from AuthTokenProvider", e); + return Optional.empty(); + } + } + + private Optional<String> getConfiguredToken(KogitoWorkItem item, Map<String, Object> parameters) { + Optional<String> paramToken = Optional.ofNullable((String) parameters.get(PROPAGATE_TOKEN_PARAM)); Review Comment: Token won't be received as a param, it will be either read from config or propagated ########## kogito-workitems/kogito-rest-workitem/src/main/java/org/kogito/workitem/rest/decorators/TokenPropagationDecorator.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.kogito.workitem.rest.decorators; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.auth.AuthTokenProvider; +import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.internal.process.workitem.KogitoWorkItem; +import org.kie.kogito.process.ProcessConfig; +import org.kie.kogito.process.workitems.impl.ConfigResolverHolder; +import org.kogito.workitem.rest.auth.AuthDecorator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.mutiny.ext.web.client.HttpRequest; + +enum AccessTokenAcquisitionStrategy { + NONE("none"), + CONFIGURED("configured"), + PROPAGATED("propagated"); + + private String name; + + AccessTokenAcquisitionStrategy(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static AccessTokenAcquisitionStrategy fromName(String strategyName) { + return Arrays.stream(AccessTokenAcquisitionStrategy.values()) + .filter(strategy -> strategy.getName().equals(strategyName)) + .findFirst() + .orElse(NONE); + } +} + +public class TokenPropagationDecorator implements AuthDecorator { + + private static final Logger logger = LoggerFactory.getLogger(TokenPropagationDecorator.class); + + public static final String ACCESS_TOKEN_ACQUISITION_STRATEGY = "AccessTokenAcquisitionStrategy"; + + private static final String PROPAGATE_TOKEN_PARAM = "propagateToken"; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + @Override + public void decorate(KogitoWorkItem item, Map<String, Object> parameters, HttpRequest<?> request) { + Optional<String> bearerToken = getBearerToken(item, parameters); + + bearerToken.ifPresentOrElse( + token -> { + logger.debug("Rest workItem `{}`: Bearer token available, request will be sent with authentication", item.getNodeInstance().getId()); + request.bearerTokenAuthentication(token); + }, + () -> logger.debug("Rest workItem `{}`: No Bearer Token available, request will be sent without authentication", item.getNodeInstance().getId())); + } + + Optional<String> getBearerToken(KogitoWorkItem item, Map<String, Object> parameters) { + Object strategyParam = parameters.get(ACCESS_TOKEN_ACQUISITION_STRATEGY); + + if (!(strategyParam instanceof String)) { + logger.debug("No token acquisition strategy specified, skipping authentication"); + return Optional.empty(); + } + + String strategyName = (String) strategyParam; + AccessTokenAcquisitionStrategy strategy = AccessTokenAcquisitionStrategy.fromName(strategyName); + + return switch (strategy) { + case PROPAGATED -> getPropagatedTokenFromHeaders(item); + case CONFIGURED -> getConfiguredToken(item, parameters); + default -> { + logger.debug("Strategy {} is NONE or unknown, skipping", strategyName); + yield Optional.empty(); + } + }; + } + + private Optional<String> getPropagatedTokenFromHeaders(KogitoWorkItem item) { + + KogitoProcessInstance processInstance = item.getProcessInstance(); + if (processInstance == null) { + logger.debug("No process instance available, cannot propagate token"); + return Optional.empty(); + } + + try { + // Cast to ProcessInstanceImpl to access getKnowledgeRuntime() + if (!(processInstance instanceof org.jbpm.process.instance.ProcessInstance)) { + logger.debug("Process instance is not a jBPM ProcessInstance, cannot access runtime"); + return Optional.empty(); + } + + org.jbpm.process.instance.ProcessInstance jbpmProcessInstance = + (org.jbpm.process.instance.ProcessInstance) processInstance; + + // Get the AuthTokenProvider from the ProcessConfig via KogitoProcessRuntime + org.kie.kogito.internal.process.runtime.KogitoProcessRuntime processRuntime = + ((org.jbpm.process.instance.InternalProcessRuntime) jbpmProcessInstance.getKnowledgeRuntime().getProcessRuntime()) + .getKogitoProcessRuntime(); + + if (processRuntime == null) { + logger.debug("KogitoProcessRuntime not available, cannot access AuthTokenProvider"); + return Optional.empty(); + } + + ProcessConfig processConfig = processRuntime.getApplication().config().get(ProcessConfig.class); + if (processConfig == null) { + logger.debug("ProcessConfig not available, cannot access AuthTokenProvider"); + return Optional.empty(); + } + + AuthTokenProvider authTokenProvider = processConfig.authTokenProvider(); + if (authTokenProvider == null) { + logger.debug("AuthTokenProvider not available"); + return Optional.empty(); + } + + Optional<String> token = authTokenProvider.getAuthToken(); + if (token.isPresent()) { + logger.debug("Using propagated token from AuthTokenProvider"); + return token; + } else { + logger.debug("No token available from AuthTokenProvider"); + return Optional.empty(); + } + } catch (Exception e) { + logger.warn("Failed to retrieve token from AuthTokenProvider", e); + return Optional.empty(); + } + } + + private Optional<String> getConfiguredToken(KogitoWorkItem item, Map<String, Object> parameters) { + Optional<String> paramToken = Optional.ofNullable((String) parameters.get(PROPAGATE_TOKEN_PARAM)); + if (paramToken.isPresent()) { + logger.debug("Using configured token from parameters (BPMN or injected)"); + return paramToken; + } + + try { + if (item.getProcessInstance() == null) { Review Comment: No need for this if, process instance shouldn't be null. If, by any chance it is, it is ok that we have a NPE in line 170 ########## kogito-workitems/kogito-rest-workitem/src/main/java/org/kogito/workitem/rest/decorators/TokenPropagationDecorator.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.kogito.workitem.rest.decorators; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.auth.AuthTokenProvider; +import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.internal.process.workitem.KogitoWorkItem; +import org.kie.kogito.process.ProcessConfig; +import org.kie.kogito.process.workitems.impl.ConfigResolverHolder; +import org.kogito.workitem.rest.auth.AuthDecorator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.mutiny.ext.web.client.HttpRequest; + +enum AccessTokenAcquisitionStrategy { + NONE("none"), + CONFIGURED("configured"), + PROPAGATED("propagated"); + + private String name; + + AccessTokenAcquisitionStrategy(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static AccessTokenAcquisitionStrategy fromName(String strategyName) { + return Arrays.stream(AccessTokenAcquisitionStrategy.values()) + .filter(strategy -> strategy.getName().equals(strategyName)) + .findFirst() + .orElse(NONE); + } +} + +public class TokenPropagationDecorator implements AuthDecorator { + + private static final Logger logger = LoggerFactory.getLogger(TokenPropagationDecorator.class); + + public static final String ACCESS_TOKEN_ACQUISITION_STRATEGY = "AccessTokenAcquisitionStrategy"; + + private static final String PROPAGATE_TOKEN_PARAM = "propagateToken"; Review Comment: Not needed ########## kogito-workitems/kogito-rest-workitem/src/test/java/org/kogito/workitem/rest/decorators/TokenPropagationDecoratorTest.java: ########## @@ -0,0 +1,269 @@ +/* + * 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.kogito.workitem.rest.decorators; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.drools.core.common.InternalKnowledgeRuntime; +import org.jbpm.process.instance.InternalProcessRuntime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kie.kogito.Application; +import org.kie.kogito.Config; +import org.kie.kogito.auth.AuthTokenProvider; +import org.kie.kogito.internal.process.runtime.KogitoNodeInstance; +import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.internal.process.runtime.KogitoProcessRuntime; +import org.kie.kogito.internal.process.workitem.KogitoWorkItem; +import org.kie.kogito.process.ProcessConfig; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import io.vertx.mutiny.ext.web.client.HttpRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class TokenPropagationDecoratorTest { + + private static final String CONFIGURED_TOKEN = "configured-token-12345"; + private static final String PROPAGATED_TOKEN = "propagated-token-67890"; + private static final String ACCESS_TOKEN_ACQUISITION_STRATEGY = "AccessTokenAcquisitionStrategy"; + private static final String PROPAGATE_TOKEN_PARAM = "propagateToken"; Review Comment: Cannot we use the constants in `TokenPropagationDecorator`? ########## kogito-workitems/kogito-rest-workitem/src/main/java/org/kogito/workitem/rest/decorators/TokenPropagationDecorator.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.kogito.workitem.rest.decorators; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.auth.AuthTokenProvider; +import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.internal.process.workitem.KogitoWorkItem; +import org.kie.kogito.process.ProcessConfig; +import org.kie.kogito.process.workitems.impl.ConfigResolverHolder; +import org.kogito.workitem.rest.auth.AuthDecorator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.mutiny.ext.web.client.HttpRequest; + +enum AccessTokenAcquisitionStrategy { + NONE("none"), + CONFIGURED("configured"), + PROPAGATED("propagated"); + + private String name; + + AccessTokenAcquisitionStrategy(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static AccessTokenAcquisitionStrategy fromName(String strategyName) { + return Arrays.stream(AccessTokenAcquisitionStrategy.values()) + .filter(strategy -> strategy.getName().equals(strategyName)) + .findFirst() + .orElse(NONE); + } +} + +public class TokenPropagationDecorator implements AuthDecorator { + + private static final Logger logger = LoggerFactory.getLogger(TokenPropagationDecorator.class); + + public static final String ACCESS_TOKEN_ACQUISITION_STRATEGY = "AccessTokenAcquisitionStrategy"; + + private static final String PROPAGATE_TOKEN_PARAM = "propagateToken"; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + @Override + public void decorate(KogitoWorkItem item, Map<String, Object> parameters, HttpRequest<?> request) { + Optional<String> bearerToken = getBearerToken(item, parameters); + + bearerToken.ifPresentOrElse( + token -> { + logger.debug("Rest workItem `{}`: Bearer token available, request will be sent with authentication", item.getNodeInstance().getId()); + request.bearerTokenAuthentication(token); + }, + () -> logger.debug("Rest workItem `{}`: No Bearer Token available, request will be sent without authentication", item.getNodeInstance().getId())); + } + + Optional<String> getBearerToken(KogitoWorkItem item, Map<String, Object> parameters) { + Object strategyParam = parameters.get(ACCESS_TOKEN_ACQUISITION_STRATEGY); + + if (!(strategyParam instanceof String)) { + logger.debug("No token acquisition strategy specified, skipping authentication"); + return Optional.empty(); + } + + String strategyName = (String) strategyParam; + AccessTokenAcquisitionStrategy strategy = AccessTokenAcquisitionStrategy.fromName(strategyName); + + return switch (strategy) { + case PROPAGATED -> getPropagatedTokenFromHeaders(item); + case CONFIGURED -> getConfiguredToken(item, parameters); + default -> { + logger.debug("Strategy {} is NONE or unknown, skipping", strategyName); + yield Optional.empty(); + } + }; + } + + private Optional<String> getPropagatedTokenFromHeaders(KogitoWorkItem item) { + + KogitoProcessInstance processInstance = item.getProcessInstance(); + if (processInstance == null) { + logger.debug("No process instance available, cannot propagate token"); + return Optional.empty(); + } + + try { + // Cast to ProcessInstanceImpl to access getKnowledgeRuntime() + if (!(processInstance instanceof org.jbpm.process.instance.ProcessInstance)) { + logger.debug("Process instance is not a jBPM ProcessInstance, cannot access runtime"); + return Optional.empty(); + } + + org.jbpm.process.instance.ProcessInstance jbpmProcessInstance = + (org.jbpm.process.instance.ProcessInstance) processInstance; + + // Get the AuthTokenProvider from the ProcessConfig via KogitoProcessRuntime + org.kie.kogito.internal.process.runtime.KogitoProcessRuntime processRuntime = + ((org.jbpm.process.instance.InternalProcessRuntime) jbpmProcessInstance.getKnowledgeRuntime().getProcessRuntime()) + .getKogitoProcessRuntime(); + + if (processRuntime == null) { + logger.debug("KogitoProcessRuntime not available, cannot access AuthTokenProvider"); + return Optional.empty(); + } + + ProcessConfig processConfig = processRuntime.getApplication().config().get(ProcessConfig.class); + if (processConfig == null) { + logger.debug("ProcessConfig not available, cannot access AuthTokenProvider"); + return Optional.empty(); + } + + AuthTokenProvider authTokenProvider = processConfig.authTokenProvider(); + if (authTokenProvider == null) { + logger.debug("AuthTokenProvider not available"); + return Optional.empty(); + } + + Optional<String> token = authTokenProvider.getAuthToken(); + if (token.isPresent()) { + logger.debug("Using propagated token from AuthTokenProvider"); + return token; + } else { + logger.debug("No token available from AuthTokenProvider"); + return Optional.empty(); + } + } catch (Exception e) { + logger.warn("Failed to retrieve token from AuthTokenProvider", e); + return Optional.empty(); + } + } + + private Optional<String> getConfiguredToken(KogitoWorkItem item, Map<String, Object> parameters) { + Optional<String> paramToken = Optional.ofNullable((String) parameters.get(PROPAGATE_TOKEN_PARAM)); + if (paramToken.isPresent()) { + logger.debug("Using configured token from parameters (BPMN or injected)"); + return paramToken; + } + + try { + if (item.getProcessInstance() == null) { + logger.debug("No process instance available, cannot read from ConfigResolver"); + return Optional.empty(); + } + + String processId = item.getProcessInstance().getProcessId(); + String taskName = (String) parameters.getOrDefault("RestServiceCallTaskId", item.getName()); + + if (processId == null || taskName == null) { + logger.debug("Process ID or task name is null, cannot build configuration key"); + return Optional.empty(); + } + + String configKey = String.format("kogito.processes.%s.%s.access_token", processId, taskName); + + Optional<String> configToken = ConfigResolverHolder.getConfigResolver() + .getConfigProperty(configKey, String.class); + + if (configToken.isPresent()) { + logger.debug("Using configured token from ConfigResolver with key: {}", configKey); + return configToken; + } else { + logger.debug("No token found in ConfigResolver for key: {}", configKey); + } + } catch (Exception e) { + logger.warn("Failed to retrieve token from ConfigResolver", e); Review Comment: I'd throw here too, if we cannot read the token by any chance it means that something wrong happens and the request shouldn't be sent ########## kogito-workitems/kogito-rest-workitem/src/main/java/org/kogito/workitem/rest/decorators/TokenPropagationDecorator.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.kogito.workitem.rest.decorators; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.auth.AuthTokenProvider; +import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.internal.process.workitem.KogitoWorkItem; +import org.kie.kogito.process.ProcessConfig; +import org.kie.kogito.process.workitems.impl.ConfigResolverHolder; +import org.kogito.workitem.rest.auth.AuthDecorator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.mutiny.ext.web.client.HttpRequest; + +enum AccessTokenAcquisitionStrategy { + NONE("none"), + CONFIGURED("configured"), + PROPAGATED("propagated"); + + private String name; + + AccessTokenAcquisitionStrategy(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static AccessTokenAcquisitionStrategy fromName(String strategyName) { + return Arrays.stream(AccessTokenAcquisitionStrategy.values()) + .filter(strategy -> strategy.getName().equals(strategyName)) + .findFirst() + .orElse(NONE); + } +} + +public class TokenPropagationDecorator implements AuthDecorator { + + private static final Logger logger = LoggerFactory.getLogger(TokenPropagationDecorator.class); + + public static final String ACCESS_TOKEN_ACQUISITION_STRATEGY = "AccessTokenAcquisitionStrategy"; + + private static final String PROPAGATE_TOKEN_PARAM = "propagateToken"; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + @Override + public void decorate(KogitoWorkItem item, Map<String, Object> parameters, HttpRequest<?> request) { + Optional<String> bearerToken = getBearerToken(item, parameters); + + bearerToken.ifPresentOrElse( + token -> { + logger.debug("Rest workItem `{}`: Bearer token available, request will be sent with authentication", item.getNodeInstance().getId()); + request.bearerTokenAuthentication(token); + }, + () -> logger.debug("Rest workItem `{}`: No Bearer Token available, request will be sent without authentication", item.getNodeInstance().getId())); + } + + Optional<String> getBearerToken(KogitoWorkItem item, Map<String, Object> parameters) { + Object strategyParam = parameters.get(ACCESS_TOKEN_ACQUISITION_STRATEGY); + + if (!(strategyParam instanceof String)) { + logger.debug("No token acquisition strategy specified, skipping authentication"); + return Optional.empty(); + } + + String strategyName = (String) strategyParam; + AccessTokenAcquisitionStrategy strategy = AccessTokenAcquisitionStrategy.fromName(strategyName); + + return switch (strategy) { + case PROPAGATED -> getPropagatedTokenFromHeaders(item); + case CONFIGURED -> getConfiguredToken(item, parameters); + default -> { + logger.debug("Strategy {} is NONE or unknown, skipping", strategyName); + yield Optional.empty(); + } + }; + } + + private Optional<String> getPropagatedTokenFromHeaders(KogitoWorkItem item) { + + KogitoProcessInstance processInstance = item.getProcessInstance(); + if (processInstance == null) { + logger.debug("No process instance available, cannot propagate token"); + return Optional.empty(); + } + + try { + // Cast to ProcessInstanceImpl to access getKnowledgeRuntime() + if (!(processInstance instanceof org.jbpm.process.instance.ProcessInstance)) { + logger.debug("Process instance is not a jBPM ProcessInstance, cannot access runtime"); + return Optional.empty(); + } + + org.jbpm.process.instance.ProcessInstance jbpmProcessInstance = + (org.jbpm.process.instance.ProcessInstance) processInstance; + + // Get the AuthTokenProvider from the ProcessConfig via KogitoProcessRuntime + org.kie.kogito.internal.process.runtime.KogitoProcessRuntime processRuntime = + ((org.jbpm.process.instance.InternalProcessRuntime) jbpmProcessInstance.getKnowledgeRuntime().getProcessRuntime()) + .getKogitoProcessRuntime(); + + if (processRuntime == null) { + logger.debug("KogitoProcessRuntime not available, cannot access AuthTokenProvider"); + return Optional.empty(); + } + + ProcessConfig processConfig = processRuntime.getApplication().config().get(ProcessConfig.class); + if (processConfig == null) { + logger.debug("ProcessConfig not available, cannot access AuthTokenProvider"); + return Optional.empty(); + } + + AuthTokenProvider authTokenProvider = processConfig.authTokenProvider(); + if (authTokenProvider == null) { + logger.debug("AuthTokenProvider not available"); + return Optional.empty(); + } + + Optional<String> token = authTokenProvider.getAuthToken(); + if (token.isPresent()) { + logger.debug("Using propagated token from AuthTokenProvider"); + return token; + } else { + logger.debug("No token available from AuthTokenProvider"); + return Optional.empty(); + } + } catch (Exception e) { + logger.warn("Failed to retrieve token from AuthTokenProvider", e); + return Optional.empty(); + } + } + + private Optional<String> getConfiguredToken(KogitoWorkItem item, Map<String, Object> parameters) { + Optional<String> paramToken = Optional.ofNullable((String) parameters.get(PROPAGATE_TOKEN_PARAM)); + if (paramToken.isPresent()) { + logger.debug("Using configured token from parameters (BPMN or injected)"); + return paramToken; + } + + try { + if (item.getProcessInstance() == null) { + logger.debug("No process instance available, cannot read from ConfigResolver"); + return Optional.empty(); + } + + String processId = item.getProcessInstance().getProcessId(); + String taskName = (String) parameters.getOrDefault("RestServiceCallTaskId", item.getName()); Review Comment: Maybe use a constant for `RestServiceCallTaskId` ########## kogito-workitems/kogito-rest-workitem/src/main/java/org/kogito/workitem/rest/decorators/TokenPropagationDecorator.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.kogito.workitem.rest.decorators; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.auth.AuthTokenProvider; +import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.internal.process.workitem.KogitoWorkItem; +import org.kie.kogito.process.ProcessConfig; +import org.kie.kogito.process.workitems.impl.ConfigResolverHolder; +import org.kogito.workitem.rest.auth.AuthDecorator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.mutiny.ext.web.client.HttpRequest; + +enum AccessTokenAcquisitionStrategy { + NONE("none"), + CONFIGURED("configured"), + PROPAGATED("propagated"); + + private String name; + + AccessTokenAcquisitionStrategy(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static AccessTokenAcquisitionStrategy fromName(String strategyName) { + return Arrays.stream(AccessTokenAcquisitionStrategy.values()) + .filter(strategy -> strategy.getName().equals(strategyName)) + .findFirst() + .orElse(NONE); + } +} + +public class TokenPropagationDecorator implements AuthDecorator { + + private static final Logger logger = LoggerFactory.getLogger(TokenPropagationDecorator.class); + + public static final String ACCESS_TOKEN_ACQUISITION_STRATEGY = "AccessTokenAcquisitionStrategy"; + + private static final String PROPAGATE_TOKEN_PARAM = "propagateToken"; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + @Override + public void decorate(KogitoWorkItem item, Map<String, Object> parameters, HttpRequest<?> request) { + Optional<String> bearerToken = getBearerToken(item, parameters); + + bearerToken.ifPresentOrElse( + token -> { + logger.debug("Rest workItem `{}`: Bearer token available, request will be sent with authentication", item.getNodeInstance().getId()); + request.bearerTokenAuthentication(token); + }, + () -> logger.debug("Rest workItem `{}`: No Bearer Token available, request will be sent without authentication", item.getNodeInstance().getId())); + } + + Optional<String> getBearerToken(KogitoWorkItem item, Map<String, Object> parameters) { + Object strategyParam = parameters.get(ACCESS_TOKEN_ACQUISITION_STRATEGY); + + if (!(strategyParam instanceof String)) { + logger.debug("No token acquisition strategy specified, skipping authentication"); Review Comment: In general I'm in favor of throwing exception when we have a wrong config that can lead into a `possible` wrong execution and make it clear to the user that something wrong happened. Getting an `unexpected` value here (ej a number) shouldn't be prevented by the editor, so I'm okish falling back to `none` (or empty). @martinweiler @Christine-Jose wdyt? ########## kogito-workitems/kogito-rest-workitem/src/main/java/org/kogito/workitem/rest/decorators/TokenPropagationDecorator.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.kogito.workitem.rest.decorators; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.auth.AuthTokenProvider; +import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.internal.process.workitem.KogitoWorkItem; +import org.kie.kogito.process.ProcessConfig; +import org.kie.kogito.process.workitems.impl.ConfigResolverHolder; +import org.kogito.workitem.rest.auth.AuthDecorator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.mutiny.ext.web.client.HttpRequest; + +enum AccessTokenAcquisitionStrategy { + NONE("none"), + CONFIGURED("configured"), + PROPAGATED("propagated"); + + private String name; + + AccessTokenAcquisitionStrategy(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static AccessTokenAcquisitionStrategy fromName(String strategyName) { + return Arrays.stream(AccessTokenAcquisitionStrategy.values()) + .filter(strategy -> strategy.getName().equals(strategyName)) + .findFirst() + .orElse(NONE); + } +} + +public class TokenPropagationDecorator implements AuthDecorator { + + private static final Logger logger = LoggerFactory.getLogger(TokenPropagationDecorator.class); + + public static final String ACCESS_TOKEN_ACQUISITION_STRATEGY = "AccessTokenAcquisitionStrategy"; + + private static final String PROPAGATE_TOKEN_PARAM = "propagateToken"; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + @Override + public void decorate(KogitoWorkItem item, Map<String, Object> parameters, HttpRequest<?> request) { + Optional<String> bearerToken = getBearerToken(item, parameters); + + bearerToken.ifPresentOrElse( + token -> { + logger.debug("Rest workItem `{}`: Bearer token available, request will be sent with authentication", item.getNodeInstance().getId()); + request.bearerTokenAuthentication(token); + }, + () -> logger.debug("Rest workItem `{}`: No Bearer Token available, request will be sent without authentication", item.getNodeInstance().getId())); + } + + Optional<String> getBearerToken(KogitoWorkItem item, Map<String, Object> parameters) { + Object strategyParam = parameters.get(ACCESS_TOKEN_ACQUISITION_STRATEGY); + + if (!(strategyParam instanceof String)) { + logger.debug("No token acquisition strategy specified, skipping authentication"); + return Optional.empty(); + } + + String strategyName = (String) strategyParam; + AccessTokenAcquisitionStrategy strategy = AccessTokenAcquisitionStrategy.fromName(strategyName); + + return switch (strategy) { + case PROPAGATED -> getPropagatedTokenFromHeaders(item); + case CONFIGURED -> getConfiguredToken(item, parameters); + default -> { + logger.debug("Strategy {} is NONE or unknown, skipping", strategyName); + yield Optional.empty(); + } + }; + } + + private Optional<String> getPropagatedTokenFromHeaders(KogitoWorkItem item) { + + KogitoProcessInstance processInstance = item.getProcessInstance(); + if (processInstance == null) { + logger.debug("No process instance available, cannot propagate token"); + return Optional.empty(); + } + + try { + // Cast to ProcessInstanceImpl to access getKnowledgeRuntime() + if (!(processInstance instanceof org.jbpm.process.instance.ProcessInstance)) { + logger.debug("Process instance is not a jBPM ProcessInstance, cannot access runtime"); + return Optional.empty(); + } + + org.jbpm.process.instance.ProcessInstance jbpmProcessInstance = + (org.jbpm.process.instance.ProcessInstance) processInstance; + + // Get the AuthTokenProvider from the ProcessConfig via KogitoProcessRuntime + org.kie.kogito.internal.process.runtime.KogitoProcessRuntime processRuntime = + ((org.jbpm.process.instance.InternalProcessRuntime) jbpmProcessInstance.getKnowledgeRuntime().getProcessRuntime()) + .getKogitoProcessRuntime(); + + if (processRuntime == null) { + logger.debug("KogitoProcessRuntime not available, cannot access AuthTokenProvider"); + return Optional.empty(); + } + + ProcessConfig processConfig = processRuntime.getApplication().config().get(ProcessConfig.class); + if (processConfig == null) { + logger.debug("ProcessConfig not available, cannot access AuthTokenProvider"); + return Optional.empty(); + } + + AuthTokenProvider authTokenProvider = processConfig.authTokenProvider(); + if (authTokenProvider == null) { + logger.debug("AuthTokenProvider not available"); + return Optional.empty(); + } Review Comment: I think that there's no need for all this checks. I think that all this conditions should be always satisfied and if not (ej, processRuntime being null) is it ok that and exception is thrown and we propagate in the `catch` statement. adding a debug trace and returning an empty optional may hide a possible wrong execution ########## kogito-workitems/kogito-rest-workitem/src/main/java/org/kogito/workitem/rest/decorators/TokenPropagationDecorator.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.kogito.workitem.rest.decorators; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.auth.AuthTokenProvider; +import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.internal.process.workitem.KogitoWorkItem; +import org.kie.kogito.process.ProcessConfig; +import org.kie.kogito.process.workitems.impl.ConfigResolverHolder; +import org.kogito.workitem.rest.auth.AuthDecorator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.mutiny.ext.web.client.HttpRequest; + +enum AccessTokenAcquisitionStrategy { + NONE("none"), + CONFIGURED("configured"), + PROPAGATED("propagated"); + + private String name; + + AccessTokenAcquisitionStrategy(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static AccessTokenAcquisitionStrategy fromName(String strategyName) { + return Arrays.stream(AccessTokenAcquisitionStrategy.values()) + .filter(strategy -> strategy.getName().equals(strategyName)) + .findFirst() + .orElse(NONE); + } +} + +public class TokenPropagationDecorator implements AuthDecorator { + + private static final Logger logger = LoggerFactory.getLogger(TokenPropagationDecorator.class); + + public static final String ACCESS_TOKEN_ACQUISITION_STRATEGY = "AccessTokenAcquisitionStrategy"; + + private static final String PROPAGATE_TOKEN_PARAM = "propagateToken"; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + @Override + public void decorate(KogitoWorkItem item, Map<String, Object> parameters, HttpRequest<?> request) { + Optional<String> bearerToken = getBearerToken(item, parameters); + + bearerToken.ifPresentOrElse( + token -> { + logger.debug("Rest workItem `{}`: Bearer token available, request will be sent with authentication", item.getNodeInstance().getId()); + request.bearerTokenAuthentication(token); + }, + () -> logger.debug("Rest workItem `{}`: No Bearer Token available, request will be sent without authentication", item.getNodeInstance().getId())); + } + + Optional<String> getBearerToken(KogitoWorkItem item, Map<String, Object> parameters) { + Object strategyParam = parameters.get(ACCESS_TOKEN_ACQUISITION_STRATEGY); + + if (!(strategyParam instanceof String)) { + logger.debug("No token acquisition strategy specified, skipping authentication"); + return Optional.empty(); + } + + String strategyName = (String) strategyParam; + AccessTokenAcquisitionStrategy strategy = AccessTokenAcquisitionStrategy.fromName(strategyName); + + return switch (strategy) { + case PROPAGATED -> getPropagatedTokenFromHeaders(item); + case CONFIGURED -> getConfiguredToken(item, parameters); + default -> { + logger.debug("Strategy {} is NONE or unknown, skipping", strategyName); + yield Optional.empty(); + } + }; + } + + private Optional<String> getPropagatedTokenFromHeaders(KogitoWorkItem item) { + + KogitoProcessInstance processInstance = item.getProcessInstance(); + if (processInstance == null) { Review Comment: No need for this if, this shouldn't ever be the case and if it is, exception should be thrown later not swallowed. ########## kogito-workitems/kogito-rest-workitem/src/main/java/org/kogito/workitem/rest/decorators/TokenPropagationDecorator.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.kogito.workitem.rest.decorators; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.auth.AuthTokenProvider; +import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.internal.process.workitem.KogitoWorkItem; +import org.kie.kogito.process.ProcessConfig; +import org.kie.kogito.process.workitems.impl.ConfigResolverHolder; +import org.kogito.workitem.rest.auth.AuthDecorator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.mutiny.ext.web.client.HttpRequest; + +enum AccessTokenAcquisitionStrategy { + NONE("none"), + CONFIGURED("configured"), + PROPAGATED("propagated"); + + private String name; + + AccessTokenAcquisitionStrategy(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static AccessTokenAcquisitionStrategy fromName(String strategyName) { + return Arrays.stream(AccessTokenAcquisitionStrategy.values()) + .filter(strategy -> strategy.getName().equals(strategyName)) + .findFirst() + .orElse(NONE); + } +} + +public class TokenPropagationDecorator implements AuthDecorator { + + private static final Logger logger = LoggerFactory.getLogger(TokenPropagationDecorator.class); + + public static final String ACCESS_TOKEN_ACQUISITION_STRATEGY = "AccessTokenAcquisitionStrategy"; + + private static final String PROPAGATE_TOKEN_PARAM = "propagateToken"; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + @Override + public void decorate(KogitoWorkItem item, Map<String, Object> parameters, HttpRequest<?> request) { + Optional<String> bearerToken = getBearerToken(item, parameters); + + bearerToken.ifPresentOrElse( + token -> { + logger.debug("Rest workItem `{}`: Bearer token available, request will be sent with authentication", item.getNodeInstance().getId()); + request.bearerTokenAuthentication(token); + }, + () -> logger.debug("Rest workItem `{}`: No Bearer Token available, request will be sent without authentication", item.getNodeInstance().getId())); + } + + Optional<String> getBearerToken(KogitoWorkItem item, Map<String, Object> parameters) { + Object strategyParam = parameters.get(ACCESS_TOKEN_ACQUISITION_STRATEGY); + + if (!(strategyParam instanceof String)) { + logger.debug("No token acquisition strategy specified, skipping authentication"); + return Optional.empty(); + } + + String strategyName = (String) strategyParam; + AccessTokenAcquisitionStrategy strategy = AccessTokenAcquisitionStrategy.fromName(strategyName); + + return switch (strategy) { + case PROPAGATED -> getPropagatedTokenFromHeaders(item); + case CONFIGURED -> getConfiguredToken(item, parameters); + default -> { + logger.debug("Strategy {} is NONE or unknown, skipping", strategyName); + yield Optional.empty(); + } + }; + } + + private Optional<String> getPropagatedTokenFromHeaders(KogitoWorkItem item) { + + KogitoProcessInstance processInstance = item.getProcessInstance(); + if (processInstance == null) { + logger.debug("No process instance available, cannot propagate token"); + return Optional.empty(); + } + + try { + // Cast to ProcessInstanceImpl to access getKnowledgeRuntime() + if (!(processInstance instanceof org.jbpm.process.instance.ProcessInstance)) { Review Comment: No need for this if, I think we can directly cast and not swallow any possible exception. ########## kogito-workitems/kogito-rest-workitem/src/main/java/org/kogito/workitem/rest/decorators/TokenPropagationDecorator.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.kogito.workitem.rest.decorators; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.auth.AuthTokenProvider; +import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.internal.process.workitem.KogitoWorkItem; +import org.kie.kogito.process.ProcessConfig; +import org.kie.kogito.process.workitems.impl.ConfigResolverHolder; +import org.kogito.workitem.rest.auth.AuthDecorator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.mutiny.ext.web.client.HttpRequest; + +enum AccessTokenAcquisitionStrategy { + NONE("none"), + CONFIGURED("configured"), + PROPAGATED("propagated"); + + private String name; + + AccessTokenAcquisitionStrategy(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static AccessTokenAcquisitionStrategy fromName(String strategyName) { + return Arrays.stream(AccessTokenAcquisitionStrategy.values()) + .filter(strategy -> strategy.getName().equals(strategyName)) + .findFirst() + .orElse(NONE); + } +} + +public class TokenPropagationDecorator implements AuthDecorator { + + private static final Logger logger = LoggerFactory.getLogger(TokenPropagationDecorator.class); + + public static final String ACCESS_TOKEN_ACQUISITION_STRATEGY = "AccessTokenAcquisitionStrategy"; + + private static final String PROPAGATE_TOKEN_PARAM = "propagateToken"; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + @Override + public void decorate(KogitoWorkItem item, Map<String, Object> parameters, HttpRequest<?> request) { + Optional<String> bearerToken = getBearerToken(item, parameters); + + bearerToken.ifPresentOrElse( + token -> { + logger.debug("Rest workItem `{}`: Bearer token available, request will be sent with authentication", item.getNodeInstance().getId()); + request.bearerTokenAuthentication(token); + }, + () -> logger.debug("Rest workItem `{}`: No Bearer Token available, request will be sent without authentication", item.getNodeInstance().getId())); + } + + Optional<String> getBearerToken(KogitoWorkItem item, Map<String, Object> parameters) { + Object strategyParam = parameters.get(ACCESS_TOKEN_ACQUISITION_STRATEGY); + + if (!(strategyParam instanceof String)) { + logger.debug("No token acquisition strategy specified, skipping authentication"); + return Optional.empty(); + } + + String strategyName = (String) strategyParam; + AccessTokenAcquisitionStrategy strategy = AccessTokenAcquisitionStrategy.fromName(strategyName); + + return switch (strategy) { + case PROPAGATED -> getPropagatedTokenFromHeaders(item); + case CONFIGURED -> getConfiguredToken(item, parameters); + default -> { + logger.debug("Strategy {} is NONE or unknown, skipping", strategyName); + yield Optional.empty(); + } + }; + } + + private Optional<String> getPropagatedTokenFromHeaders(KogitoWorkItem item) { + + KogitoProcessInstance processInstance = item.getProcessInstance(); + if (processInstance == null) { + logger.debug("No process instance available, cannot propagate token"); + return Optional.empty(); + } + + try { + // Cast to ProcessInstanceImpl to access getKnowledgeRuntime() + if (!(processInstance instanceof org.jbpm.process.instance.ProcessInstance)) { + logger.debug("Process instance is not a jBPM ProcessInstance, cannot access runtime"); + return Optional.empty(); + } + + org.jbpm.process.instance.ProcessInstance jbpmProcessInstance = + (org.jbpm.process.instance.ProcessInstance) processInstance; + + // Get the AuthTokenProvider from the ProcessConfig via KogitoProcessRuntime + org.kie.kogito.internal.process.runtime.KogitoProcessRuntime processRuntime = + ((org.jbpm.process.instance.InternalProcessRuntime) jbpmProcessInstance.getKnowledgeRuntime().getProcessRuntime()) + .getKogitoProcessRuntime(); + + if (processRuntime == null) { + logger.debug("KogitoProcessRuntime not available, cannot access AuthTokenProvider"); + return Optional.empty(); + } + + ProcessConfig processConfig = processRuntime.getApplication().config().get(ProcessConfig.class); + if (processConfig == null) { + logger.debug("ProcessConfig not available, cannot access AuthTokenProvider"); + return Optional.empty(); + } + + AuthTokenProvider authTokenProvider = processConfig.authTokenProvider(); + if (authTokenProvider == null) { + logger.debug("AuthTokenProvider not available"); + return Optional.empty(); + } + + Optional<String> token = authTokenProvider.getAuthToken(); + if (token.isPresent()) { + logger.debug("Using propagated token from AuthTokenProvider"); + return token; + } else { + logger.debug("No token available from AuthTokenProvider"); + return Optional.empty(); + } + } catch (Exception e) { + logger.warn("Failed to retrieve token from AuthTokenProvider", e); + return Optional.empty(); Review Comment: I think that here we must propagate the exception instead of swallowing it, otherwise we may be sending wrong requests ########## jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/KogitoWorkItemImpl.java: ########## @@ -251,7 +251,18 @@ public String toString() { Map.Entry<String, Object> entry = iterator.next(); b.append(entry.getKey()); b.append("="); - b.append(entry.getValue()); + Object value = entry.getValue(); + // Mask sensitive token data for both legacy propagateToken and new AccessTokenAcquisitionStrategy + if (("propagateToken".equals(entry.getKey()) || "AccessTokenAcquisitionStrategy".equals(entry.getKey())) && value != null) { Review Comment: @Christine-Jose I think this won't be required since token won't be directly stored in the workitem parameters Map, ########## kogito-workitems/kogito-rest-workitem/src/main/java/org/kogito/workitem/rest/decorators/TokenPropagationDecorator.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.kogito.workitem.rest.decorators; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.auth.AuthTokenProvider; +import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.internal.process.workitem.KogitoWorkItem; +import org.kie.kogito.process.ProcessConfig; +import org.kie.kogito.process.workitems.impl.ConfigResolverHolder; +import org.kogito.workitem.rest.auth.AuthDecorator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.mutiny.ext.web.client.HttpRequest; + +enum AccessTokenAcquisitionStrategy { + NONE("none"), + CONFIGURED("configured"), + PROPAGATED("propagated"); + + private String name; + + AccessTokenAcquisitionStrategy(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static AccessTokenAcquisitionStrategy fromName(String strategyName) { + return Arrays.stream(AccessTokenAcquisitionStrategy.values()) + .filter(strategy -> strategy.getName().equals(strategyName)) + .findFirst() + .orElse(NONE); + } +} + +public class TokenPropagationDecorator implements AuthDecorator { + + private static final Logger logger = LoggerFactory.getLogger(TokenPropagationDecorator.class); + + public static final String ACCESS_TOKEN_ACQUISITION_STRATEGY = "AccessTokenAcquisitionStrategy"; + + private static final String PROPAGATE_TOKEN_PARAM = "propagateToken"; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; Review Comment: I think they are not used. -- 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]
