diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/pom.xml b/components/action-mgt/org.wso2.carbon.identity.action.execution/pom.xml new file mode 100644 index 00000000000..06293b8c868 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/pom.xml @@ -0,0 +1,190 @@ + + + + + + org.wso2.carbon.identity.framework + action-mgt + 7.3.50-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.identity.action.execution + bundle + WSO2 Identity - Action Executor Component + Action execution backend component + http://wso2.org + + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.core + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.action.management + + + com.fasterxml.jackson.core + jackson-databind + + + + org.testng + testng + test + + + org.mockito + mockito-core + test + + + org.mockito + mockito-testng + test + + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + + ${project.artifactId} + + ${project.artifactId} + + org.wso2.carbon.identity.action.execution.internal, + + + !org.wso2.carbon.identity.action.execution.internal, + org.wso2.carbon.identity.action.execution.*; + version="${carbon.identity.package.export.version}" + + + org.wso2.carbon.identity.action.management.*; + version="${carbon.identity.package.import.version.range}", + org.apache.commons.lang; version="${commons-lang.wso2.osgi.version.range}", + org.apache.commons.logging; version="${import.package.version.commons.logging}", + org.apache.commons.collections; version="${commons-collections.wso2.osgi.version.range}", + org.apache.http; version="${httpcore.version.osgi.import.range}", + org.apache.http.client.config; version="${httpcore.version.osgi.import.range}", + org.apache.http.client.methods; version="${httpcore.version.osgi.import.range}", + org.apache.http.concurrent; version="${httpcore.version.osgi.import.range}", + org.apache.http.conn; version="${httpcore.version.osgi.import.range}", + org.apache.http.entity; version="${httpcore.version.osgi.import.range}", + org.apache.http.util; version="${httpcore.version.osgi.import.range}", + org.apache.http.impl.client; version="${httpcomponents-httpclient.imp.pkg.version.range}", + org.apache.http.impl.conn; version="${httpcomponents-httpclient.imp.pkg.version.range}", + org.osgi.framework; version="${osgi.framework.imp.pkg.version.range}", + org.osgi.service.component; version="${osgi.service.component.imp.pkg.version.range}", + org.wso2.carbon.utils; version="${carbon.kernel.package.import.version.range}", + com.fasterxml.jackson.core.*; version="${com.fasterxml.jackson.annotation.version.range}", + com.fasterxml.jackson.databind.*; version="${com.fasterxml.jackson.annotation.version.range}", + com.fasterxml.jackson.annotation.*; version="${com.fasterxml.jackson.annotation.version.range}", + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire.plugin.version} + + + --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED + --add-exports java.base/jdk.internal.loader=ALL-UNNAMED + + + src/test/resources/testng.xml + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + default-prepare-agent + + prepare-agent + + + + default-prepare-agent-integration + + prepare-agent-integration + + + + default-report + + report + + + + default-report-integration + + report-integration + + + + default-check + + check + + + + + BUNDLE + + + COMPLEXITY + COVEREDRATIO + 0.90 + + + + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + + ../../../spotbugs-exclude.xml + Max + Low + true + + + + + diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionRequestBuilder.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionRequestBuilder.java new file mode 100644 index 00000000000..021b9d5372b --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionRequestBuilder.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution; + +import org.wso2.carbon.identity.action.execution.exception.ActionExecutionRequestBuilderException; +import org.wso2.carbon.identity.action.execution.model.ActionExecutionRequest; +import org.wso2.carbon.identity.action.execution.model.ActionType; + +import java.util.Map; + +/** + * This interface defines the Action Execution Request Builder. + * Action Execution Request Builder is the component that is responsible for building the Action Execution Request + * based on the action type and the event context. + */ +public interface ActionExecutionRequestBuilder { + + ActionType getSupportedActionType(); + + ActionExecutionRequest buildActionExecutionRequest(Map eventContext) throws + ActionExecutionRequestBuilderException; + +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionRequestBuilderFactory.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionRequestBuilderFactory.java new file mode 100644 index 00000000000..19636269172 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionRequestBuilderFactory.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution; + +import org.wso2.carbon.identity.action.execution.model.ActionExecutionRequest; +import org.wso2.carbon.identity.action.execution.model.ActionType; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class defines the Action Execution Request Builder Factory. + * Action Execution Request Builder Factory is the component that is responsible for building + * {@link ActionExecutionRequest} + * based on the action type and the event context. + */ +public class ActionExecutionRequestBuilderFactory { + + private static final Map actionInvocationRequestBuilders = + new HashMap<>(); + + public static ActionExecutionRequestBuilder getActionExecutionRequestBuilder(ActionType actionType) { + + return actionInvocationRequestBuilders.get(actionType); + } + + public static void registerActionExecutionRequestBuilder(ActionExecutionRequestBuilder + actionExecutionRequestBuilder) { + + actionInvocationRequestBuilders.put(actionExecutionRequestBuilder.getSupportedActionType(), + actionExecutionRequestBuilder); + } + + public static void unregisterActionExecutionRequestBuilder(ActionExecutionRequestBuilder + actionExecutionRequestBuilder) { + + actionInvocationRequestBuilders.remove(actionExecutionRequestBuilder.getSupportedActionType()); + } + +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionResponseProcessor.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionResponseProcessor.java new file mode 100644 index 00000000000..1b3feb4eb7f --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionResponseProcessor.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution; + +import org.wso2.carbon.identity.action.execution.exception.ActionExecutionResponseProcessorException; +import org.wso2.carbon.identity.action.execution.model.ActionExecutionStatus; +import org.wso2.carbon.identity.action.execution.model.ActionInvocationErrorResponse; +import org.wso2.carbon.identity.action.execution.model.ActionInvocationSuccessResponse; +import org.wso2.carbon.identity.action.execution.model.ActionType; +import org.wso2.carbon.identity.action.execution.model.Event; + +import java.util.Map; + +/** + * This interface defines the Action Execution Response Processor. + * Action Execution Response Processor is the component that is responsible for processing the response received + * from the action execution. + */ +public interface ActionExecutionResponseProcessor { + + ActionType getSupportedActionType(); + + ActionExecutionStatus processSuccessResponse(Map eventContext, + Event actionEvent, + ActionInvocationSuccessResponse successResponse) throws + ActionExecutionResponseProcessorException; + + ActionExecutionStatus processErrorResponse(Map eventContext, + Event actionEvent, + ActionInvocationErrorResponse errorResponse) throws + ActionExecutionResponseProcessorException; +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionResponseProcessorFactory.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionResponseProcessorFactory.java new file mode 100644 index 00000000000..f0ca7e5039a --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionResponseProcessorFactory.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution; + +import org.wso2.carbon.identity.action.execution.model.ActionType; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class defines the Action Execution Response Processor Factory. + * Action Execution Response Processor Factory is the component that is responsible for providing the + * {@link ActionExecutionResponseProcessor} based on the action type. + */ +public class ActionExecutionResponseProcessorFactory { + + private static final Map actionInvocationResponseProcessors = + new HashMap<>(); + + public static ActionExecutionResponseProcessor getActionExecutionResponseProcessor(ActionType actionType) { + + switch (actionType) { + case PRE_ISSUE_ACCESS_TOKEN: + return actionInvocationResponseProcessors.get(ActionType.PRE_ISSUE_ACCESS_TOKEN); + default: + return null; + } + } + + public static void registerActionExecutionResponseProcessor(ActionExecutionResponseProcessor + actionExecutionResponseProcessor) { + + actionInvocationResponseProcessors.put(actionExecutionResponseProcessor.getSupportedActionType(), + actionExecutionResponseProcessor); + } + + public static void unregisterActionExecutionResponseProcessor(ActionExecutionResponseProcessor + actionExecutionResponseProcessor) { + + actionInvocationResponseProcessors.remove(actionExecutionResponseProcessor.getSupportedActionType()); + } + +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutorService.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutorService.java new file mode 100644 index 00000000000..98646f5c9a5 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutorService.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution; + +import org.wso2.carbon.identity.action.execution.exception.ActionExecutionException; +import org.wso2.carbon.identity.action.execution.model.ActionExecutionStatus; +import org.wso2.carbon.identity.action.execution.model.ActionType; + +import java.util.Map; + +/** + * This interface defines the Action Executor Service. + * Action Executor Service is the component that is responsible for executing the action based on the action type + * and the event context. + */ +public interface ActionExecutorService { + + ActionExecutionStatus execute(ActionType actionType, Map eventContext, String tenantDomain) throws + ActionExecutionException; + +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutorServiceImpl.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutorServiceImpl.java new file mode 100644 index 00000000000..fa20f3c04bc --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutorServiceImpl.java @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.action.execution.exception.ActionExecutionException; +import org.wso2.carbon.identity.action.execution.exception.ActionExecutionRequestBuilderException; +import org.wso2.carbon.identity.action.execution.exception.ActionExecutionResponseProcessorException; +import org.wso2.carbon.identity.action.execution.exception.ActionExecutionRuntimeException; +import org.wso2.carbon.identity.action.execution.internal.ActionExecutionServiceComponentHolder; +import org.wso2.carbon.identity.action.execution.model.ActionExecutionRequest; +import org.wso2.carbon.identity.action.execution.model.ActionExecutionStatus; +import org.wso2.carbon.identity.action.execution.model.ActionInvocationErrorResponse; +import org.wso2.carbon.identity.action.execution.model.ActionInvocationResponse; +import org.wso2.carbon.identity.action.execution.model.ActionInvocationSuccessResponse; +import org.wso2.carbon.identity.action.execution.model.ActionType; +import org.wso2.carbon.identity.action.execution.model.AllowedOperation; +import org.wso2.carbon.identity.action.execution.model.PerformableOperation; +import org.wso2.carbon.identity.action.execution.util.APIClient; +import org.wso2.carbon.identity.action.execution.util.AuthMethods; +import org.wso2.carbon.identity.action.execution.util.OperationComparator; +import org.wso2.carbon.identity.action.management.exception.ActionMgtException; +import org.wso2.carbon.identity.action.management.model.Action; +import org.wso2.carbon.identity.action.management.model.AuthProperty; +import org.wso2.carbon.identity.action.management.model.AuthType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +/** + * This class is responsible for executing the action based on the action type and the event context. + * It is responsible for building the request payload, calling the API, processing the response and + * returning the status of the action execution. + */ +public class ActionExecutorServiceImpl implements ActionExecutorService { + + private static final Log LOG = LogFactory.getLog(ActionExecutorServiceImpl.class); + + private static final ActionExecutorServiceImpl INSTANCE = new ActionExecutorServiceImpl(); + private final APIClient apiClient; + + private ActionExecutorServiceImpl() { + + apiClient = new APIClient(); + } + + public static ActionExecutorServiceImpl getInstance() { + + return INSTANCE; + } + + public ActionExecutionStatus execute(ActionType actionType, Map eventContext, String tenantDomain) + throws ActionExecutionException { + + try { + List actions = getActionsByActionType(actionType, tenantDomain); + validateActions(actions, actionType); + ActionExecutionRequest actionRequest = buildActionExecutionRequest(actionType, eventContext); + ActionExecutionResponseProcessor actionExecutionResponseProcessor = getResponseProcessor(actionType); + + Action action = actions.get(0); // As of now only one action is allowed. + return Optional.ofNullable(action) + .filter(activeAction -> activeAction.getStatus() == Action.Status.ACTIVE) + .map(activeAction -> executeAction(activeAction, actionRequest, eventContext, + actionExecutionResponseProcessor)) + .orElse(new ActionExecutionStatus(ActionExecutionStatus.Status.FAILED, eventContext)); + } catch (ActionExecutionRuntimeException e) { + // todo: add to diagnostics + LOG.error("Skip executing actions for action type: " + actionType.name() + ". Error: " + e.getMessage(), e); + return new ActionExecutionStatus(ActionExecutionStatus.Status.FAILED, eventContext); + + } + } + + private List getActionsByActionType(ActionType actionType, String tenantDomain) throws + ActionExecutionRuntimeException { + + try { + return ActionExecutionServiceComponentHolder.getInstance().getActionManagementService() + .getActionsByActionType(Action.ActionTypes.valueOf(actionType.name()).getPathParam(), tenantDomain); + } catch (ActionMgtException e) { + throw new ActionExecutionRuntimeException("Error occurred while retrieving actions.", e); + } + } + + private void validateActions(List actions, ActionType actionType) throws ActionExecutionException { + + if (CollectionUtils.isEmpty(actions)) { + if (LOG.isDebugEnabled()) { + LOG.debug("No actions found for action type: " + actionType); + } + return; + } + + if (actions.size() > 1) { + // when multiple actions are supported for an action type the logic below needs to be improved such that, + // a successful processing from one action becomes the input to the successor. + throw new ActionExecutionException("Multiple actions found for action type: " + actionType.name() + + ". Current implementation doesn't support multiple actions for a single action type."); + } + } + + private ActionExecutionRequest buildActionExecutionRequest(ActionType actionType, Map eventContext) + throws ActionExecutionException { + + ActionExecutionRequestBuilder requestBuilder = + ActionExecutionRequestBuilderFactory.getActionExecutionRequestBuilder(actionType); + if (requestBuilder == null) { + throw new ActionExecutionException("No request builder found for action type: " + actionType); + } + try { + return requestBuilder.buildActionExecutionRequest(eventContext); + } catch (ActionExecutionRequestBuilderException e) { + throw new ActionExecutionRuntimeException("Error occurred while building the request payload.", e); + } + } + + private ActionExecutionResponseProcessor getResponseProcessor(ActionType actionType) + throws ActionExecutionException { + + ActionExecutionResponseProcessor responseProcessor = + ActionExecutionResponseProcessorFactory.getActionExecutionResponseProcessor(actionType); + if (responseProcessor == null) { + throw new ActionExecutionException("No response processor found for action type: " + actionType); + } + return responseProcessor; + } + + private ActionExecutionStatus executeAction(Action action, + ActionExecutionRequest actionRequest, + Map eventContext, + ActionExecutionResponseProcessor actionExecutionResponseProcessor) + throws ActionExecutionRuntimeException { + + AuthType endpointAuthentication = action.getEndpoint().getAuthentication(); + AuthMethods.AuthMethod authenticationMethod; + + try { + authenticationMethod = getAuthenticationMethod(action.getId(), endpointAuthentication); + String payload = serializeRequest(actionRequest); + + logActionRequest(action, payload); + + ActionInvocationResponse actionInvocationResponse = + executeActionAsynchronously(action, authenticationMethod, payload); + return processActionResponse(action, actionInvocationResponse, eventContext, actionRequest, + actionExecutionResponseProcessor); + } catch (ActionMgtException | JsonProcessingException | ActionExecutionResponseProcessorException e) { + throw new ActionExecutionRuntimeException("Error occurred while executing action: " + action.getId(), e); + } + } + + private ActionInvocationResponse executeActionAsynchronously(Action action, + AuthMethods.AuthMethod authenticationMethod, + String payload) { + + String apiEndpoint = action.getEndpoint().getUri(); + CompletableFuture actionExecutor = CompletableFuture.supplyAsync( + () -> apiClient.callAPI(apiEndpoint, authenticationMethod, payload)); + try { + return actionExecutor.get(); + } catch (InterruptedException | ExecutionException e) { + throw new ActionExecutionRuntimeException("Error occurred while executing action: " + action.getId(), + e); + } + } + + private void logActionRequest(Action action, String payload) { + + //todo: Add to diagnostics + if (LOG.isDebugEnabled()) { + LOG.debug(String.format( + "Calling API: %s for action type: %s action id: %s with authentication: %s payload: %s", + action.getEndpoint().getUri(), + action.getType().getActionType(), + action.getId(), + action.getEndpoint().getAuthentication(), + payload)); + } + } + + private ActionExecutionStatus processActionResponse(Action action, + ActionInvocationResponse actionInvocationResponse, + Map eventContext, + ActionExecutionRequest actionRequest, + ActionExecutionResponseProcessor + actionExecutionResponseProcessor) + throws ActionExecutionResponseProcessorException { + + if (actionInvocationResponse.isSuccess()) { + return processSuccessResponse(action, + (ActionInvocationSuccessResponse) actionInvocationResponse.getResponse(), + eventContext, actionRequest, actionExecutionResponseProcessor); + } else if (actionInvocationResponse.isError() && actionInvocationResponse.getResponse() != null) { + return processErrorResponse(action, (ActionInvocationErrorResponse) actionInvocationResponse.getResponse(), + eventContext, actionRequest, actionExecutionResponseProcessor); + } else { + logErrorResponse(action, actionInvocationResponse); + } + + return new ActionExecutionStatus(ActionExecutionStatus.Status.FAILED, eventContext); + } + + private ActionExecutionStatus processSuccessResponse(Action action, + ActionInvocationSuccessResponse successResponse, + Map eventContext, + ActionExecutionRequest actionRequest, + ActionExecutionResponseProcessor + actionExecutionResponseProcessor) + throws ActionExecutionResponseProcessorException { + + if (LOG.isDebugEnabled()) { + // todo: add to diagnostic logs + logSuccessResponse(action, successResponse); + } + + List allowedPerformableOperations = + validatePerformableOperations(actionRequest, successResponse); + ActionInvocationSuccessResponse.Builder successResponseBuilder = + new ActionInvocationSuccessResponse.Builder().operations(allowedPerformableOperations); + return actionExecutionResponseProcessor.processSuccessResponse(eventContext, + actionRequest.getEvent(), successResponseBuilder.build()); + } + + private ActionExecutionStatus processErrorResponse(Action action, + ActionInvocationErrorResponse errorResponse, + Map eventContext, + ActionExecutionRequest actionRequest, + ActionExecutionResponseProcessor + actionExecutionResponseProcessor) + throws ActionExecutionResponseProcessorException { + + logErrorResponse(action, errorResponse); + return actionExecutionResponseProcessor.processErrorResponse(eventContext, actionRequest.getEvent(), + errorResponse); + } + + private void logSuccessResponse(Action action, ActionInvocationSuccessResponse successResponse) { + + try { + String responseBody = serializeSuccessResponse(successResponse); + LOG.debug(String.format( + "Received success response from API: %s for action type: %s action id: %s with authentication: %s. " + + "Response: %s", + action.getEndpoint().getUri(), + action.getType().getActionType(), + action.getId(), + action.getEndpoint().getAuthentication().getType(), + responseBody)); + } catch (JsonProcessingException e) { + LOG.error("Error occurred while deserializing the success response for action: " + + action.getId() + " for action type: " + action.getType().getActionType(), e); + } + } + + private void logErrorResponse(Action action, ActionInvocationErrorResponse errorResponse) { + + if (LOG.isDebugEnabled()) { + // todo: add to diagnostic logs + try { + String responseBody = serializeErrorResponse(errorResponse); + LOG.debug(String.format( + "Received error response from API: %s for action type: %s action id: %s with " + + "authentication: %s. Response: %s", + action.getEndpoint().getUri(), + action.getType().getActionType(), + action.getId(), + action.getEndpoint().getAuthentication().getType(), + responseBody)); + } catch (JsonProcessingException e) { + LOG.debug("Error occurred while deserializing the error response for action: " + + action.getId() + " for action type: " + action.getType().getActionType(), e); + } + } + } + + private void logErrorResponse(Action action, ActionInvocationResponse actionInvocationResponse) { + // todo: add to diagnostic logs + if (LOG.isDebugEnabled()) { + LOG.debug(String.format( + "Failed to call API: %s for action type: %s action id: %s with authentication: %s. Error: %s", + action.getEndpoint().getUri(), + action.getType().getActionType(), + action.getId(), + action.getEndpoint().getAuthentication(), + actionInvocationResponse.getErrorLog() != null ? actionInvocationResponse.getErrorLog() : + "Unknown")); + } + } + + private String serializeRequest(ActionExecutionRequest request) throws JsonProcessingException { + + ObjectMapper requestObjectmapper = new ObjectMapper(); + requestObjectmapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + requestObjectmapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + return requestObjectmapper.writeValueAsString(request); + } + + private String serializeSuccessResponse(ActionInvocationSuccessResponse response) throws JsonProcessingException { + + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.writeValueAsString(response); + } + + private String serializeErrorResponse(ActionInvocationErrorResponse response) throws JsonProcessingException { + + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.writeValueAsString(response); + } + + private List validatePerformableOperations(ActionExecutionRequest request, + ActionInvocationSuccessResponse response) { + + List allowedOperations = request.getAllowedOperations(); + + List allowedPerformableOperations = response.getOperations().stream() + .filter(performableOperation -> allowedOperations.stream() + .anyMatch(allowedOperation -> OperationComparator.compare(allowedOperation, + performableOperation))) + .collect(Collectors.toList()); + + if (LOG.isDebugEnabled()) { + // todo: add to diagnostics + List allowedOps = new ArrayList<>(); + List notAllowedOps = new ArrayList<>(); + + response.getOperations().forEach(operation -> { + String operationDetails = "Operation: " + operation.getOp() + " with path: " + operation.getPath(); + if (allowedPerformableOperations.contains(operation)) { + allowedOps.add(operationDetails); + } else { + notAllowedOps.add(operationDetails); + } + }); + LOG.debug("Allowed Operations: " + String.join(", ", allowedOps) + + ". Not Allowed Operations: " + String.join(", ", notAllowedOps)); + } + + return allowedPerformableOperations; + } + + private AuthMethods.AuthMethod getAuthenticationMethod(String actionId, AuthType authType) + throws ActionMgtException { + + List authProperties = authType.getPropertiesWithDecryptedValues(actionId); + + switch (authType.getType()) { + case BASIC: + return new AuthMethods.BasicAuth(authProperties); + case BEARER: + return new AuthMethods.BearerAuth(authProperties); + case API_KEY: + return new AuthMethods.APIKeyAuth(authProperties); + case NONE: + return null; + default: + throw new ActionMgtException("Unsupported authentication type: " + authType.getType()); + + } + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionExecutionException.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionExecutionException.java new file mode 100644 index 00000000000..01830f76856 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionExecutionException.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.exception; + +/** + * Exception class for Action Execution. + * This exception is thrown when there is an issue in executing the action. + */ +public class ActionExecutionException extends Exception { + + public ActionExecutionException(String message) { + + super(message); + } + + public ActionExecutionException(String message, Throwable cause) { + + super(message, cause); + } + +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionExecutionRequestBuilderException.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionExecutionRequestBuilderException.java new file mode 100644 index 00000000000..1e8635a3558 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionExecutionRequestBuilderException.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.exception; + +/** + * Exception class for Action Execution Request Builder. + * This exception is thrown when there is an issue in building the Action Execution Request. + */ +public class ActionExecutionRequestBuilderException extends Exception { + + public ActionExecutionRequestBuilderException(String message) { + + super(message); + } + + public ActionExecutionRequestBuilderException(String message, Throwable cause) { + + super(message, cause); + } + +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionExecutionResponseProcessorException.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionExecutionResponseProcessorException.java new file mode 100644 index 00000000000..57d6780b649 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionExecutionResponseProcessorException.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.exception; + +/** + * Exception class for Action Execution Response Processor. + * This exception is thrown when there is an issue in processing the Action Execution Response. + */ +public class ActionExecutionResponseProcessorException extends Exception { + + public ActionExecutionResponseProcessorException(String message) { + + super(message); + } + + public ActionExecutionResponseProcessorException(String message, Throwable cause) { + + super(message, cause); + } + +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionExecutionRuntimeException.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionExecutionRuntimeException.java new file mode 100644 index 00000000000..a4142c9e20a --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionExecutionRuntimeException.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.exception; + +/** + * Runtime Exception class for Action Execution. + * This exception is thrown when there is an issue in executing the action, + * that doesn't need to handled by the consuming party of the ActionExecutionService, + * yet be logged for troubleshooting requirements. + */ +public class ActionExecutionRuntimeException extends RuntimeException { + + public ActionExecutionRuntimeException(String message) { + + super(message); + } + + public ActionExecutionRuntimeException(String message, Throwable cause) { + + super(message, cause); + } + +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionInvocationException.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionInvocationException.java new file mode 100644 index 00000000000..3a40a165e03 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/exception/ActionInvocationException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.exception; + +/** + * Exception class for Action Invocation. + * This exception is thrown when there is an issue in invoking API associated with the action. + */ +public class ActionInvocationException extends Exception { + + public ActionInvocationException(String message) { + + super(message); + } + + public ActionInvocationException(String message, Throwable cause) { + + super(message, cause); + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/internal/ActionExecutionServiceComponent.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/internal/ActionExecutionServiceComponent.java new file mode 100644 index 00000000000..3615860846b --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/internal/ActionExecutionServiceComponent.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.wso2.carbon.identity.action.execution.ActionExecutionRequestBuilder; +import org.wso2.carbon.identity.action.execution.ActionExecutionRequestBuilderFactory; +import org.wso2.carbon.identity.action.execution.ActionExecutionResponseProcessor; +import org.wso2.carbon.identity.action.execution.ActionExecutionResponseProcessorFactory; +import org.wso2.carbon.identity.action.execution.ActionExecutorService; +import org.wso2.carbon.identity.action.execution.ActionExecutorServiceImpl; +import org.wso2.carbon.identity.action.management.ActionManagementService; + +/** + * OSGI service component for the Action execution. + */ +@Component( + name = "action.execution.service.component", + immediate = true +) +public class ActionExecutionServiceComponent { + + private static final Log LOG = LogFactory.getLog(ActionExecutionServiceComponent.class); + + @Activate + protected void activate(ComponentContext context) { + + try { + BundleContext bundleCtx = context.getBundleContext(); + bundleCtx.registerService(ActionExecutorService.class.getName(), ActionExecutorServiceImpl.getInstance(), + null); + LOG.debug("Action execution bundle is activated."); + } catch (Throwable e) { + LOG.error("Error while initializing Action execution service component.", e); + } + } + + @Deactivate + protected void deactivate(ComponentContext context) { + + try { + BundleContext bundleCtx = context.getBundleContext(); + bundleCtx.ungetService(bundleCtx.getServiceReference(ActionExecutorService.class)); + LOG.debug("Action execution bundle is deactivated."); + } catch (Throwable e) { + LOG.error("Error while deactivating Action execution service component.", e); + } + } + + @Reference( + name = "action.management.service", + service = ActionManagementService.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetActionManagementService" + ) + protected void setActionManagementService(ActionManagementService actionManagementService) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Registering a reference for ActionManagementService in the ActionExecutionServiceComponent."); + } + ActionExecutionServiceComponentHolder.getInstance().setActionManagementService(actionManagementService); + } + + protected void unsetActionManagementService(ActionManagementService actionManagementService) { + + if (LOG.isDebugEnabled()) { + LOG.debug( + "Unregistering the reference for ActionManagementService in the ActionExecutionServiceComponent."); + } + if (ActionExecutionServiceComponentHolder.getInstance().getActionManagementService() + .equals(actionManagementService)) { + ActionExecutionServiceComponentHolder.getInstance().setActionManagementService(null); + } + } + + @Reference( + name = "action.execution.request.builder", + service = ActionExecutionRequestBuilder.class, + cardinality = ReferenceCardinality.MULTIPLE, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetActionExecutionRequestBuilder" + ) + protected void setActionExecutionRequestBuilder(ActionExecutionRequestBuilder actionExecutionRequestBuilder) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Registering ActionExecutionRequestBuilder: " + + actionExecutionRequestBuilder.getClass().getName() + + " in the ActionExecutionServiceComponent."); + } + ActionExecutionRequestBuilderFactory.registerActionExecutionRequestBuilder(actionExecutionRequestBuilder); + } + + protected void unsetActionExecutionRequestBuilder(ActionExecutionRequestBuilder actionExecutionRequestBuilder) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Unregistering ActionExecutionRequestBuilder: " + + actionExecutionRequestBuilder.getClass().getName() + " in the ActionExecutionServiceComponent."); + } + ActionExecutionRequestBuilderFactory.unregisterActionExecutionRequestBuilder(actionExecutionRequestBuilder); + } + + @Reference( + name = "action.execution.response.processor", + service = ActionExecutionResponseProcessor.class, + cardinality = ReferenceCardinality.MULTIPLE, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetActionExecutionResponseProcessor" + ) + protected void setActionExecutionResponseProcessor( + ActionExecutionResponseProcessor actionExecutionResponseProcessor) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Registering ActionExecutionResponseProcessor: " + + actionExecutionResponseProcessor.getClass().getName() + + " in the ActionExecutionServiceComponent."); + } + ActionExecutionResponseProcessorFactory.registerActionExecutionResponseProcessor( + actionExecutionResponseProcessor); + } + + protected void unsetActionExecutionResponseProcessor( + ActionExecutionResponseProcessor actionExecutionResponseProcessor) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Unregistering ActionExecutionResponseProcessor: " + + actionExecutionResponseProcessor.getClass().getName() + " in the ActionExecutionServiceComponent."); + } + ActionExecutionResponseProcessorFactory.unregisterActionExecutionResponseProcessor( + actionExecutionResponseProcessor); + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/internal/ActionExecutionServiceComponentHolder.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/internal/ActionExecutionServiceComponentHolder.java new file mode 100644 index 00000000000..83be753ef41 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/internal/ActionExecutionServiceComponentHolder.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.internal; + +import org.wso2.carbon.identity.action.management.ActionManagementService; + +/** + * This class holds references for dependent services required for Action Execution Service to function. + */ +public class ActionExecutionServiceComponentHolder { + + private static final ActionExecutionServiceComponentHolder INSTANCE = new ActionExecutionServiceComponentHolder(); + + private ActionManagementService actionManagementService; + + private ActionExecutionServiceComponentHolder() { + + } + + public static ActionExecutionServiceComponentHolder getInstance() { + + return INSTANCE; + } + + public ActionManagementService getActionManagementService() { + + return actionManagementService; + } + + public void setActionManagementService(ActionManagementService actionManagementService) { + + this.actionManagementService = actionManagementService; + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionExecutionRequest.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionExecutionRequest.java new file mode 100644 index 00000000000..04959234c63 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionExecutionRequest.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +import org.slf4j.MDC; + +import java.util.List; +import java.util.Optional; + +/** + * This class models the Action Execution Request. + * Action Execution Request is the request object that is passed to the Action Executor Service to execute an action. + * It contains the {@link ActionType}, flow id, {@link Event} and a list of {@link AllowedOperation}. + */ +public class ActionExecutionRequest { + + private static final String CORRELATION_ID_MDC = "Correlation-ID"; + private final ActionType actionType; + private final String flowId; + private final Event event; + private final List allowedOperations; + + public ActionExecutionRequest(Builder builder) { + + this.actionType = builder.actionType; + this.flowId = builder.flowId; + this.event = builder.event; + this.allowedOperations = builder.allowedOperations; + } + + public ActionType getActionType() { + + return actionType; + } + + public String getFlowId() { + + return flowId; + } + + public String getRequestId() { + + return getCorrelationId(); + } + + public Event getEvent() { + + return event; + } + + public List getAllowedOperations() { + + return allowedOperations; + } + + private String getCorrelationId() { + + return Optional.ofNullable(MDC.get(CORRELATION_ID_MDC)).orElse(""); + } + + /** + * Builder for the {@link ActionExecutionRequest}. + */ + public static class Builder { + + private ActionType actionType; + private String flowId; + private Event event; + private List allowedOperations; + + public Builder actionType(ActionType actionType) { + + this.actionType = actionType; + return this; + } + + public Builder flowId(String flowId) { + + this.flowId = flowId; + return this; + } + + public Builder event(Event event) { + + this.event = event; + return this; + } + + public Builder allowedOperations(List allowedOperations) { + + this.allowedOperations = allowedOperations; + return this; + } + + public ActionExecutionRequest build() { + + return new ActionExecutionRequest(this); + } + } +} + diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionExecutionStatus.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionExecutionStatus.java new file mode 100644 index 00000000000..c659dfa66dc --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionExecutionStatus.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +import java.util.Map; + +/** + * This class models the Action Execution Status. + * Action Execution Status is the status object that is returned by the Action Executor Service after executing an + * action. It contains the status of the action execution and the response context. + */ +public class ActionExecutionStatus { + + private final Status status; + private final Map responseContext; + + public ActionExecutionStatus(Status status, Map responseContext) { + + this.status = status; + this.responseContext = responseContext; + } + + public Status getStatus() { + + return status; + } + + public Map getResponseContext() { + + return responseContext; + } + + /** + * This enum defines the Action Execution Status. + */ + public enum Status { + SUCCESS, + FAILED, + INCOMPLETE, + ERROR + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationErrorResponse.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationErrorResponse.java new file mode 100644 index 00000000000..c2cb471e0b0 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationErrorResponse.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +/** + * This class is used to represent the error response of an action invocation. + */ +@JsonDeserialize(builder = ActionInvocationErrorResponse.Builder.class) +public class ActionInvocationErrorResponse implements ActionInvocationResponse.APIResponse { + + private final ActionInvocationResponse.Status actionStatus; + private final String error; + private final String errorDescription; + + private ActionInvocationErrorResponse(Builder builder) { + + this.actionStatus = builder.actionStatus; + this.error = builder.error; + this.errorDescription = builder.errorDescription; + } + + public ActionInvocationResponse.Status getActionStatus() { + + return actionStatus; + } + + public String getError() { + + return error; + } + + public String getErrorDescription() { + + return errorDescription; + } + + /** + * This class is used to build the {@link ActionInvocationErrorResponse}. + */ + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + + private ActionInvocationResponse.Status actionStatus; + private String error; + private String errorDescription; + + @JsonProperty("actionStatus") + public Builder actionStatus(ActionInvocationResponse.Status actionStatus) { + + if (!ActionInvocationResponse.Status.ERROR.equals(actionStatus)) { + throw new IllegalArgumentException("actionStatus must be ERROR"); + } + this.actionStatus = actionStatus; + return this; + } + + @JsonProperty("error") + public Builder error(String error) { + + this.error = error; + return this; + } + + @JsonProperty("errorDescription") + public Builder errorDescription(String errorDescription) { + + this.errorDescription = errorDescription; + return this; + } + + public ActionInvocationErrorResponse build() { + + return new ActionInvocationErrorResponse(this); + } + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationResponse.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationResponse.java new file mode 100644 index 00000000000..03cc7dab366 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationResponse.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +/** + * This class is used to represent the response of an action invocation. + * The response can be either a success or an error. + */ +public class ActionInvocationResponse { + + private Status actionStatus; + private APIResponse response; + + private boolean retry; + + private String errorLog; + + private ActionInvocationResponse() { + + } + + public APIResponse getResponse() { + + return response; + } + + public boolean isSuccess() { + + return Status.SUCCESS.equals(actionStatus); + } + + public boolean isError() { + + return Status.ERROR.equals(actionStatus); + } + + public boolean isRetry() { + + return retry; + } + + public String getErrorLog() { + + return errorLog; + } + + /** + * Defines action invocation status. + */ + public enum Status { + SUCCESS, + ERROR + } + + /** + * This interface defines the response of the API call. + */ + public interface APIResponse { + + Status getActionStatus(); + + } + + /** + * This class is used to build the {@link ActionInvocationResponse}. + */ + public static class Builder { + + private Status actionStatus; + private APIResponse response; + private boolean retry; + + private String errorLog; + + public Builder response(APIResponse response) { + + this.actionStatus = response.getActionStatus(); + this.response = response; + return this; + } + + public Builder retry(boolean retry) { + + this.retry = retry; + this.actionStatus = Status.ERROR; + return this; + } + + public Builder errorLog(String errorLog) { + + this.errorLog = errorLog; + this.actionStatus = Status.ERROR; + return this; + } + + public ActionInvocationResponse build() { + + ActionInvocationResponse response = new ActionInvocationResponse(); + response.actionStatus = this.actionStatus; + response.response = this.response; + response.retry = this.retry; + response.errorLog = this.errorLog; + return response; + } + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationSuccessResponse.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationSuccessResponse.java new file mode 100644 index 00000000000..95e5df391a1 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationSuccessResponse.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +import java.util.List; + +/** + * This class is used to represent the success response of an action invocation. + * This response will contain the list of operations that need to be performed. + */ +@JsonDeserialize(builder = ActionInvocationSuccessResponse.Builder.class) +public class ActionInvocationSuccessResponse implements ActionInvocationResponse.APIResponse { + + private final ActionInvocationResponse.Status actionStatus; + + private final List operations; + + private ActionInvocationSuccessResponse(Builder builder) { + + this.actionStatus = builder.actionStatus; + this.operations = builder.operations; + } + + @Override + public ActionInvocationResponse.Status getActionStatus() { + + return actionStatus; + } + + public List getOperations() { + + return operations; + } + + /** + * This class is used to build the {@link ActionInvocationSuccessResponse}. + */ + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + + private ActionInvocationResponse.Status actionStatus; + private List operations; + + @JsonProperty("actionStatus") + public Builder actionStatus(String actionStatus) { + + if (!ActionInvocationResponse.Status.SUCCESS.name().equals(actionStatus)) { + throw new IllegalArgumentException("actionStatus must be SUCCESS"); + } + this.actionStatus = ActionInvocationResponse.Status.SUCCESS; + return this; + } + + @JsonProperty("operations") + public Builder operations(@JsonProperty("operations") List operations) { + + this.operations = operations; + return this; + } + + public ActionInvocationSuccessResponse build() { + + return new ActionInvocationSuccessResponse(this); + } + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionType.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionType.java new file mode 100644 index 00000000000..5b1c1df4e0b --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionType.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +/** + * This class models the Action Type. + * Action Type is the type of the action that is executed by the Action Executor Service. + */ +public enum ActionType { + PRE_ISSUE_ACCESS_TOKEN, +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/AllowedOperation.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/AllowedOperation.java new file mode 100644 index 00000000000..eaa412242d1 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/AllowedOperation.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +import java.util.List; + +/** + * This class models the Allowed Operation. + * Allowed Operation is the operation that is allowed to be performed during the execution of a particular action. + * The operation is defined by the operation type and the paths that the operation is allowed to be performed on. + * Allowed operations follow JSON Patch format (RFC 6902) semantics to define the operations that are allowed to be + * performed. + */ +public class AllowedOperation { + + private Operation op; + + private List paths; + + public Operation getOp() { + + return op; + } + + public void setOp(Operation op) { + + this.op = op; + } + + public List getPaths() { + + return paths; + } + + public void setPaths(List paths) { + + this.paths = paths; + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Application.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Application.java new file mode 100644 index 00000000000..9019e08b524 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Application.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +/** + * This class models the Application. + * Application is the entity that represents the application that the action is triggered for. + */ +public class Application { + + private final String id; + private final String name; + + public Application(String id, String name) { + + this.id = id; + this.name = name; + } + + public String getId() { + + return id; + } + + public String getName() { + + return name; + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Event.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Event.java new file mode 100644 index 00000000000..cbb5a14ccbb --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Event.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +/** + * This class models the Event. + * Event is the entity that represents the event that is sent to the Action over Action Execution Request. + * It contains the request, tenant, organization, user, and user store information. + * The abstraction allows to model events with additional context based on the action type. + */ +public abstract class Event { + + protected Request request; + protected Tenant tenant; + protected Organization organization; + protected User user; + protected UserStore userStore; + + public Tenant getTenant() { + + return tenant; + } + + public Organization getOrganization() { + + return organization; + } + + public Request getRequest() { + + return request; + } + + public User getUser() { + + return user; + } + + public UserStore getUserStore() { + + return userStore; + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Operation.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Operation.java new file mode 100644 index 00000000000..65414f71706 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Operation.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * This class models the Operation types allowed. + * Operation is defined by the operation type and the paths that the operation is performed on. + * Operations follow JSON Patch format (RFC 6902) semantics to define the operations that are performed. + */ +public enum Operation { + ADD("add"), + REMOVE("remove"), + REPLACE("replace"); + + private final String value; + + Operation(String value) { + + this.value = value; + } + + @JsonValue + public String getValue() { + + return value; + } + + @JsonCreator + public static Operation forValue(String value) { + + for (Operation op : Operation.values()) { + if (op.getValue().equals(value)) { + return op; + } + } + throw new IllegalArgumentException("Invalid operation value: " + value); + } +} + diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Organization.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Organization.java new file mode 100644 index 00000000000..c6f64224210 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Organization.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +/** + * This class models the Organization. + * Organization is the entity that represents the organization of the user for whom the action is triggered for. + */ +public class Organization { + + private final String id; + + private final String name; + + public Organization(String id, String name) { + + this.id = id; + this.name = name; + } + + public String getId() { + + return id; + } + + public String getName() { + + return name; + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/PerformableOperation.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/PerformableOperation.java new file mode 100644 index 00000000000..d07765ccc0b --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/PerformableOperation.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +/** + * This class models the Performable Operation. + * Performable Operation is the operation that is requested to be performed in the system during the execution of an + * action. It contains the operation type, the path of the operation and the value associated with add or replace + * operations. + * Performable operations are expected to follow the JSON Patch format (RFC 6902), providing a standardized way to + * express the operations to be performed. + */ +public class PerformableOperation { + + private Operation op; + private String path; + private Object value; + + public Operation getOp() { + + return op; + } + + public void setOp(Operation op) { + + this.op = op; + } + + public String getPath() { + + return path; + } + + public void setPath(String path) { + + this.path = path; + } + + public Object getValue() { + + return value; + } + + public void setValue(Object value) { + + this.value = value; + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Request.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Request.java new file mode 100644 index 00000000000..8998afaf417 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Request.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * This class models the Request. + * Request is the entity that represents the request that is sent to Action over Action Execution Request. + * Request contains additional headers and additional parameters relevant to the trigger that are sent to the Action. + * The abstraction allows to model requests with additional context based on the action type. + */ +public abstract class Request { + + protected Map additionalHeaders = new HashMap<>(); + protected Map additionalParams = new HashMap<>(); + + public Map getAdditionalHeaders() { + + return additionalHeaders != null ? additionalHeaders : Collections.emptyMap(); + } + + public Map getAdditionalParams() { + + return additionalParams != null ? additionalParams : Collections.emptyMap(); + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Tenant.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Tenant.java new file mode 100644 index 00000000000..24c275c06f5 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Tenant.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +/** + * This class models the Tenant. + * Tenant is the entity that represents the tenant of the action trigger. + */ +public class Tenant { + + private final String id; + private final String name; + + public Tenant(String id, String name) { + + this.id = id; + this.name = name; + } + + public String getId() { + + return id; + } + + public String getName() { + + return name; + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/User.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/User.java new file mode 100644 index 00000000000..4a168c43973 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/User.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +/** + * This class models the User. + * User is the entity that represents the user for whom the action is triggered for. + */ +public class User { + + private String id; + + public User(String id) { + + this.id = id; + } + + public String getId() { + + return id; + } + public void setId(String id) { + + this.id = id; + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/UserStore.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/UserStore.java new file mode 100644 index 00000000000..0753c072d35 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/UserStore.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.model; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * This class models the UserStore. + * UserStore is the entity that represents the user store of the user for whom the action is triggered for. + * User store is present when the user's profile is managed within the system only. + */ +public class UserStore { + + private String id; + private String name; + + public UserStore(String name) { + + setName(name); + } + + public String getId() { + + return id; + } + + public String getName() { + + return name; + } + + public void setName(String name) { + + /* + * As of now user store id is generated by encoding the user store name. + */ + this.id = name != null ? Base64.getEncoder().encodeToString(name.getBytes(StandardCharsets.UTF_8)) : null; + this.name = name; + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/util/APIClient.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/util/APIClient.java new file mode 100644 index 00000000000..f5fa137795b --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/util/APIClient.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.util.EntityUtils; +import org.wso2.carbon.identity.action.execution.exception.ActionInvocationException; +import org.wso2.carbon.identity.action.execution.model.ActionInvocationErrorResponse; +import org.wso2.carbon.identity.action.execution.model.ActionInvocationResponse; +import org.wso2.carbon.identity.action.execution.model.ActionInvocationSuccessResponse; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +/** + * This class is responsible for making API calls to the external services. + */ +public class APIClient { + + private static final Log LOG = LogFactory.getLog(APIClient.class); + private final CloseableHttpClient httpClient; + + public APIClient() { + + // todo: read connection configurations related to the http client of actions from the server configuration. + // Initialize the http client. Set connection time out to 2s and read time out to 5s. + int readTimeout = 5000; + int connectionRequestTimeout = 2000; + int connectionTimeout = 2000; + + RequestConfig config = RequestConfig.custom() + .setConnectTimeout(connectionTimeout) + .setConnectionRequestTimeout(connectionRequestTimeout) + .setSocketTimeout(readTimeout) + .setRedirectsEnabled(false) + .setRelativeRedirectsAllowed(false) + .build(); + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(20); + httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).setConnectionManager(connectionManager) + .build(); + } + + public ActionInvocationResponse callAPI(String url, AuthMethods.AuthMethod authMethod, + String payload) { + + HttpPost httpPost = new HttpPost(url); + setRequestEntity(httpPost, payload, authMethod); + + return executeRequest(httpPost).orElse(new ActionInvocationResponse.Builder() + .errorLog("Failed to execute the action request or maximum retry attempts reached.") + .build()); + } + + private void setRequestEntity(HttpPost httpPost, String jsonRequest, AuthMethods.AuthMethod authMethod) { + + StringEntity entity = new StringEntity(jsonRequest, StandardCharsets.UTF_8); + if (authMethod != null) { + authMethod.applyAuth(httpPost); + } + httpPost.setEntity(entity); + httpPost.setHeader("Accept", "application/json"); + httpPost.setHeader("Content-type", "application/json"); + } + + private Optional executeRequest(HttpPost request) { + + int attempts = 0; + int retryCount = 2; // todo: read from server configurations + + while (attempts < retryCount) { + try (CloseableHttpResponse response = httpClient.execute(request)) { + ActionInvocationResponse actionInvocationResponse = handleResponse(response); + if (!actionInvocationResponse.isError() || !actionInvocationResponse.isRetry()) { + return Optional.of(actionInvocationResponse); + } + //todo: add to diagnostic logs + LOG.warn("API: " + request.getURI() + " seems to be unavailable. Retrying the request. Attempt " + + (attempts + 1) + " of " + retryCount); + } catch (ConnectTimeoutException | SocketTimeoutException e) { + //todo: add to diagnostic logs + LOG.warn("Request for API: " + request.getURI() + " timed out. Retrying the request. Attempt " + + (attempts + 1) + " of " + retryCount); + } catch (Exception e) { + //todo: add to diagnostic logs + LOG.error("Request for API: " + request.getURI() + " failed due to an error.", e); + break; + } finally { + request.releaseConnection(); + } + attempts++; + } + + LOG.warn("Maximum retry attempts reached for API: " + request.getURI()); + return Optional.empty(); + } + + private ActionInvocationResponse handleResponse(HttpResponse response) { + + int statusCode = response.getStatusLine().getStatusCode(); + HttpEntity responseEntity = response.getEntity(); + + ActionInvocationResponse.Builder actionInvocationResponseBuilder = new ActionInvocationResponse.Builder(); + + try { + switch (statusCode) { + case HttpStatus.SC_OK: + ActionInvocationSuccessResponse successResponse = handleSuccessResponse(responseEntity); + actionInvocationResponseBuilder.response(successResponse); + break; + case HttpStatus.SC_BAD_REQUEST: + case HttpStatus.SC_INTERNAL_SERVER_ERROR: + ActionInvocationErrorResponse errorResponse = handleErrorResponse(responseEntity); + actionInvocationResponseBuilder.response(errorResponse); + break; + case HttpStatus.SC_UNAUTHORIZED: + break; + case HttpStatus.SC_BAD_GATEWAY: + case HttpStatus.SC_SERVICE_UNAVAILABLE: + case HttpStatus.SC_GATEWAY_TIMEOUT: + actionInvocationResponseBuilder.retry(true); + break; + default: + throw new ActionInvocationException("Unexpected response status code: " + statusCode); + } + } catch (ActionInvocationException e) { + // Set error in response to be logged at diagnostic logs for troubleshooting. + actionInvocationResponseBuilder.errorLog("Unexpected response. Error: " + e.getMessage()); + } + + return actionInvocationResponseBuilder.build(); + } + + private ActionInvocationSuccessResponse handleSuccessResponse(HttpEntity responseEntity) + throws ActionInvocationException { + + return deserialize(responseEntity, ActionInvocationSuccessResponse.class); + } + + private ActionInvocationErrorResponse handleErrorResponse(HttpEntity responseEntity) + throws ActionInvocationException { + + // If an error response is received, return the error response in order to communicate back to the client. + if (isAcceptablePayload(responseEntity)) { + return deserialize(responseEntity, ActionInvocationErrorResponse.class); + } + return null; + } + + private T deserialize(HttpEntity responseEntity, Class returnType) throws ActionInvocationException { + + if (!isAcceptablePayload(responseEntity)) { + throw new ActionInvocationException("The response content type is not application/json."); + } + + ObjectMapper objectMapper = new ObjectMapper(); + try { + String jsonResponse = EntityUtils.toString(responseEntity); + return objectMapper.readValue(jsonResponse, returnType); + } catch (IOException e) { + throw new ActionInvocationException("Error parsing the JSON response.", e); + } + } + + private boolean isAcceptablePayload(HttpEntity responseEntity) { + + return responseEntity != null && responseEntity.getContentType() != null && + responseEntity.getContentType().getValue().contains("application/json"); + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/util/AuthMethods.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/util/AuthMethods.java new file mode 100644 index 00000000000..c6bb1ab3331 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/util/AuthMethods.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.util; + +import org.apache.http.client.methods.HttpPost; +import org.wso2.carbon.identity.action.management.model.AuthProperty; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; + +/** + * This class contains the authentication methods. + * The authentication methods are used to authenticate the HTTP requests. + */ +public final class AuthMethods { + + private AuthMethods() { + + } + + /** + * This interface defines how authentication methods are applied to http request. + */ + public interface AuthMethod { + + void applyAuth(HttpPost httpPost); + + String getAuthType(); + } + + /** + * This class applies bearer authentication to the http request. + */ + public static final class BearerAuth implements AuthMethod { + + private String token; + + public BearerAuth(List authPropertyList) { + + authPropertyList.stream() + .filter(authProperty -> "ACCESS_TOKEN".equals(authProperty.getName())) + .findFirst() + .ifPresent(authProperty -> this.token = authProperty.getValue()); + } + + @Override + public void applyAuth(HttpPost httpPost) { + + httpPost.setHeader("Authorization", "Bearer " + token); + } + + @Override + public String getAuthType() { + + return "BEARER"; + } + } + + /** + * This class applies basic authentication to the http request. + */ + public static final class BasicAuth implements AuthMethod { + + private String username; + private String password; + + public BasicAuth(List authPropertyList) { + + authPropertyList.forEach(authProperty -> { + switch (authProperty.getName()) { + case "USERNAME": + this.username = authProperty.getValue(); + break; + case "PASSWORD": + this.password = authProperty.getValue(); + break; + default: + break; + } + }); + } + + @Override + public void applyAuth(HttpPost httpPost) { + + String auth = username + ":" + password; + byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8)); + String authHeader = "Basic " + new String(encodedAuth, StandardCharsets.UTF_8); + httpPost.setHeader("Authorization", authHeader); + } + + @Override + public String getAuthType() { + + return "BASIC"; + } + } + + /** + * This class applies API Key based authentication to the http request. + */ + public static final class APIKeyAuth implements AuthMethod { + + private String apiHeader; + private String apiKey; + + public APIKeyAuth(List authPropertyList) { + + authPropertyList.forEach(authProperty -> { + switch (authProperty.getName()) { + case "HEADER": + this.apiHeader = authProperty.getValue(); + break; + case "VALUE": + this.apiKey = authProperty.getValue(); + break; + default: + break; + } + }); + } + + @Override + public void applyAuth(HttpPost httpPost) { + + httpPost.setHeader(apiHeader, apiKey); + } + + @Override + public String getAuthType() { + + return "API-KEY"; + } + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/util/OperationComparator.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/util/OperationComparator.java new file mode 100644 index 00000000000..ecb9cae0d4f --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/util/OperationComparator.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. 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.wso2.carbon.identity.action.execution.util; + +import org.wso2.carbon.identity.action.execution.model.AllowedOperation; +import org.wso2.carbon.identity.action.execution.model.PerformableOperation; + +/** + * This class compares an allowed operation against a performable operation to determine if the latter is permitted. + * The comparison between an {@link AllowedOperation} and a {@link PerformableOperation} ensures that only authorized + * modifications are made during the execution of an action. This class facilitates the validation of performable + * operations against a set of predefined allowed operations, based on the action type. + * + *

Key aspects of the comparison include:

+ *
    + *
  • Equality of operation types (e.g., "add", "remove", "replace") as defined in the JSON Patch + * specification (RFC 6902).
  • + *
  • Matching of operation paths, considering both exact matches and base path matches to allow + * for flexibility in specifying allowed operations.
  • + *
+ */ +public class OperationComparator { + + public static boolean compare(AllowedOperation allowedOp, PerformableOperation performableOp) { + + if (!allowedOp.getOp().equals(performableOp.getOp())) { + return false; + } + + String performableOperationBasePath = performableOp.getPath().contains("/") + ? performableOp.getPath().substring(0, performableOp.getPath().lastIndexOf('/') + 1) + : ""; + + for (String allowedPath : allowedOp.getPaths()) { + if (performableOp.getPath().equals(allowedPath) || + performableOperationBasePath.equals(allowedPath)) { + return true; + } + } + + return false; + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/test/resources/testng.xml b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/test/resources/testng.xml new file mode 100644 index 00000000000..19097b4b2ab --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/test/resources/testng.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/components/action-mgt/pom.xml b/components/action-mgt/pom.xml index 8be194d3f45..47f1214d1ea 100644 --- a/components/action-mgt/pom.xml +++ b/components/action-mgt/pom.xml @@ -37,5 +37,6 @@ org.wso2.carbon.identity.action.management + org.wso2.carbon.identity.action.execution