diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/pom.xml b/components/policy-mgt/org.wso2.carbon.identity.policy.management/pom.xml new file mode 100644 index 000000000000..437a6ba76bae --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/pom.xml @@ -0,0 +1,189 @@ + + + + + + + org.wso2.carbon.identity.framework + policy-mgt + 7.11.135-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.identity.policy.management + bundle + WSO2 Carbon - Policy Management Component + Generic policy management backend component + http://wso2.org + + + + + + org.eclipse.platform + org.eclipse.osgi + + + org.eclipse.platform + org.eclipse.osgi.services + + + + + org.wso2.carbon.utils + org.wso2.carbon.database.utils + + + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.core + + + + + commons-logging + commons-logging + + + + + commons-lang.wso2 + commons-lang + + + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.rule.management + + + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.rule.evaluation + + + + + org.testng + testng + test + + + org.mockito + mockito-core + test + + + com.h2database + h2 + test + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.testutil + test + + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + + ${project.artifactId} + + ${project.artifactId} + + org.wso2.carbon.identity.policy.management.internal.* + + + !org.wso2.carbon.identity.policy.management.internal.*, + org.wso2.carbon.identity.policy.management.api.*; + version="${carbon.identity.package.export.version}" + + + org.apache.commons.logging; version="${import.package.version.commons.logging}", + org.apache.commons.lang; version="${commons-lang.wso2.osgi.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.osgi.service.component.annotations; + version="${osgi.service.component.imp.pkg.version.range}", + org.wso2.carbon.database.utils.jdbc; + version="${org.wso2.carbon.database.utils.version.range}", + org.wso2.carbon.database.utils.jdbc.exceptions; + version="${org.wso2.carbon.database.utils.version.range}", + org.wso2.carbon.identity.core.util; + version="${carbon.identity.package.import.version.range}", + org.wso2.carbon.identity.rule.management.api.exception; + version="${carbon.identity.package.import.version.range}", + org.wso2.carbon.identity.rule.management.api.model; + version="${carbon.identity.package.import.version.range}", + org.wso2.carbon.identity.rule.management.api.service; + version="${carbon.identity.package.import.version.range}", + org.wso2.carbon.identity.rule.management.api.util; + version="${carbon.identity.package.import.version.range}", + org.wso2.carbon.identity.rule.evaluation.api.exception; + version="${carbon.identity.package.import.version.range}", + org.wso2.carbon.identity.rule.evaluation.api.model; + version="${carbon.identity.package.import.version.range}", + org.wso2.carbon.identity.rule.evaluation.api.service; + version="${carbon.identity.package.import.version.range}", + org.wso2.carbon.utils; + version="${carbon.kernel.package.import.version.range}", + org.wso2.carbon.identity.core.cache; + version="${carbon.identity.package.import.version.range}" + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire.plugin.version} + + + src/test/resources/testng.xml + + + + + com.github.spotbugs + spotbugs-maven-plugin + + ../../../spotbugs-exclude.xml + Max + High + true + + + + + + diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/constant/ErrorMessage.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/constant/ErrorMessage.java new file mode 100644 index 000000000000..2dbe295d38be --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/constant/ErrorMessage.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2026, 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.policy.management.api.constant; + +/** + * Error messages for Policy Management. + */ +public enum ErrorMessage { + + // Client errors. + ERROR_POLICY_NOT_FOUND("PM-60001", "Policy not found.", + "No policy found for the given policy id: %s."), + ERROR_INVALID_POLICY_REQUEST_FIELD("PM-60002", "Invalid request.", + "%s is empty or invalid."), + ERROR_INVALID_POLICY_RULE("PM-60003", "Invalid policy rule.", + "Policy rule validation failed: %s"), + ERROR_POLICY_ALREADY_EXISTS("PM-60004", "Policy already exists.", + "A policy with name '%s' already exists for the tenant."), + ERROR_DUPLICATE_PLATFORM_IN_POLICY("PM-60005", "Duplicate platform in policy.", + "Policy '%s' has more than one rule for platform '%s'."), + + // Server errors. + ERROR_WHILE_ADDING_POLICY("PM-65001", "Error while adding Policy.", + "Error while persisting Policy in the system."), + ERROR_WHILE_RETRIEVING_POLICY("PM-65002", "Error while retrieving Policy.", + "Error while retrieving Policy from the system."), + ERROR_WHILE_UPDATING_POLICY("PM-65003", "Error while updating Policy.", + "Error while updating Policy in the system."), + ERROR_WHILE_DELETING_POLICY("PM-65004", "Error while deleting Policy.", + "Error while deleting Policy from the system."), + ERROR_WHILE_ADDING_RULE_FOR_POLICY("PM-65005", "Error while adding Rule for Policy.", + "Error while adding Rule for Policy: %s in the system."), + ERROR_WHILE_UPDATING_RULE_FOR_POLICY("PM-65006", "Error while updating Rule for Policy.", + "Error while updating Rule for Policy: %s in the system."), + ERROR_WHILE_DELETING_RULE_FOR_POLICY("PM-65007", "Error while deleting Rule for Policy.", + "Error while deleting Rule for Policy: %s from the system."); + + private final String code; + private final String message; + private final String description; + + ErrorMessage(String code, String message, String description) { + + this.code = code; + this.message = message; + this.description = description; + } + + public String getCode() { + + return code; + } + + public String getMessage() { + + return message; + } + + public String getDescription() { + + return description; + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/exception/PolicyManagementClientException.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/exception/PolicyManagementClientException.java new file mode 100644 index 000000000000..e03d5eaa42b2 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/exception/PolicyManagementClientException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2026, 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.policy.management.api.exception; + +/** + * Client exception for Device Policy Management. + */ +public class PolicyManagementClientException extends PolicyManagementException { + + public PolicyManagementClientException(String message, String description, String errorCode) { + + super(message, description, errorCode); + } + + public PolicyManagementClientException(String message, String description, String errorCode, + Throwable cause) { + + super(message, description, errorCode, cause); + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/exception/PolicyManagementException.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/exception/PolicyManagementException.java new file mode 100644 index 000000000000..d2d02614774d --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/exception/PolicyManagementException.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026, 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.policy.management.api.exception; + +/** + * Base exception for Device Policy Management. + */ +public class PolicyManagementException extends Exception { + + private final String errorCode; + private final String description; + + public PolicyManagementException(String message, String description, String errorCode) { + + super(message); + this.errorCode = errorCode; + this.description = description; + } + + public PolicyManagementException(String message, String description, String errorCode, + Throwable cause) { + + super(message, cause); + this.errorCode = errorCode; + this.description = description; + } + + public String getErrorCode() { + + return errorCode; + } + + public String getDescription() { + + return description; + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/exception/PolicyManagementServerException.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/exception/PolicyManagementServerException.java new file mode 100644 index 000000000000..d33372407bda --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/exception/PolicyManagementServerException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2026, 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.policy.management.api.exception; + +/** + * Server exception for Device Policy Management. + */ +public class PolicyManagementServerException extends PolicyManagementException { + + public PolicyManagementServerException(String message, String description, String errorCode) { + + super(message, description, errorCode); + } + + public PolicyManagementServerException(String message, String description, String errorCode, + Throwable cause) { + + super(message, description, errorCode, cause); + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/Policy.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/Policy.java new file mode 100644 index 000000000000..c04d7aaef4b9 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/Policy.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2026, 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.policy.management.api.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Device compliance policy. + */ +public class Policy { + + private final String id; + private final String name; + private final String tenantDomain; + private final List resources; + + public Policy(String id, String name, String tenantDomain, List resources) { + + this.id = id; + this.name = name; + this.tenantDomain = tenantDomain; + this.resources = resources != null + ? Collections.unmodifiableList(new ArrayList<>(resources)) : Collections.emptyList(); + } + + public String getId() { + + return id; + } + + public String getName() { + + return name; + } + + public String getTenantDomain() { + + return tenantDomain; + } + + public List getResources() { + + return resources; + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/PolicyBasicInfo.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/PolicyBasicInfo.java new file mode 100644 index 000000000000..5137c660660f --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/PolicyBasicInfo.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2026, 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.policy.management.api.model; + +/** + * Lightweight summary of a Policy for list views. + * Carries only the fields a list page needs (id and name); the full {@link Policy} with hydrated + * resources is retrieved via {@code getPolicyById}. + */ +public class PolicyBasicInfo { + + private final String id; + private final String name; + + public PolicyBasicInfo(String id, String name) { + + this.id = id; + this.name = name; + } + + /** + * Returns the policy ID. + * + * @return Policy ID. + */ + public String getId() { + + return id; + } + + /** + * Returns the policy name. + * + * @return Policy name. + */ + public String getName() { + + return name; + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/PolicyResource.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/PolicyResource.java new file mode 100644 index 000000000000..4f685023e047 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/PolicyResource.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026, 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.policy.management.api.model; + +import org.wso2.carbon.identity.rule.management.api.model.Rule; + +/** + * Association between a Policy and a resource (a Rule or an Action) for a specific target. + * The target is a selector value (e.g. a device platform); one target may have at most one + * resource of each {@link ResourceType} per policy. + */ +public class PolicyResource { + + private final String id; + private final String target; + private final ResourceType resourceType; + private final String resourceId; + private final Rule rule; + + public PolicyResource(String id, String target, ResourceType resourceType, String resourceId, Rule rule) { + + this.id = id; + this.target = target; + this.resourceType = resourceType; + this.resourceId = resourceId; + this.rule = rule; + } + + public String getId() { + + return id; + } + + public String getTarget() { + + return target; + } + + public ResourceType getResourceType() { + + return resourceType; + } + + public String getResourceId() { + + return resourceId; + } + + /** + * Returns the hydrated rule. Non-null only when {@link #getResourceType()} is {@link ResourceType#RULE} + * and the resource has been hydrated by the service layer. + * + * @return Hydrated rule, or {@code null}. + */ + public Rule getRule() { + + return rule; + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/ResourceType.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/ResourceType.java new file mode 100644 index 000000000000..9bff43016983 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/model/ResourceType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, 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.policy.management.api.model; + +/** + * Type of resource attached to a policy for a given target. + * Stored in the database as its {@code name()} value. + */ +public enum ResourceType { + + /** An IS-native rule managed by rule-mgt. */ + RULE, + + /** An external action managed by action-mgt. */ + ACTION +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/service/PolicyEvaluationService.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/service/PolicyEvaluationService.java new file mode 100644 index 000000000000..0b8a865bce87 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/service/PolicyEvaluationService.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2026, 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.policy.management.api.service; + +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException; +import org.wso2.carbon.identity.rule.evaluation.api.exception.RuleEvaluationException; +import org.wso2.carbon.identity.rule.evaluation.api.model.FlowContext; +import org.wso2.carbon.identity.rule.evaluation.api.model.RuleEvaluationResult; + +/** + * Service interface for evaluating compliance policies against a caller-supplied rule context. + */ +public interface PolicyEvaluationService { + + /** + * Evaluates the rule matching {@code ruleSelector} within the named policy against the provided + * {@link FlowContext}. + * + *

Returns {@code null} when no policy with {@code policyName} exists — the caller interprets + * this as "no constraint" and decides the appropriate response. Returns a satisfied result with + * a {@code null} rule ID when a policy exists but no rule matches the selector (compliant by default). + * + * @param policyName Name of the policy to evaluate. + * @param ruleSelector Value used to select a rule within the policy (e.g. device platform). + * @param flowContext Context carrying the data fields for rule evaluation. + * @param tenantDomain Tenant domain. + * @return Evaluation result, or {@code null} if the named policy does not exist. + * @throws PolicyManagementException If policy retrieval fails. + * @throws RuleEvaluationException If rule evaluation fails. + */ + RuleEvaluationResult evaluate(String policyName, String ruleSelector, + FlowContext flowContext, String tenantDomain) + throws PolicyManagementException, RuleEvaluationException; +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/service/PolicyManagementService.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/service/PolicyManagementService.java new file mode 100644 index 000000000000..8b4d37a54bdd --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/service/PolicyManagementService.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2026, 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.policy.management.api.service; + +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException; +import org.wso2.carbon.identity.policy.management.api.model.Policy; +import org.wso2.carbon.identity.policy.management.api.model.PolicyBasicInfo; +import org.wso2.carbon.identity.policy.management.api.model.PolicyResource; + +import java.util.List; + +/** + * Service interface for managing device compliance policies. + */ +public interface PolicyManagementService { + + /** + * Creates a new policy. The supplied ID is ignored; a server-generated UUID is assigned. + * + * @param policy Policy to create. Name must be non-blank. + * @param tenantDomain Tenant domain. + * @return Created policy with the server-assigned ID. + * @throws PolicyManagementException If the policy name is blank or persistence fails. + */ + Policy addPolicy(Policy policy, String tenantDomain) throws PolicyManagementException; + + /** + * Replaces an existing policy (PUT semantics). All fields, rules, and actions are replaced wholesale. + * + * @param policy Policy with the new state. ID must reference an existing policy. + * @param tenantDomain Tenant domain. + * @return Updated policy as persisted. + * @throws PolicyManagementException If the policy is not found or persistence fails. + */ + Policy updatePolicy(Policy policy, String tenantDomain) throws PolicyManagementException; + + /** + * Deletes the policy. Idempotent — deleting a non-existent policy is a no-op. + * + * @param policyId Policy ID. + * @param tenantDomain Tenant domain. + * @throws PolicyManagementException If persistence fails. + */ + void deletePolicy(String policyId, String tenantDomain) throws PolicyManagementException; + + /** + * Returns the policy by ID with each rule-typed {@link PolicyResource} hydrated from rule-mgt. + * + * @param policyId Policy ID. + * @param tenantDomain Tenant domain. + * @return Policy with hydrated rules, or {@code null} if not found. + * @throws PolicyManagementException If retrieval or rule hydration fails. + */ + Policy getPolicyById(String policyId, String tenantDomain) throws PolicyManagementException; + + /** + * Returns the policy by name with each rule-typed {@link PolicyResource} hydrated from rule-mgt. + * + * @param policyName Policy name. + * @param tenantDomain Tenant domain. + * @return Policy with hydrated rules, or {@code null} if not found. + * @throws PolicyManagementException If retrieval or rule hydration fails. + */ + Policy getPolicyByName(String policyName, String tenantDomain) throws PolicyManagementException; + + /** + * Returns a page of policy summaries for the tenant, optionally filtered by name. Summaries are + * lightweight (no hydrated rules); call {@link #getPolicyById} for the full hydrated policy. + * + * @param tenantDomain Tenant domain. + * @param filter Name filter; {@code null} or blank returns all policies. + * @param offset Zero-based start index. + * @param limit Maximum number of results to return. + * @return Page of policy summaries (never {@code null}). + * @throws PolicyManagementException If retrieval fails. + */ + List getPolicies(String tenantDomain, String filter, int offset, int limit) + throws PolicyManagementException; + + /** + * Returns the total number of policies matching the given filter, for pagination. + * + * @param tenantDomain Tenant domain. + * @param filter Name filter; {@code null} or blank counts all policies. + * @return Total number of matching policies. + * @throws PolicyManagementException If retrieval fails. + */ + int getPolicyCount(String tenantDomain, String filter) throws PolicyManagementException; +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/util/PolicyManagementExceptionHandler.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/util/PolicyManagementExceptionHandler.java new file mode 100644 index 000000000000..ad56e1b2adce --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/api/util/PolicyManagementExceptionHandler.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2026, 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.policy.management.api.util; + +import org.apache.commons.lang.ArrayUtils; +import org.wso2.carbon.identity.policy.management.api.constant.ErrorMessage; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementClientException; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementServerException; + +/** + * Utility class for constructing policy management exceptions with the standard + * (message, description, code[, cause]) shape backed by ErrorMessage entries. + */ +public class PolicyManagementExceptionHandler { + + private PolicyManagementExceptionHandler() { + + } + + public static PolicyManagementClientException handleClientException(ErrorMessage error, String... data) { + + String description = error.getDescription(); + if (ArrayUtils.isNotEmpty(data)) { + description = String.format(description, (Object[]) data); + } + + return new PolicyManagementClientException(error.getMessage(), description, error.getCode()); + } + + public static PolicyManagementClientException handleClientException(ErrorMessage error, Throwable e, + String... data) { + + String description = error.getDescription(); + if (ArrayUtils.isNotEmpty(data)) { + description = String.format(description, (Object[]) data); + } + + return new PolicyManagementClientException(error.getMessage(), description, error.getCode(), e); + } + + public static PolicyManagementServerException handleServerException(ErrorMessage error, Throwable e, + String... data) { + + String description = error.getDescription(); + if (ArrayUtils.isNotEmpty(data)) { + description = String.format(description, (Object[]) data); + } + + return new PolicyManagementServerException(error.getMessage(), description, error.getCode(), e); + } + + public static PolicyManagementServerException handleServerException(ErrorMessage error, String... data) { + + String description = error.getDescription(); + if (ArrayUtils.isNotEmpty(data)) { + description = String.format(description, (Object[]) data); + } + + return new PolicyManagementServerException(error.getMessage(), description, error.getCode()); + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCache.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCache.java new file mode 100644 index 000000000000..30bce577a27d --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCache.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026, 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.policy.management.internal.cache; + +import org.wso2.carbon.identity.core.cache.BaseCache; +import org.wso2.carbon.utils.CarbonUtils; + +/** + * Cache for Policy Management. + */ +public class PolicyCache extends BaseCache { + + private static final String CACHE_NAME = "PolicyCache"; + private static final PolicyCache INSTANCE = new PolicyCache(); + + private PolicyCache() { + + super(CACHE_NAME); + } + + public static PolicyCache getInstance() { + + CarbonUtils.checkSecurity(); + return INSTANCE; + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCacheEntry.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCacheEntry.java new file mode 100644 index 000000000000..dcfd20a2a3fe --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCacheEntry.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026, 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.policy.management.internal.cache; + +import org.wso2.carbon.identity.core.cache.CacheEntry; +import org.wso2.carbon.identity.policy.management.api.model.Policy; + +/** + * Cache entry for Policy Management. + * Stores an un-hydrated Policy (rule IDs only, no full Rule objects). + */ +public class PolicyCacheEntry extends CacheEntry { + + private static final long serialVersionUID = -4310859152763490172L; + + private final Policy policy; + + public PolicyCacheEntry(Policy policy) { + + this.policy = policy; + } + + public Policy getPolicy() { + + return policy; + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCacheKey.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCacheKey.java new file mode 100644 index 000000000000..0b64537b30b7 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/cache/PolicyCacheKey.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026, 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.policy.management.internal.cache; + +import org.wso2.carbon.identity.core.cache.CacheKey; + +/** + * Cache key for Policy Management. + * Identifies a cached policy by its name; tenant scoping is handled by the cache's tenantId parameter. + */ +public class PolicyCacheKey extends CacheKey { + + private static final long serialVersionUID = 1861274580132498765L; + + private final String policyName; + + public PolicyCacheKey(String policyName) { + + this.policyName = policyName; + } + + public String getPolicyName() { + + return policyName; + } + + @Override + public boolean equals(Object o) { + + if (!(o instanceof PolicyCacheKey)) { + return false; + } + return policyName.equals(((PolicyCacheKey) o).getPolicyName()); + } + + @Override + public int hashCode() { + + return policyName.hashCode(); + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/component/PolicyMgtComponentServiceHolder.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/component/PolicyMgtComponentServiceHolder.java new file mode 100644 index 000000000000..3bd0cac0a859 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/component/PolicyMgtComponentServiceHolder.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2026, 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.policy.management.internal.component; + +import org.wso2.carbon.identity.policy.management.api.service.PolicyManagementService; +import org.wso2.carbon.identity.rule.evaluation.api.service.RuleEvaluationService; +import org.wso2.carbon.identity.rule.management.api.service.RuleManagementService; + +/** + * Service holder for the policy management component. + * Provides access to OSGi services consumed by this bundle. + */ +public class PolicyMgtComponentServiceHolder { + + private static final PolicyMgtComponentServiceHolder INSTANCE = new PolicyMgtComponentServiceHolder(); + + private RuleManagementService ruleManagementService; + private RuleEvaluationService ruleEvaluationService; + private PolicyManagementService policyManagementService; + + private PolicyMgtComponentServiceHolder() { + + } + + /** + * Returns the singleton service holder instance. + * + * @return Service holder. + */ + public static PolicyMgtComponentServiceHolder getInstance() { + + return INSTANCE; + } + + public RuleManagementService getRuleManagementService() { + + return ruleManagementService; + } + + public void setRuleManagementService(RuleManagementService ruleManagementService) { + + this.ruleManagementService = ruleManagementService; + } + + public RuleEvaluationService getRuleEvaluationService() { + + return ruleEvaluationService; + } + + public void setRuleEvaluationService(RuleEvaluationService ruleEvaluationService) { + + this.ruleEvaluationService = ruleEvaluationService; + } + + public PolicyManagementService getPolicyManagementService() { + + return policyManagementService; + } + + public void setPolicyManagementService(PolicyManagementService policyManagementService) { + + this.policyManagementService = policyManagementService; + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/component/PolicyMgtServiceComponent.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/component/PolicyMgtServiceComponent.java new file mode 100644 index 000000000000..65259e4b8066 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/component/PolicyMgtServiceComponent.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2026, 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.policy.management.internal.component; + +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.policy.management.api.service.PolicyEvaluationService; +import org.wso2.carbon.identity.policy.management.api.service.PolicyManagementService; +import org.wso2.carbon.identity.policy.management.internal.service.impl.PolicyEvaluationServiceImpl; +import org.wso2.carbon.identity.policy.management.internal.service.impl.PolicyManagementServiceImpl; +import org.wso2.carbon.identity.rule.evaluation.api.service.RuleEvaluationService; +import org.wso2.carbon.identity.rule.management.api.service.RuleManagementService; + +/** + * OSGi DS component that registers the PolicyManagementService. + */ +@Component( + name = "policy.management.service.component", + immediate = true +) +public class PolicyMgtServiceComponent { + + private static final Log LOG = LogFactory.getLog(PolicyMgtServiceComponent.class); + + @Activate + protected void activate(ComponentContext context) { + + try { + BundleContext bundleCtx = context.getBundleContext(); + PolicyManagementServiceImpl policyManagementService = new PolicyManagementServiceImpl(); + bundleCtx.registerService(PolicyManagementService.class.getName(), policyManagementService, null); + PolicyMgtComponentServiceHolder.getInstance().setPolicyManagementService(policyManagementService); + + PolicyEvaluationServiceImpl policyEvaluationService = new PolicyEvaluationServiceImpl(); + bundleCtx.registerService(PolicyEvaluationService.class.getName(), policyEvaluationService, null); + LOG.debug("Policy management bundle activated."); + } catch (Throwable e) { + LOG.error("Error while initializing policy management service component.", e); + } + } + + @Deactivate + protected void deactivate(ComponentContext context) { + + LOG.debug("Policy management bundle deactivated."); + } + + @Reference( + name = "rule.management.service", + service = RuleManagementService.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetRuleManagementService" + ) + protected void setRuleManagementService(RuleManagementService ruleManagementService) { + + PolicyMgtComponentServiceHolder.getInstance().setRuleManagementService(ruleManagementService); + LOG.debug("RuleManagementService set in Policy Management component."); + } + + protected void unsetRuleManagementService(RuleManagementService ruleManagementService) { + + PolicyMgtComponentServiceHolder holder = PolicyMgtComponentServiceHolder.getInstance(); + if (holder.getRuleManagementService() == ruleManagementService) { + holder.setRuleManagementService(null); + } + LOG.debug("RuleManagementService unset in Policy Management component."); + } + + @Reference( + name = "rule.evaluation.service", + service = RuleEvaluationService.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetRuleEvaluationService" + ) + protected void setRuleEvaluationService(RuleEvaluationService ruleEvaluationService) { + + PolicyMgtComponentServiceHolder.getInstance().setRuleEvaluationService(ruleEvaluationService); + LOG.debug("RuleEvaluationService set in Policy Management component."); + } + + protected void unsetRuleEvaluationService(RuleEvaluationService ruleEvaluationService) { + + PolicyMgtComponentServiceHolder holder = PolicyMgtComponentServiceHolder.getInstance(); + if (holder.getRuleEvaluationService() == ruleEvaluationService) { + holder.setRuleEvaluationService(null); + } + LOG.debug("RuleEvaluationService unset in Policy Management component."); + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/constant/PolicyMgtSQLConstants.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/constant/PolicyMgtSQLConstants.java new file mode 100644 index 000000000000..ce2ffb966695 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/constant/PolicyMgtSQLConstants.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2026, 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.policy.management.internal.constant; + +/** + * SQL constants for Policy Management DAO. + * Two tables: IDN_POLICY (main) and IDN_POLICY_RESOURCE (polymorphic attachment of rules/actions to a policy). + */ +public final class PolicyMgtSQLConstants { + + private PolicyMgtSQLConstants() { + + } + + /** Column name constants. */ + public static final class Column { + + public static final String ID = "ID"; + public static final String POLICY_ID = "POLICY_ID"; + public static final String POLICY_NAME = "POLICY_NAME"; + public static final String TENANT_ID = "TENANT_ID"; + public static final String TARGET = "TARGET"; + public static final String RESOURCE_TYPE = "RESOURCE_TYPE"; + public static final String RESOURCE_ID = "RESOURCE_ID"; + + public static final String FILTER = "FILTER"; + public static final String LIMIT = "LIMIT"; + public static final String OFFSET = "OFFSET"; + public static final String LOWER_BOUND = "LOWER_BOUND"; + public static final String UPPER_BOUND = "UPPER_BOUND"; + + private Column() { + + } + } + + /** SQL query constants. */ + public static final class Query { + + // IDN_POLICY table. + public static final String ADD_POLICY = + "INSERT INTO IDN_POLICY (ID, POLICY_NAME, TENANT_ID) " + + "VALUES (:ID;, :POLICY_NAME;, :TENANT_ID;)"; + + public static final String UPDATE_POLICY = + "UPDATE IDN_POLICY SET POLICY_NAME = :POLICY_NAME; " + + "WHERE ID = :POLICY_ID; AND TENANT_ID = :TENANT_ID;"; + + public static final String DELETE_POLICY = + "DELETE FROM IDN_POLICY WHERE ID = :POLICY_ID; AND TENANT_ID = :TENANT_ID;"; + + public static final String GET_POLICY_BY_ID = + "SELECT ID, POLICY_NAME FROM IDN_POLICY " + + "WHERE ID = :POLICY_ID; AND TENANT_ID = :TENANT_ID;"; + + public static final String GET_POLICY_BY_NAME = + "SELECT ID, POLICY_NAME FROM IDN_POLICY " + + "WHERE POLICY_NAME = :POLICY_NAME; AND TENANT_ID = :TENANT_ID;"; + + // Paginated policy listing. A name filter fragment (LOWER(POLICY_NAME) LIKE LOWER(:FILTER;)) + // is appended via the *_FILTER variants. Pagination syntax differs per database, so a variant + // is selected at runtime based on the detected database type. + + // Default: H2, MySQL, MariaDB, PostgreSQL. + public static final String GET_POLICIES_PAGINATED = + "SELECT ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " + + "ORDER BY POLICY_NAME ASC LIMIT :LIMIT; OFFSET :OFFSET;"; + + public static final String GET_POLICIES_PAGINATED_FILTER = + "SELECT ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " + + "AND LOWER(POLICY_NAME) LIKE LOWER(:FILTER;) " + + "ORDER BY POLICY_NAME ASC LIMIT :LIMIT; OFFSET :OFFSET;"; + + // MS SQL Server. + public static final String GET_POLICIES_PAGINATED_MSSQL = + "SELECT ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " + + "ORDER BY POLICY_NAME ASC OFFSET :OFFSET; ROWS FETCH NEXT :LIMIT; ROWS ONLY"; + + public static final String GET_POLICIES_PAGINATED_FILTER_MSSQL = + "SELECT ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " + + "AND LOWER(POLICY_NAME) LIKE LOWER(:FILTER;) " + + "ORDER BY POLICY_NAME ASC OFFSET :OFFSET; ROWS FETCH NEXT :LIMIT; ROWS ONLY"; + + // Oracle. + public static final String GET_POLICIES_PAGINATED_ORACLE = + "SELECT ID, POLICY_NAME FROM (SELECT ID, POLICY_NAME, rownum AS rnum FROM " + + "(SELECT ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " + + "ORDER BY POLICY_NAME ASC) WHERE rownum <= :UPPER_BOUND;) WHERE rnum > :OFFSET;"; + + public static final String GET_POLICIES_PAGINATED_FILTER_ORACLE = + "SELECT ID, POLICY_NAME FROM (SELECT ID, POLICY_NAME, rownum AS rnum FROM " + + "(SELECT ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " + + "AND LOWER(POLICY_NAME) LIKE LOWER(:FILTER;) ORDER BY POLICY_NAME ASC) " + + "WHERE rownum <= :UPPER_BOUND;) WHERE rnum > :OFFSET;"; + + // DB2. + public static final String GET_POLICIES_PAGINATED_DB2 = + "SELECT ID, POLICY_NAME FROM (SELECT ROW_NUMBER() OVER(ORDER BY POLICY_NAME ASC) AS rn, " + + "ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID;) " + + "WHERE rn BETWEEN :LOWER_BOUND; AND :UPPER_BOUND;"; + + public static final String GET_POLICIES_PAGINATED_FILTER_DB2 = + "SELECT ID, POLICY_NAME FROM (SELECT ROW_NUMBER() OVER(ORDER BY POLICY_NAME ASC) AS rn, " + + "ID, POLICY_NAME FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " + + "AND LOWER(POLICY_NAME) LIKE LOWER(:FILTER;)) WHERE rn BETWEEN :LOWER_BOUND; AND :UPPER_BOUND;"; + + public static final String GET_POLICIES_COUNT = + "SELECT COUNT(*) FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID;"; + + public static final String GET_POLICIES_COUNT_FILTER = + "SELECT COUNT(*) FROM IDN_POLICY WHERE TENANT_ID = :TENANT_ID; " + + "AND LOWER(POLICY_NAME) LIKE LOWER(:FILTER;)"; + + public static final String CHECK_POLICY_NAME_EXISTS = + "SELECT ID FROM IDN_POLICY " + + "WHERE POLICY_NAME = :POLICY_NAME; AND TENANT_ID = :TENANT_ID;"; + + // IDN_POLICY_RESOURCE attachment table. + public static final String ADD_POLICY_RESOURCE = + "INSERT INTO IDN_POLICY_RESOURCE (ID, POLICY_ID, TARGET, RESOURCE_TYPE, RESOURCE_ID) " + + "VALUES (:ID;, :POLICY_ID;, :TARGET;, :RESOURCE_TYPE;, :RESOURCE_ID;)"; + + public static final String GET_POLICY_RESOURCES = + "SELECT ID, TARGET, RESOURCE_TYPE, RESOURCE_ID FROM IDN_POLICY_RESOURCE " + + "WHERE POLICY_ID = :POLICY_ID;"; + + public static final String DELETE_POLICY_RESOURCES = + "DELETE FROM IDN_POLICY_RESOURCE WHERE POLICY_ID = :POLICY_ID;"; + + private Query() { + + } + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/PolicyManagementDAO.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/PolicyManagementDAO.java new file mode 100644 index 000000000000..135604bd183d --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/PolicyManagementDAO.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2026, 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.policy.management.internal.dao; + +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException; +import org.wso2.carbon.identity.policy.management.api.model.Policy; +import org.wso2.carbon.identity.policy.management.api.model.PolicyBasicInfo; + +import java.util.List; + +/** + * Interface for Policy Management DAO. + * Handles CRUD for IDN_POLICY and IDN_POLICY_RESOURCE tables. + * Returns Policy objects with PolicyResource lists populated (resourceIds only — rules are hydrated by the + * service layer). + */ +public interface PolicyManagementDAO { + + /** + * Persists a new policy and its resources. + * + * @param policy Policy to persist (id pre-assigned). + * @param tenantId Tenant ID. + * @return Persisted policy. + * @throws PolicyManagementException If persistence fails. + */ + Policy addPolicy(Policy policy, int tenantId) throws PolicyManagementException; + + /** + * Replaces an existing policy and its resources (the resource rows are deleted and re-inserted). + * + * @param policy Policy with the new state. + * @param tenantId Tenant ID. + * @return Updated policy. + * @throws PolicyManagementException If persistence fails. + */ + Policy updatePolicy(Policy policy, int tenantId) throws PolicyManagementException; + + /** + * Deletes the policy and its resources (cascaded). Idempotent. + * + * @param policyId Policy ID. + * @param tenantId Tenant ID. + * @throws PolicyManagementException If persistence fails. + */ + void deletePolicy(String policyId, int tenantId) throws PolicyManagementException; + + /** + * Returns the policy by ID with its resources (resource ids only; rules are not hydrated). + * + * @param policyId Policy ID. + * @param tenantId Tenant ID. + * @return Policy, or {@code null} if no policy exists for the given ID and tenant. + * @throws PolicyManagementException If retrieval fails. + */ + Policy getPolicyById(String policyId, int tenantId) throws PolicyManagementException; + + /** + * Returns the policy by name with its resources (resource ids only; rules are not hydrated). + * + * @param policyName Policy name. + * @param tenantId Tenant ID. + * @return Policy, or {@code null} if no policy exists for the given name and tenant. + * @throws PolicyManagementException If retrieval fails. + */ + Policy getPolicyByName(String policyName, int tenantId) throws PolicyManagementException; + + /** + * Returns a paginated, optionally name-filtered list of policies (basic info only), ordered by name. + * + * @param tenantId Tenant ID. + * @param filter Case-insensitive substring to match against the policy name; {@code null}/blank for no filter. + * @param offset Number of records to skip. + * @param limit Maximum number of records to return; a non-positive value yields an empty list. + * @return List of matching policies (never {@code null}). + * @throws PolicyManagementException If retrieval fails. + */ + List getPolicies(int tenantId, String filter, int offset, int limit) + throws PolicyManagementException; + + /** + * Returns the total number of policies for the tenant, applying the same name filter as + * {@link #getPolicies(int, String, int, int)}. + * + * @param tenantId Tenant ID. + * @param filter Case-insensitive substring to match against the policy name; {@code null}/blank for no filter. + * @return Matching policy count. + * @throws PolicyManagementException If retrieval fails. + */ + int getPolicyCount(int tenantId, String filter) throws PolicyManagementException; + + /** + * Returns the ID of the policy with the given name, used for name-uniqueness checks. + * + * @param policyName Policy name. + * @param tenantId Tenant ID. + * @return Policy ID, or {@code null} if no policy exists with that name for the tenant. + * @throws PolicyManagementException If retrieval fails. + */ + String getPolicyIdByName(String policyName, int tenantId) throws PolicyManagementException; +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/impl/CacheBackedPolicyManagementDAO.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/impl/CacheBackedPolicyManagementDAO.java new file mode 100644 index 000000000000..0d786273e6b7 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/impl/CacheBackedPolicyManagementDAO.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2026, 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.policy.management.internal.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException; +import org.wso2.carbon.identity.policy.management.api.model.Policy; +import org.wso2.carbon.identity.policy.management.api.model.PolicyBasicInfo; +import org.wso2.carbon.identity.policy.management.internal.cache.PolicyCache; +import org.wso2.carbon.identity.policy.management.internal.cache.PolicyCacheEntry; +import org.wso2.carbon.identity.policy.management.internal.cache.PolicyCacheKey; +import org.wso2.carbon.identity.policy.management.internal.dao.PolicyManagementDAO; + +import java.util.List; + +/** + * Cache-backed Policy Management DAO. + * Wraps a PolicyManagementDAO with an in-process cache to reduce DB hits on the hot path. + * Hot path: getPolicyByName (called on every device evaluation) is served from cache after first read. + * Cache is keyed by policy name only and is precisely invalidated on update and delete. + */ +public class CacheBackedPolicyManagementDAO implements PolicyManagementDAO { + + private static final Log LOG = LogFactory.getLog(CacheBackedPolicyManagementDAO.class); + + private final PolicyManagementDAO policyManagementDAO; + private final PolicyCache policyCache; + + public CacheBackedPolicyManagementDAO(PolicyManagementDAO policyManagementDAO) { + + this.policyManagementDAO = policyManagementDAO; + policyCache = PolicyCache.getInstance(); + } + + /** + * Add a new Policy. + * This method directly invokes the data layer operation without caching the result. + * + * @param policy Policy object. + * @param tenantId Tenant ID. + * @return Created Policy object. + * @throws PolicyManagementException Policy Management Exception. + */ + @Override + public Policy addPolicy(Policy policy, int tenantId) throws PolicyManagementException { + + return policyManagementDAO.addPolicy(policy, tenantId); + } + + /** + * Update an existing Policy. + * Reads the persisted policy first to detect renames, then delegates the update, + * and finally clears the name-based cache entries for both the old and new names. + * + * @param policy Policy object with updated state. + * @param tenantId Tenant ID. + * @return Updated Policy object. + * @throws PolicyManagementException Policy Management Exception. + */ + @Override + public Policy updatePolicy(Policy policy, int tenantId) throws PolicyManagementException { + + Policy existingPolicy = policyManagementDAO.getPolicyById(policy.getId(), tenantId); + Policy updatedPolicy = policyManagementDAO.updatePolicy(policy, tenantId); + + if (existingPolicy != null) { + policyCache.clearCacheEntry(new PolicyCacheKey(existingPolicy.getName()), tenantId); + if (LOG.isDebugEnabled()) { + LOG.debug("Policy cache cleared for old name: " + existingPolicy.getName() + " on update."); + } + } + policyCache.clearCacheEntry(new PolicyCacheKey(policy.getName()), tenantId); + if (LOG.isDebugEnabled()) { + LOG.debug("Policy cache cleared for name: " + policy.getName() + " on update."); + } + return updatedPolicy; + } + + /** + * Delete a Policy. + * Reads the persisted policy first to resolve its name, delegates the delete, + * then clears the name-based cache entry. + * + * @param policyId Policy ID. + * @param tenantId Tenant ID. + * @throws PolicyManagementException Policy Management Exception. + */ + @Override + public void deletePolicy(String policyId, int tenantId) throws PolicyManagementException { + + Policy existingPolicy = policyManagementDAO.getPolicyById(policyId, tenantId); + policyManagementDAO.deletePolicy(policyId, tenantId); + if (existingPolicy != null) { + policyCache.clearCacheEntry(new PolicyCacheKey(existingPolicy.getName()), tenantId); + if (LOG.isDebugEnabled()) { + LOG.debug("Policy cache cleared for name: " + existingPolicy.getName() + " on delete."); + } + } + } + + /** + * Get a Policy by Policy ID. + * Not cached; always delegates to the underlying DAO. + * + * @param policyId Policy ID. + * @param tenantId Tenant ID. + * @return Policy object, or {@code null} if not found. + * @throws PolicyManagementException Policy Management Exception. + */ + @Override + public Policy getPolicyById(String policyId, int tenantId) throws PolicyManagementException { + + return policyManagementDAO.getPolicyById(policyId, tenantId); + } + + /** + * Get a Policy by Policy name. + * This method first checks the cache for the Policy object. + * If the Policy object is not found in the cache, it invokes the data layer operation + * and caches the result under the name-based key. + * + * @param policyName Policy name. + * @param tenantId Tenant ID. + * @return Policy object, or {@code null} if not found. + * @throws PolicyManagementException Policy Management Exception. + */ + @Override + public Policy getPolicyByName(String policyName, int tenantId) throws PolicyManagementException { + + PolicyCacheEntry cacheEntry = policyCache.getValueFromCache( + new PolicyCacheKey(policyName), tenantId); + if (cacheEntry != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Policy cache hit for name: " + policyName); + } + return cacheEntry.getPolicy(); + } + Policy policy = policyManagementDAO.getPolicyByName(policyName, tenantId); + if (policy != null) { + policyCache.addToCacheOnRead( + new PolicyCacheKey(policyName), new PolicyCacheEntry(policy), tenantId); + } + return policy; + } + + /** + * Get a page of Policy summaries for a tenant, optionally filtered by name. + * Paginated lists are not cached; this directly invokes the data layer operation. + * + * @param tenantId Tenant ID. + * @param filter Name filter; {@code null} or blank means no filter. + * @param offset Zero-based start index. + * @param limit Maximum number of results. + * @return List of policy summaries. Never {@code null}. + * @throws PolicyManagementException Policy Management Exception. + */ + @Override + public List getPolicies(int tenantId, String filter, int offset, int limit) + throws PolicyManagementException { + + return policyManagementDAO.getPolicies(tenantId, filter, offset, limit); + } + + /** + * Count Policies matching the given filter. Not cached; directly invokes the data layer. + * + * @param tenantId Tenant ID. + * @param filter Name filter; {@code null} or blank means count all. + * @return Total number of matching policies. + * @throws PolicyManagementException Policy Management Exception. + */ + @Override + public int getPolicyCount(int tenantId, String filter) throws PolicyManagementException { + + return policyManagementDAO.getPolicyCount(tenantId, filter); + } + + /** + * Get a Policy ID by Policy name. + * This method directly invokes the data layer operation without caching. + * + * @param policyName Policy name. + * @param tenantId Tenant ID. + * @return Policy ID, or {@code null} if not found. + * @throws PolicyManagementException Policy Management Exception. + */ + @Override + public String getPolicyIdByName(String policyName, int tenantId) throws PolicyManagementException { + + return policyManagementDAO.getPolicyIdByName(policyName, tenantId); + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/impl/PolicyManagementDAOImpl.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/impl/PolicyManagementDAOImpl.java new file mode 100644 index 000000000000..320c1dfbe0d1 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/dao/impl/PolicyManagementDAOImpl.java @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2026, 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.policy.management.internal.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.database.utils.jdbc.NamedJdbcTemplate; +import org.wso2.carbon.database.utils.jdbc.NamedPreparedStatement; +import org.wso2.carbon.database.utils.jdbc.NamedTemplate; +import org.wso2.carbon.database.utils.jdbc.exceptions.DataAccessException; +import org.wso2.carbon.database.utils.jdbc.exceptions.TransactionException; +import org.wso2.carbon.identity.core.util.IdentityDatabaseUtil; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.core.util.JdbcUtils; +import org.wso2.carbon.identity.policy.management.api.constant.ErrorMessage; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException; +import org.wso2.carbon.identity.policy.management.api.model.Policy; +import org.wso2.carbon.identity.policy.management.api.model.PolicyBasicInfo; +import org.wso2.carbon.identity.policy.management.api.model.PolicyResource; +import org.wso2.carbon.identity.policy.management.api.model.ResourceType; +import org.wso2.carbon.identity.policy.management.api.util.PolicyManagementExceptionHandler; +import org.wso2.carbon.identity.policy.management.internal.constant.PolicyMgtSQLConstants; +import org.wso2.carbon.identity.policy.management.internal.dao.PolicyManagementDAO; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +/** + * Policy Management DAO Implementation. + */ +public class PolicyManagementDAOImpl implements PolicyManagementDAO { + + private static final Log LOG = LogFactory.getLog(PolicyManagementDAOImpl.class); + + @Override + public Policy addPolicy(Policy policy, int tenantId) throws PolicyManagementException { + + NamedJdbcTemplate jdbcTemplate = + new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + jdbcTemplate.withTransaction(template -> { + template.executeInsert( + PolicyMgtSQLConstants.Query.ADD_POLICY, + preparedStatement -> { + preparedStatement.setString(PolicyMgtSQLConstants.Column.ID, policy.getId()); + preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_NAME, policy.getName()); + preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId); + }, + policy, + false); + + for (PolicyResource policyResource : policy.getResources()) { + final String resourceRowId = UUID.randomUUID().toString(); + template.executeInsert( + PolicyMgtSQLConstants.Query.ADD_POLICY_RESOURCE, + preparedStatement -> { + preparedStatement.setString(PolicyMgtSQLConstants.Column.ID, resourceRowId); + preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_ID, policy.getId()); + preparedStatement.setString(PolicyMgtSQLConstants.Column.TARGET, + policyResource.getTarget()); + preparedStatement.setString(PolicyMgtSQLConstants.Column.RESOURCE_TYPE, + policyResource.getResourceType().name()); + preparedStatement.setString(PolicyMgtSQLConstants.Column.RESOURCE_ID, + policyResource.getResourceId()); + }, + policyResource, + false); + } + + return null; + }); + } catch (TransactionException e) { + throw PolicyManagementExceptionHandler.handleServerException( + ErrorMessage.ERROR_WHILE_ADDING_POLICY, e); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Policy added with ID: " + policy.getId()); + } + return policy; + } + + @Override + public Policy updatePolicy(Policy policy, int tenantId) throws PolicyManagementException { + + NamedJdbcTemplate jdbcTemplate = + new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + jdbcTemplate.withTransaction(template -> { + template.executeUpdate( + PolicyMgtSQLConstants.Query.UPDATE_POLICY, + preparedStatement -> { + preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_NAME, policy.getName()); + preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_ID, policy.getId()); + preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId); + }); + + template.executeUpdate( + PolicyMgtSQLConstants.Query.DELETE_POLICY_RESOURCES, + preparedStatement -> preparedStatement.setString( + PolicyMgtSQLConstants.Column.POLICY_ID, policy.getId())); + + for (PolicyResource policyResource : policy.getResources()) { + final String resourceRowId = UUID.randomUUID().toString(); + template.executeInsert( + PolicyMgtSQLConstants.Query.ADD_POLICY_RESOURCE, + preparedStatement -> { + preparedStatement.setString(PolicyMgtSQLConstants.Column.ID, resourceRowId); + preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_ID, policy.getId()); + preparedStatement.setString(PolicyMgtSQLConstants.Column.TARGET, + policyResource.getTarget()); + preparedStatement.setString(PolicyMgtSQLConstants.Column.RESOURCE_TYPE, + policyResource.getResourceType().name()); + preparedStatement.setString(PolicyMgtSQLConstants.Column.RESOURCE_ID, + policyResource.getResourceId()); + }, + policyResource, + false); + } + + return null; + }); + } catch (TransactionException e) { + throw PolicyManagementExceptionHandler.handleServerException( + ErrorMessage.ERROR_WHILE_UPDATING_POLICY, e); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Policy updated with ID: " + policy.getId()); + } + return policy; + } + + @Override + public void deletePolicy(String policyId, int tenantId) throws PolicyManagementException { + + NamedJdbcTemplate jdbcTemplate = + new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + jdbcTemplate.withTransaction(template -> { + template.executeUpdate( + PolicyMgtSQLConstants.Query.DELETE_POLICY, + preparedStatement -> { + preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_ID, policyId); + preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId); + }); + return null; + }); + } catch (TransactionException e) { + throw PolicyManagementExceptionHandler.handleServerException( + ErrorMessage.ERROR_WHILE_DELETING_POLICY, e); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Policy deleted with ID: " + policyId); + } + } + + @Override + public Policy getPolicyById(String policyId, int tenantId) throws PolicyManagementException { + + String tenantDomain = IdentityTenantUtil.getTenantDomain(tenantId); + NamedJdbcTemplate jdbcTemplate = + new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + return jdbcTemplate.withTransaction(template -> { + Policy base = template.fetchSingleRecord( + PolicyMgtSQLConstants.Query.GET_POLICY_BY_ID, + (resultSet, rowNumber) -> new Policy( + resultSet.getString(PolicyMgtSQLConstants.Column.ID), + resultSet.getString(PolicyMgtSQLConstants.Column.POLICY_NAME), + tenantDomain, null), + preparedStatement -> { + preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_ID, policyId); + preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId); + }); + if (base == null) { + return null; + } + List resources = fetchPolicyResources(template, base.getId()); + return new Policy(base.getId(), base.getName(), tenantDomain, resources); + }); + } catch (TransactionException e) { + throw PolicyManagementExceptionHandler.handleServerException( + ErrorMessage.ERROR_WHILE_RETRIEVING_POLICY, e); + } + } + + @Override + public Policy getPolicyByName(String policyName, int tenantId) throws PolicyManagementException { + + String tenantDomain = IdentityTenantUtil.getTenantDomain(tenantId); + NamedJdbcTemplate jdbcTemplate = + new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + return jdbcTemplate.withTransaction(template -> { + Policy base = template.fetchSingleRecord( + PolicyMgtSQLConstants.Query.GET_POLICY_BY_NAME, + (resultSet, rowNumber) -> new Policy( + resultSet.getString(PolicyMgtSQLConstants.Column.ID), + resultSet.getString(PolicyMgtSQLConstants.Column.POLICY_NAME), + tenantDomain, null), + preparedStatement -> { + preparedStatement.setString(PolicyMgtSQLConstants.Column.POLICY_NAME, policyName); + preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId); + }); + if (base == null) { + return null; + } + List resources = fetchPolicyResources(template, base.getId()); + return new Policy(base.getId(), base.getName(), tenantDomain, resources); + }); + } catch (TransactionException e) { + throw PolicyManagementExceptionHandler.handleServerException( + ErrorMessage.ERROR_WHILE_RETRIEVING_POLICY, e); + } + } + + @Override + public String getPolicyIdByName(String policyName, int tenantId) throws PolicyManagementException { + + NamedJdbcTemplate jdbcTemplate = + new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + return jdbcTemplate.withTransaction( + template -> template.fetchSingleRecord( + PolicyMgtSQLConstants.Query.CHECK_POLICY_NAME_EXISTS, + (resultSet, rowNumber) -> + resultSet.getString(PolicyMgtSQLConstants.Column.ID), + preparedStatement -> { + preparedStatement.setString( + PolicyMgtSQLConstants.Column.POLICY_NAME, policyName); + preparedStatement.setInt( + PolicyMgtSQLConstants.Column.TENANT_ID, tenantId); + })); + } catch (TransactionException e) { + throw PolicyManagementExceptionHandler.handleServerException( + ErrorMessage.ERROR_WHILE_RETRIEVING_POLICY, e); + } + } + + @Override + public List getPolicies(int tenantId, String filter, int offset, int limit) + throws PolicyManagementException { + + // FETCH NEXT 0 ROWS (MS SQL) is invalid and an empty page is meaningless, so short-circuit. + if (limit <= 0) { + return Collections.emptyList(); + } + int safeOffset = Math.max(offset, 0); + boolean hasFilter = filter != null && !filter.trim().isEmpty(); + String filterValue = hasFilter ? "%" + filter.trim() + "%" : null; + + NamedJdbcTemplate jdbcTemplate = + new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + PaginationStyle style = resolvePaginationStyle(); + String query = resolvePaginatedQuery(style, hasFilter); + List policies = jdbcTemplate., RuntimeException>withTransaction( + template -> template.executeQuery( + query, + (resultSet, rowNumber) -> new PolicyBasicInfo( + resultSet.getString(PolicyMgtSQLConstants.Column.ID), + resultSet.getString(PolicyMgtSQLConstants.Column.POLICY_NAME)), + preparedStatement -> { + preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId); + if (hasFilter) { + preparedStatement.setString(PolicyMgtSQLConstants.Column.FILTER, filterValue); + } + bindPaginationParams(preparedStatement, style, safeOffset, limit); + })); + return policies != null ? policies : Collections.emptyList(); + } catch (TransactionException | DataAccessException e) { + throw PolicyManagementExceptionHandler.handleServerException( + ErrorMessage.ERROR_WHILE_RETRIEVING_POLICY, e); + } + } + + @Override + public int getPolicyCount(int tenantId, String filter) throws PolicyManagementException { + + boolean hasFilter = filter != null && !filter.trim().isEmpty(); + String filterValue = hasFilter ? "%" + filter.trim() + "%" : null; + String query = hasFilter ? PolicyMgtSQLConstants.Query.GET_POLICIES_COUNT_FILTER + : PolicyMgtSQLConstants.Query.GET_POLICIES_COUNT; + + NamedJdbcTemplate jdbcTemplate = + new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + Integer count = jdbcTemplate.withTransaction( + template -> template.fetchSingleRecord( + query, + (resultSet, rowNumber) -> resultSet.getInt(1), + preparedStatement -> { + preparedStatement.setInt(PolicyMgtSQLConstants.Column.TENANT_ID, tenantId); + if (hasFilter) { + preparedStatement.setString(PolicyMgtSQLConstants.Column.FILTER, filterValue); + } + })); + return count != null ? count : 0; + } catch (TransactionException e) { + throw PolicyManagementExceptionHandler.handleServerException( + ErrorMessage.ERROR_WHILE_RETRIEVING_POLICY, e); + } + } + + /** + * Supported database-specific pagination dialects. + */ + private enum PaginationStyle { + DEFAULT, MSSQL, ORACLE, DB2 + } + + private PaginationStyle resolvePaginationStyle() throws DataAccessException { + + // H2, MySQL, MariaDB and PostgreSQL all accept the LIMIT ... OFFSET ... syntax (DEFAULT). + if (JdbcUtils.isOracleDB()) { + return PaginationStyle.ORACLE; + } + if (JdbcUtils.isDB2DB()) { + return PaginationStyle.DB2; + } + if (JdbcUtils.isMSSqlDB()) { + return PaginationStyle.MSSQL; + } + return PaginationStyle.DEFAULT; + } + + private String resolvePaginatedQuery(PaginationStyle style, boolean hasFilter) { + + switch (style) { + case ORACLE: + return hasFilter ? PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_FILTER_ORACLE + : PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_ORACLE; + case DB2: + return hasFilter ? PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_FILTER_DB2 + : PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_DB2; + case MSSQL: + return hasFilter ? PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_FILTER_MSSQL + : PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_MSSQL; + default: + return hasFilter ? PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED_FILTER + : PolicyMgtSQLConstants.Query.GET_POLICIES_PAGINATED; + } + } + + private void bindPaginationParams(NamedPreparedStatement preparedStatement, PaginationStyle style, + int offset, int limit) throws SQLException { + + switch (style) { + case ORACLE: + preparedStatement.setInt(PolicyMgtSQLConstants.Column.UPPER_BOUND, offset + limit); + preparedStatement.setInt(PolicyMgtSQLConstants.Column.OFFSET, offset); + break; + case DB2: + preparedStatement.setInt(PolicyMgtSQLConstants.Column.LOWER_BOUND, offset + 1); + preparedStatement.setInt(PolicyMgtSQLConstants.Column.UPPER_BOUND, offset + limit); + break; + case MSSQL: + preparedStatement.setInt(PolicyMgtSQLConstants.Column.OFFSET, offset); + preparedStatement.setInt(PolicyMgtSQLConstants.Column.LIMIT, limit); + break; + default: + preparedStatement.setInt(PolicyMgtSQLConstants.Column.LIMIT, limit); + preparedStatement.setInt(PolicyMgtSQLConstants.Column.OFFSET, offset); + } + } + + private List fetchPolicyResources(NamedTemplate template, String policyId) + throws DataAccessException { + + List resources = template.executeQuery( + PolicyMgtSQLConstants.Query.GET_POLICY_RESOURCES, + (resultSet, rowNumber) -> new PolicyResource( + resultSet.getString(PolicyMgtSQLConstants.Column.ID), + resultSet.getString(PolicyMgtSQLConstants.Column.TARGET), + ResourceType.valueOf(resultSet.getString(PolicyMgtSQLConstants.Column.RESOURCE_TYPE)), + resultSet.getString(PolicyMgtSQLConstants.Column.RESOURCE_ID), + null), + preparedStatement -> preparedStatement.setString( + PolicyMgtSQLConstants.Column.POLICY_ID, policyId)); + return resources != null ? resources : Collections.emptyList(); + } + +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/service/impl/PolicyEvaluationServiceImpl.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/service/impl/PolicyEvaluationServiceImpl.java new file mode 100644 index 000000000000..4cb9b97add13 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/service/impl/PolicyEvaluationServiceImpl.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2026, 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.policy.management.internal.service.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException; +import org.wso2.carbon.identity.policy.management.api.model.Policy; +import org.wso2.carbon.identity.policy.management.api.model.PolicyResource; +import org.wso2.carbon.identity.policy.management.api.model.ResourceType; +import org.wso2.carbon.identity.policy.management.api.service.PolicyEvaluationService; +import org.wso2.carbon.identity.policy.management.internal.component.PolicyMgtComponentServiceHolder; +import org.wso2.carbon.identity.rule.evaluation.api.exception.RuleEvaluationException; +import org.wso2.carbon.identity.rule.evaluation.api.model.FlowContext; +import org.wso2.carbon.identity.rule.evaluation.api.model.RuleEvaluationResult; + +/** + * Default implementation of {@link PolicyEvaluationService}. + */ +public class PolicyEvaluationServiceImpl implements PolicyEvaluationService { + + private static final Log LOG = LogFactory.getLog(PolicyEvaluationServiceImpl.class); + + @Override + public RuleEvaluationResult evaluate(String policyName, String ruleSelector, + FlowContext flowContext, String tenantDomain) + throws PolicyManagementException, RuleEvaluationException { + + Policy policy = PolicyMgtComponentServiceHolder.getInstance() + .getPolicyManagementService() + .getPolicyByName(policyName, tenantDomain); + + if (policy == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Policy not found: " + policyName + " for tenant: " + tenantDomain); + } + return null; + } + + if (ruleSelector == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Rule selector is null for policy '" + policyName + "' — treating as compliant."); + } + return new RuleEvaluationResult(null, true); + } + + PolicyResource matchingResource = policy.getResources().stream() + .filter(r -> r.getResourceType() == ResourceType.RULE + && r.getTarget() != null + && ruleSelector.equalsIgnoreCase(r.getTarget())) + .findFirst() + .orElse(null); + + if (matchingResource == null || matchingResource.getRule() == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("No rule for selector '" + ruleSelector + "' in policy '" + policyName + + "' — treating as compliant."); + } + return new RuleEvaluationResult(null, true); + } + + return PolicyMgtComponentServiceHolder.getInstance() + .getRuleEvaluationService() + .evaluate(matchingResource.getRule().getId(), flowContext, tenantDomain); + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/service/impl/PolicyManagementServiceImpl.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/service/impl/PolicyManagementServiceImpl.java new file mode 100644 index 000000000000..0a6a92ab07a7 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/main/java/org/wso2/carbon/identity/policy/management/internal/service/impl/PolicyManagementServiceImpl.java @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * 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.policy.management.internal.service.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.policy.management.api.constant.ErrorMessage; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementClientException; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException; +import org.wso2.carbon.identity.policy.management.api.model.Policy; +import org.wso2.carbon.identity.policy.management.api.model.PolicyBasicInfo; +import org.wso2.carbon.identity.policy.management.api.model.PolicyResource; +import org.wso2.carbon.identity.policy.management.api.model.ResourceType; +import org.wso2.carbon.identity.policy.management.api.service.PolicyManagementService; +import org.wso2.carbon.identity.policy.management.api.util.PolicyManagementExceptionHandler; +import org.wso2.carbon.identity.policy.management.internal.component.PolicyMgtComponentServiceHolder; +import org.wso2.carbon.identity.policy.management.internal.dao.PolicyManagementDAO; +import org.wso2.carbon.identity.policy.management.internal.dao.impl.CacheBackedPolicyManagementDAO; +import org.wso2.carbon.identity.policy.management.internal.dao.impl.PolicyManagementDAOImpl; +import org.wso2.carbon.identity.rule.management.api.exception.RuleManagementException; +import org.wso2.carbon.identity.rule.management.api.model.Rule; +import org.wso2.carbon.identity.rule.management.api.service.RuleManagementService; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.UUID; + +/** + * Implementation of Policy Management Service. + * Orchestrates rule-mgt service calls and best-effort saga compensation around the DAO layer. + */ +public class PolicyManagementServiceImpl implements PolicyManagementService { + + private static final Log LOG = LogFactory.getLog(PolicyManagementServiceImpl.class); + private final PolicyManagementDAO policyManagementDAO; + + /** + * Default constructor used by OSGi component. Delegates to the DAO-backed constructor. + */ + public PolicyManagementServiceImpl() { + + this(new CacheBackedPolicyManagementDAO(new PolicyManagementDAOImpl())); + } + + /** + * Constructor for tests or manual instantiation with a custom DAO. + * + * @param policyManagementDAO DAO implementation to use. + */ + public PolicyManagementServiceImpl(PolicyManagementDAO policyManagementDAO) { + + this.policyManagementDAO = policyManagementDAO; + } + + @Override + public Policy addPolicy(Policy policy, String tenantDomain) throws PolicyManagementException { + + validatePolicyFields(policy); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Creating policy with name: %s for tenant: %s", + policy.getName(), tenantDomain)); + } + int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); + validateUniquePolicyName(policy.getName(), null, tenantId); + + Policy policyWithId = new Policy( + UUID.randomUUID().toString(), + policy.getName(), + tenantDomain, + policy.getResources()); + + RuleManagementService ruleManagementService = + PolicyMgtComponentServiceHolder.getInstance().getRuleManagementService(); + List createdRuleIds = new ArrayList<>(); + List resourcesWithIds = new ArrayList<>(); + + try { + for (PolicyResource pr : policyWithId.getResources()) { + if (pr.getResourceType() != ResourceType.RULE) { + // ACTION (and future types): resourceId already references an existing resource. + resourcesWithIds.add(pr); + continue; + } + Rule createdRule = ruleManagementService.addRule(pr.getRule(), tenantDomain); + createdRuleIds.add(createdRule.getId()); + resourcesWithIds.add(new PolicyResource( + pr.getId(), pr.getTarget(), ResourceType.RULE, createdRule.getId(), null)); + if (LOG.isDebugEnabled()) { + LOG.debug("Rule added for policy target '" + pr.getTarget() + + "' with ruleId: " + createdRule.getId()); + } + } + } catch (RuleManagementException e) { + compensateCreatedRules(createdRuleIds, tenantDomain, ruleManagementService); + throw PolicyManagementExceptionHandler.handleServerException( + ErrorMessage.ERROR_WHILE_ADDING_RULE_FOR_POLICY, e, policyWithId.getName()); + } + + Policy policyWithResourceIds = new Policy( + policyWithId.getId(), policyWithId.getName(), tenantDomain, resourcesWithIds); + + try { + return policyManagementDAO.addPolicy(policyWithResourceIds, tenantId); + } catch (PolicyManagementException e) { + compensateCreatedRules(createdRuleIds, tenantDomain, ruleManagementService); + throw e; + } + } + + @Override + public Policy updatePolicy(Policy policy, String tenantDomain) throws PolicyManagementException { + + validatePolicyFields(policy); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Updating policy with ID: %s for tenant: %s", + policy.getId(), tenantDomain)); + } + int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); + Policy existingPolicy = policyManagementDAO.getPolicyById(policy.getId(), tenantId); + if (existingPolicy == null) { + throw PolicyManagementExceptionHandler.handleClientException( + ErrorMessage.ERROR_POLICY_NOT_FOUND, policy.getId()); + } + validateUniquePolicyName(policy.getName(), policy.getId(), tenantId); + + RuleManagementService ruleManagementService = + PolicyMgtComponentServiceHolder.getInstance().getRuleManagementService(); + + // Create the new rules first so the old rules remain intact until the DB commit succeeds. The old rules + // are only deleted after the policy is durably updated, keeping the operation recoverable on any failure. + List createdRuleIds = new ArrayList<>(); + List resourcesWithIds = new ArrayList<>(); + + try { + for (PolicyResource pr : policy.getResources()) { + if (pr.getResourceType() != ResourceType.RULE) { + // ACTION (and future types): resourceId already references an existing resource. + resourcesWithIds.add(pr); + continue; + } + Rule createdRule = ruleManagementService.addRule(pr.getRule(), tenantDomain); + createdRuleIds.add(createdRule.getId()); + resourcesWithIds.add(new PolicyResource( + pr.getId(), pr.getTarget(), ResourceType.RULE, createdRule.getId(), null)); + } + } catch (RuleManagementException e) { + compensateCreatedRules(createdRuleIds, tenantDomain, ruleManagementService); + throw PolicyManagementExceptionHandler.handleServerException( + ErrorMessage.ERROR_WHILE_UPDATING_RULE_FOR_POLICY, e, policy.getId()); + } + + Policy policyWithResourceIds = new Policy( + policy.getId(), policy.getName(), tenantDomain, resourcesWithIds); + + Policy updatedPolicy; + try { + updatedPolicy = policyManagementDAO.updatePolicy(policyWithResourceIds, tenantId); + } catch (PolicyManagementException e) { + compensateCreatedRules(createdRuleIds, tenantDomain, ruleManagementService); + throw e; + } + + // DB commit succeeded; the old rules are now safe to remove (best-effort). + deleteRulesFromRuleManagementService(existingPolicy.getResources(), tenantDomain, ruleManagementService, + policy.getId()); + + return updatedPolicy; + } + + @Override + public void deletePolicy(String policyId, String tenantDomain) throws PolicyManagementException { + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Deleting policy with ID: %s for tenant: %s", + policyId, tenantDomain)); + } + int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); + RuleManagementService ruleManagementService = + PolicyMgtComponentServiceHolder.getInstance().getRuleManagementService(); + Policy existingPolicy = policyManagementDAO.getPolicyById(policyId, tenantId); + if (existingPolicy == null) { + return; + } + policyManagementDAO.deletePolicy(policyId, tenantId); + deleteRulesFromRuleManagementService(existingPolicy.getResources(), tenantDomain, ruleManagementService, + policyId); + } + + @Override + public Policy getPolicyById(String policyId, String tenantDomain) throws PolicyManagementException { + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Retrieving policy with ID: %s for tenant: %s", + policyId, tenantDomain)); + } + int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); + Policy policy = policyManagementDAO.getPolicyById(policyId, tenantId); + if (policy == null) { + return null; + } + return hydrateResources(policy, tenantDomain); + } + + @Override + public Policy getPolicyByName(String policyName, String tenantDomain) throws PolicyManagementException { + + if (policyName == null || policyName.trim().isEmpty()) { + throw PolicyManagementExceptionHandler.handleClientException( + ErrorMessage.ERROR_INVALID_POLICY_REQUEST_FIELD, "Policy name"); + } + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Retrieving policy with name: %s for tenant: %s", + policyName, tenantDomain)); + } + int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); + Policy policy = policyManagementDAO.getPolicyByName(policyName, tenantId); + if (policy == null) { + return null; + } + return hydrateResources(policy, tenantDomain); + } + + @Override + public List getPolicies(String tenantDomain, String filter, int offset, int limit) + throws PolicyManagementException { + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Listing policies for tenant: %s with filter: %s, offset: %d, limit: %d", + tenantDomain, filter, offset, limit)); + } + return policyManagementDAO.getPolicies( + IdentityTenantUtil.getTenantId(tenantDomain), filter, offset, limit); + } + + @Override + public int getPolicyCount(String tenantDomain, String filter) throws PolicyManagementException { + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Counting policies for tenant: %s with filter: %s", tenantDomain, filter)); + } + return policyManagementDAO.getPolicyCount(IdentityTenantUtil.getTenantId(tenantDomain), filter); + } + + // Only RULE resources are hydrated; actions are referenced by id and need no hydration here. + private Policy hydrateResources(Policy policy, String tenantDomain) throws PolicyManagementException { + + List hydratedResources = new ArrayList<>(); + for (PolicyResource pr : policy.getResources()) { + if (pr.getResourceType() != ResourceType.RULE) { + hydratedResources.add(pr); + continue; + } + try { + Rule rule = PolicyMgtComponentServiceHolder.getInstance() + .getRuleManagementService() + .getRuleByRuleId(pr.getResourceId(), tenantDomain); + hydratedResources.add(new PolicyResource( + pr.getId(), pr.getTarget(), ResourceType.RULE, pr.getResourceId(), rule)); + } catch (RuleManagementException e) { + throw PolicyManagementExceptionHandler.handleServerException( + ErrorMessage.ERROR_WHILE_RETRIEVING_POLICY, e); + } + } + return new Policy(policy.getId(), policy.getName(), policy.getTenantDomain(), hydratedResources); + } + + private void deleteRulesFromRuleManagementService(List resources, String tenantDomain, + RuleManagementService ruleManagementService, + String policyId) { + + for (PolicyResource pr : resources) { + if (pr.getResourceType() != ResourceType.RULE) { + continue; + } + try { + ruleManagementService.deleteRule(pr.getResourceId(), tenantDomain); + } catch (RuleManagementException e) { + LOG.error("Failed to delete rule " + pr.getResourceId() + + " from rule-mgt for policy " + policyId + ". Rule may be orphaned.", e); + } + } + } + + private void compensateCreatedRules(List ruleIds, String tenantDomain, + RuleManagementService ruleManagementService) { + + for (String ruleId : ruleIds) { + try { + ruleManagementService.deleteRule(ruleId, tenantDomain); + } catch (RuleManagementException ex) { + LOG.error("Saga compensation failed: could not delete rule " + ruleId + + " from rule-mgt after policy persistence failure.", ex); + } + } + } + + private void validatePolicyFields(Policy policy) throws PolicyManagementClientException { + + if (policy == null) { + throw PolicyManagementExceptionHandler.handleClientException( + ErrorMessage.ERROR_INVALID_POLICY_REQUEST_FIELD, "Policy"); + } + if (policy.getName() == null || policy.getName().trim().isEmpty()) { + throw PolicyManagementExceptionHandler.handleClientException( + ErrorMessage.ERROR_INVALID_POLICY_REQUEST_FIELD, "Policy name"); + } + validateUniqueTargetsPerResourceType(policy); + } + + private void validateUniqueTargetsPerResourceType(Policy policy) throws PolicyManagementClientException { + + Set seenTargets = new HashSet<>(); + for (PolicyResource resource : policy.getResources()) { + if (resource == null || resource.getTarget() == null) { + continue; + } + if (resource.getResourceType() == null) { + throw PolicyManagementExceptionHandler.handleClientException( + ErrorMessage.ERROR_INVALID_POLICY_REQUEST_FIELD, "Resource type"); + } + String key = resource.getResourceType().name() + "|" + + resource.getTarget().toLowerCase(Locale.ROOT); + if (!seenTargets.add(key)) { + throw PolicyManagementExceptionHandler.handleClientException( + ErrorMessage.ERROR_DUPLICATE_PLATFORM_IN_POLICY, + policy.getName(), resource.getTarget()); + } + } + } + + private void validateUniquePolicyName(String name, String excludePolicyId, int tenantId) + throws PolicyManagementException { + + String existingId = policyManagementDAO.getPolicyIdByName(name, tenantId); + if (existingId != null && !existingId.equals(excludePolicyId)) { + throw PolicyManagementExceptionHandler.handleClientException( + ErrorMessage.ERROR_POLICY_ALREADY_EXISTS, name); + } + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/dao/CacheBackedPolicyManagementDAOTest.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/dao/CacheBackedPolicyManagementDAOTest.java new file mode 100644 index 000000000000..298049b7778e --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/dao/CacheBackedPolicyManagementDAOTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2026, 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.policy.management.dao; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.wso2.carbon.identity.common.testng.WithCarbonHome; +import org.wso2.carbon.identity.common.testng.WithRealmService; +import org.wso2.carbon.identity.core.internal.component.IdentityCoreServiceDataHolder; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException; +import org.wso2.carbon.identity.policy.management.api.model.Policy; +import org.wso2.carbon.identity.policy.management.internal.cache.PolicyCache; +import org.wso2.carbon.identity.policy.management.internal.cache.PolicyCacheEntry; +import org.wso2.carbon.identity.policy.management.internal.cache.PolicyCacheKey; +import org.wso2.carbon.identity.policy.management.internal.dao.PolicyManagementDAO; +import org.wso2.carbon.identity.policy.management.internal.dao.impl.CacheBackedPolicyManagementDAO; + +import java.util.Collections; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; + +/** + * Unit tests for CacheBackedPolicyManagementDAO. + * Verifies name-only caching, pass-through for ID lookups, and precise cache invalidation on writes. + */ +@WithCarbonHome +@WithRealmService(injectToSingletons = {IdentityCoreServiceDataHolder.class}) +public class CacheBackedPolicyManagementDAOTest { + + private static final String POLICY_ID = "policyId"; + private static final String POLICY_NAME = "TestPolicy"; + private static final String TENANT_DOMAIN = "carbon.super"; + private static final int TENANT_ID = 1; + + private PolicyManagementDAO policyManagementDAO; + private CacheBackedPolicyManagementDAO cacheBackedPolicyManagementDAO; + private PolicyCache policyCache; + + @BeforeClass + public void setUpClass() { + + policyCache = PolicyCache.getInstance(); + } + + @BeforeMethod + public void setUp() { + + policyManagementDAO = mock(PolicyManagementDAO.class); + cacheBackedPolicyManagementDAO = new CacheBackedPolicyManagementDAO(policyManagementDAO); + policyCache.clear(TENANT_ID); + } + + private Policy policy() { + + return new Policy(POLICY_ID, POLICY_NAME, TENANT_DOMAIN, Collections.emptyList()); + } + + @Test + public void testAddPolicyDoesNotCache() throws PolicyManagementException { + + Policy policy = policy(); + + cacheBackedPolicyManagementDAO.addPolicy(policy, TENANT_ID); + + verify(policyManagementDAO).addPolicy(policy, TENANT_ID); + assertNull(policyCache.getValueFromCache(new PolicyCacheKey(POLICY_NAME), TENANT_ID)); + } + + @Test + public void testGetPolicyByNameCacheMissPopulatesNameEntry() throws PolicyManagementException { + + Policy policy = policy(); + when(policyManagementDAO.getPolicyByName(POLICY_NAME, TENANT_ID)).thenReturn(policy); + + Policy result = cacheBackedPolicyManagementDAO.getPolicyByName(POLICY_NAME, TENANT_ID); + + assertEquals(result, policy); + verify(policyManagementDAO).getPolicyByName(POLICY_NAME, TENANT_ID); + assertEquals(policyCache.getValueFromCache(new PolicyCacheKey(POLICY_NAME), TENANT_ID).getPolicy(), + policy); + } + + @Test + public void testGetPolicyByNameCacheHit() throws PolicyManagementException { + + Policy policy = policy(); + policyCache.addToCacheOnRead(new PolicyCacheKey(POLICY_NAME), new PolicyCacheEntry(policy), TENANT_ID); + + Policy result = cacheBackedPolicyManagementDAO.getPolicyByName(POLICY_NAME, TENANT_ID); + + assertEquals(result, policy); + verify(policyManagementDAO, never()).getPolicyByName(POLICY_NAME, TENANT_ID); + } + + @Test + public void testGetPolicyByIdAlwaysDelegates() throws PolicyManagementException { + + Policy policy = policy(); + when(policyManagementDAO.getPolicyById(POLICY_ID, TENANT_ID)).thenReturn(policy); + + Policy first = cacheBackedPolicyManagementDAO.getPolicyById(POLICY_ID, TENANT_ID); + Policy second = cacheBackedPolicyManagementDAO.getPolicyById(POLICY_ID, TENANT_ID); + + assertEquals(first, policy); + assertEquals(second, policy); + // Both calls must go to the underlying DAO — getPolicyById is never cached. + verify(policyManagementDAO, times(2)).getPolicyById(POLICY_ID, TENANT_ID); + } + + @Test + public void testUpdatePolicyClearsNameEntry() throws PolicyManagementException { + + Policy policy = policy(); + policyCache.addToCacheOnRead(new PolicyCacheKey(POLICY_NAME), new PolicyCacheEntry(policy), TENANT_ID); + when(policyManagementDAO.getPolicyById(POLICY_ID, TENANT_ID)).thenReturn(policy); + when(policyManagementDAO.updatePolicy(policy, TENANT_ID)).thenReturn(policy); + + cacheBackedPolicyManagementDAO.updatePolicy(policy, TENANT_ID); + + verify(policyManagementDAO).updatePolicy(policy, TENANT_ID); + assertNull(policyCache.getValueFromCache(new PolicyCacheKey(POLICY_NAME), TENANT_ID)); + } + + @Test + public void testUpdatePolicyRenamesClearsBothOldAndNewName() throws PolicyManagementException { + + String oldName = "OldPolicy"; + Policy existing = new Policy(POLICY_ID, oldName, TENANT_DOMAIN, Collections.emptyList()); + Policy renamed = new Policy(POLICY_ID, POLICY_NAME, TENANT_DOMAIN, Collections.emptyList()); + policyCache.addToCacheOnRead(new PolicyCacheKey(oldName), new PolicyCacheEntry(existing), TENANT_ID); + policyCache.addToCacheOnRead(new PolicyCacheKey(POLICY_NAME), new PolicyCacheEntry(existing), TENANT_ID); + when(policyManagementDAO.getPolicyById(POLICY_ID, TENANT_ID)).thenReturn(existing); + when(policyManagementDAO.updatePolicy(renamed, TENANT_ID)).thenReturn(renamed); + + cacheBackedPolicyManagementDAO.updatePolicy(renamed, TENANT_ID); + + assertNull(policyCache.getValueFromCache(new PolicyCacheKey(oldName), TENANT_ID)); + assertNull(policyCache.getValueFromCache(new PolicyCacheKey(POLICY_NAME), TENANT_ID)); + } + + @Test + public void testDeletePolicyClearsNameEntry() throws PolicyManagementException { + + Policy policy = policy(); + policyCache.addToCacheOnRead(new PolicyCacheKey(POLICY_NAME), new PolicyCacheEntry(policy), TENANT_ID); + when(policyManagementDAO.getPolicyById(POLICY_ID, TENANT_ID)).thenReturn(policy); + + cacheBackedPolicyManagementDAO.deletePolicy(POLICY_ID, TENANT_ID); + + verify(policyManagementDAO).deletePolicy(POLICY_ID, TENANT_ID); + assertNull(policyCache.getValueFromCache(new PolicyCacheKey(POLICY_NAME), TENANT_ID)); + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/dao/PolicyManagementDAOImplTest.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/dao/PolicyManagementDAOImplTest.java new file mode 100644 index 000000000000..ea428b6c196a --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/dao/PolicyManagementDAOImplTest.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2026, 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.policy.management.dao; + +import org.mockito.MockedStatic; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.wso2.carbon.identity.common.testng.WithCarbonHome; +import org.wso2.carbon.identity.common.testng.WithH2Database; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException; +import org.wso2.carbon.identity.policy.management.api.model.Policy; +import org.wso2.carbon.identity.policy.management.api.model.PolicyBasicInfo; +import org.wso2.carbon.identity.policy.management.api.model.PolicyResource; +import org.wso2.carbon.identity.policy.management.api.model.ResourceType; +import org.wso2.carbon.identity.policy.management.internal.dao.impl.PolicyManagementDAOImpl; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.mockito.Mockito.mockStatic; + +/** + * Unit tests for PolicyManagementDAOImpl. + */ +@WithCarbonHome +@WithH2Database(files = {"dbscripts/h2.sql"}) +public class PolicyManagementDAOImplTest { + + private static final int TENANT_ID = -1234; + private static final String TENANT_DOMAIN = "carbon.super"; + private static final String TEST_POLICY_NAME = "TestPolicy"; + private static final String TEST_RULE_ID = UUID.randomUUID().toString(); + + private PolicyManagementDAOImpl policyManagementDAO; + private MockedStatic identityTenantUtil; + private String createdPolicyId; + + @BeforeClass + public void setUp() { + + policyManagementDAO = new PolicyManagementDAOImpl(); + identityTenantUtil = mockStatic(IdentityTenantUtil.class); + identityTenantUtil.when(() -> IdentityTenantUtil.getTenantDomain(TENANT_ID)) + .thenReturn(TENANT_DOMAIN); + identityTenantUtil.when(() -> IdentityTenantUtil.getTenantId(TENANT_DOMAIN)) + .thenReturn(TENANT_ID); + } + + @AfterClass + public void tearDown() { + + identityTenantUtil.close(); + } + + @Test(priority = 1) + public void testAddPolicy() throws PolicyManagementException { + + List resources = Collections.singletonList( + new PolicyResource(null, "android", ResourceType.RULE, TEST_RULE_ID, null)); + + Policy policy = new Policy( + UUID.randomUUID().toString(), + TEST_POLICY_NAME, + TENANT_DOMAIN, + resources); + + Policy result = policyManagementDAO.addPolicy(policy, TENANT_ID); + + Assert.assertNotNull(result); + Assert.assertEquals(result.getName(), TEST_POLICY_NAME); + Assert.assertEquals(result.getResources().size(), 1); + Assert.assertEquals(result.getResources().get(0).getTarget(), "android"); + + createdPolicyId = result.getId(); + } + + @Test(priority = 2, dependsOnMethods = {"testAddPolicy"}) + public void testGetPolicyById() throws PolicyManagementException { + + Policy result = policyManagementDAO.getPolicyById(createdPolicyId, TENANT_ID); + + Assert.assertNotNull(result); + Assert.assertEquals(result.getId(), createdPolicyId); + Assert.assertEquals(result.getName(), TEST_POLICY_NAME); + Assert.assertEquals(result.getResources().size(), 1); + Assert.assertEquals(result.getResources().get(0).getResourceId(), TEST_RULE_ID); + } + + @Test(priority = 3, dependsOnMethods = {"testAddPolicy"}) + public void testUpdatePolicy() throws PolicyManagementException { + + String updatedName = "UpdatedPolicy"; + String updatedRuleId = UUID.randomUUID().toString(); + + List updatedResources = Collections.singletonList( + new PolicyResource(null, "ios", ResourceType.RULE, updatedRuleId, null)); + + Policy updatedPolicy = new Policy(createdPolicyId, updatedName, TENANT_DOMAIN, updatedResources); + + Policy result = policyManagementDAO.updatePolicy(updatedPolicy, TENANT_ID); + + Assert.assertNotNull(result); + Assert.assertEquals(result.getName(), updatedName); + Assert.assertEquals(result.getResources().size(), 1); + Assert.assertEquals(result.getResources().get(0).getTarget(), "ios"); + } + + @Test(priority = 4, dependsOnMethods = {"testAddPolicy"}) + public void testGetPolicyById_NotFound() throws PolicyManagementException { + + Policy result = policyManagementDAO.getPolicyById( + UUID.randomUUID().toString(), TENANT_ID); + + Assert.assertNull(result); + } + + @Test(priority = 5, dependsOnMethods = {"testAddPolicy"}) + public void testDeletePolicy() throws PolicyManagementException { + + policyManagementDAO.deletePolicy(createdPolicyId, TENANT_ID); + + Policy result = policyManagementDAO.getPolicyById(createdPolicyId, TENANT_ID); + Assert.assertNull(result); + } + + private String addPolicy(String name) throws PolicyManagementException { + + Policy policy = new Policy(UUID.randomUUID().toString(), name, TENANT_DOMAIN, Collections.emptyList()); + return policyManagementDAO.addPolicy(policy, TENANT_ID).getId(); + } + + @Test(priority = 6) + public void testGetPoliciesPaginationAndCount() throws PolicyManagementException { + + addPolicy("PageTestAlpha"); + addPolicy("PageTestBeta"); + addPolicy("PageTestGamma"); + + Assert.assertEquals(policyManagementDAO.getPolicyCount(TENANT_ID, "PageTest"), 3); + + List firstPage = policyManagementDAO.getPolicies(TENANT_ID, "PageTest", 0, 2); + Assert.assertEquals(firstPage.size(), 2); + // Results are ordered by policy name ascending. + Assert.assertEquals(firstPage.get(0).getName(), "PageTestAlpha"); + Assert.assertEquals(firstPage.get(1).getName(), "PageTestBeta"); + + List secondPage = policyManagementDAO.getPolicies(TENANT_ID, "PageTest", 2, 2); + Assert.assertEquals(secondPage.size(), 1); + Assert.assertEquals(secondPage.get(0).getName(), "PageTestGamma"); + + // A non-positive limit yields an empty page. + Assert.assertTrue(policyManagementDAO.getPolicies(TENANT_ID, "PageTest", 0, 0).isEmpty()); + } + + @Test(priority = 7) + public void testGetPoliciesWithFilter() throws PolicyManagementException { + + addPolicy("FilterTestUnique"); + + Assert.assertEquals(policyManagementDAO.getPolicyCount(TENANT_ID, "FilterTestUnique"), 1); + List result = policyManagementDAO.getPolicies(TENANT_ID, "FilterTestUnique", 0, 10); + Assert.assertEquals(result.size(), 1); + Assert.assertEquals(result.get(0).getName(), "FilterTestUnique"); + } + + @Test(priority = 8) + public void testGetPolicyByName() throws PolicyManagementException { + + addPolicy("NameLookupPolicy"); + + Policy result = policyManagementDAO.getPolicyByName("NameLookupPolicy", TENANT_ID); + Assert.assertNotNull(result); + Assert.assertEquals(result.getName(), "NameLookupPolicy"); + } + + @Test(priority = 9) + public void testResourceRoundTripAndTypeStored() throws PolicyManagementException { + + String ruleId = UUID.randomUUID().toString(); + Policy policy = new Policy(UUID.randomUUID().toString(), "ResourceRoundTrip", TENANT_DOMAIN, + Collections.singletonList(new PolicyResource(null, "ios", ResourceType.RULE, ruleId, null))); + String id = policyManagementDAO.addPolicy(policy, TENANT_ID).getId(); + + Policy fetched = policyManagementDAO.getPolicyById(id, TENANT_ID); + Assert.assertEquals(fetched.getResources().size(), 1); + PolicyResource resource = fetched.getResources().get(0); + Assert.assertEquals(resource.getResourceType(), ResourceType.RULE); + Assert.assertEquals(resource.getTarget(), "ios"); + Assert.assertEquals(resource.getResourceId(), ruleId); + } + + @Test(priority = 10, expectedExceptions = PolicyManagementException.class) + public void testDuplicateTargetPerTypeRejected() throws PolicyManagementException { + + // Two RULE resources for the same target violate UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE). + Policy policy = new Policy(UUID.randomUUID().toString(), "DuplicateTargetPolicy", TENANT_DOMAIN, + Arrays.asList( + new PolicyResource(null, "ios", ResourceType.RULE, UUID.randomUUID().toString(), null), + new PolicyResource(null, "ios", ResourceType.RULE, UUID.randomUUID().toString(), null))); + + policyManagementDAO.addPolicy(policy, TENANT_ID); + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/service/PolicyEvaluationServiceImplTest.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/service/PolicyEvaluationServiceImplTest.java new file mode 100644 index 000000000000..8b549db744e5 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/service/PolicyEvaluationServiceImplTest.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2026, 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.policy.management.service; + +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException; +import org.wso2.carbon.identity.policy.management.api.model.Policy; +import org.wso2.carbon.identity.policy.management.api.model.PolicyResource; +import org.wso2.carbon.identity.policy.management.api.model.ResourceType; +import org.wso2.carbon.identity.policy.management.api.service.PolicyManagementService; +import org.wso2.carbon.identity.policy.management.internal.component.PolicyMgtComponentServiceHolder; +import org.wso2.carbon.identity.policy.management.internal.service.impl.PolicyEvaluationServiceImpl; +import org.wso2.carbon.identity.rule.evaluation.api.exception.RuleEvaluationException; +import org.wso2.carbon.identity.rule.evaluation.api.model.FlowContext; +import org.wso2.carbon.identity.rule.evaluation.api.model.RuleEvaluationResult; +import org.wso2.carbon.identity.rule.evaluation.api.service.RuleEvaluationService; +import org.wso2.carbon.identity.rule.management.api.model.Rule; + +import java.util.Collections; +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Unit tests for PolicyEvaluationServiceImpl. + */ +public class PolicyEvaluationServiceImplTest { + + private static final String POLICY_NAME = "TestPolicy"; + private static final String TENANT_DOMAIN = "carbon.super"; + private static final String RULE_ID = UUID.randomUUID().toString(); + + private PolicyManagementService policyManagementService; + private RuleEvaluationService ruleEvaluationService; + private PolicyEvaluationServiceImpl policyEvaluationService; + private FlowContext flowContext; + + @BeforeMethod + public void setUp() { + + policyManagementService = mock(PolicyManagementService.class); + ruleEvaluationService = mock(RuleEvaluationService.class); + PolicyMgtComponentServiceHolder.getInstance().setPolicyManagementService(policyManagementService); + PolicyMgtComponentServiceHolder.getInstance().setRuleEvaluationService(ruleEvaluationService); + policyEvaluationService = new PolicyEvaluationServiceImpl(); + flowContext = mock(FlowContext.class); + } + + private Policy policyWithRule(String target) { + + Rule rule = mock(Rule.class); + when(rule.getId()).thenReturn(RULE_ID); + PolicyResource resource = new PolicyResource( + UUID.randomUUID().toString(), target, ResourceType.RULE, RULE_ID, rule); + return new Policy(UUID.randomUUID().toString(), POLICY_NAME, TENANT_DOMAIN, + Collections.singletonList(resource)); + } + + @Test + public void testPolicyNotFoundReturnsNull() throws PolicyManagementException, RuleEvaluationException { + + when(policyManagementService.getPolicyByName(POLICY_NAME, TENANT_DOMAIN)).thenReturn(null); + + RuleEvaluationResult result = policyEvaluationService.evaluate( + POLICY_NAME, "ios", flowContext, TENANT_DOMAIN); + + Assert.assertNull(result); + } + + @Test + public void testNoMatchingResourceReturnsCompliant() throws PolicyManagementException, RuleEvaluationException { + + Policy policy = policyWithRule("android"); + when(policyManagementService.getPolicyByName(POLICY_NAME, TENANT_DOMAIN)).thenReturn(policy); + + RuleEvaluationResult result = policyEvaluationService.evaluate( + POLICY_NAME, "ios", flowContext, TENANT_DOMAIN); + + Assert.assertNotNull(result); + Assert.assertTrue(result.isRuleSatisfied()); + verify(ruleEvaluationService, org.mockito.Mockito.never()) + .evaluate(eq(RULE_ID), eq(flowContext), eq(TENANT_DOMAIN)); + } + + @Test + public void testMatchingRuleDelegatesToRuleEvaluation() throws PolicyManagementException, RuleEvaluationException { + + Policy policy = policyWithRule("ios"); + when(policyManagementService.getPolicyByName(POLICY_NAME, TENANT_DOMAIN)).thenReturn(policy); + RuleEvaluationResult expected = mock(RuleEvaluationResult.class); + when(ruleEvaluationService.evaluate(RULE_ID, flowContext, TENANT_DOMAIN)).thenReturn(expected); + + RuleEvaluationResult result = policyEvaluationService.evaluate( + POLICY_NAME, "ios", flowContext, TENANT_DOMAIN); + + Assert.assertEquals(result, expected); + verify(ruleEvaluationService).evaluate(RULE_ID, flowContext, TENANT_DOMAIN); + } + + @Test + public void testNullSelectorReturnsCompliant() throws PolicyManagementException, RuleEvaluationException { + + Policy policy = policyWithRule("ios"); + when(policyManagementService.getPolicyByName(POLICY_NAME, TENANT_DOMAIN)).thenReturn(policy); + + RuleEvaluationResult result = policyEvaluationService.evaluate( + POLICY_NAME, null, flowContext, TENANT_DOMAIN); + + Assert.assertNotNull(result); + Assert.assertTrue(result.isRuleSatisfied()); + verify(ruleEvaluationService, org.mockito.Mockito.never()) + .evaluate(eq(RULE_ID), eq(flowContext), eq(TENANT_DOMAIN)); + } + + @Test + public void testResourceWithNullTargetIsSkipped() throws PolicyManagementException, RuleEvaluationException { + + Rule rule = mock(Rule.class); + when(rule.getId()).thenReturn(RULE_ID); + // Resource with a null target — the filter must not NPE on equalsIgnoreCase. + PolicyResource nullTargetResource = new PolicyResource( + UUID.randomUUID().toString(), null, ResourceType.RULE, RULE_ID, rule); + Policy policy = new Policy(UUID.randomUUID().toString(), POLICY_NAME, TENANT_DOMAIN, + Collections.singletonList(nullTargetResource)); + when(policyManagementService.getPolicyByName(POLICY_NAME, TENANT_DOMAIN)).thenReturn(policy); + + RuleEvaluationResult result = policyEvaluationService.evaluate( + POLICY_NAME, "ios", flowContext, TENANT_DOMAIN); + + Assert.assertNotNull(result); + Assert.assertTrue(result.isRuleSatisfied()); + verify(ruleEvaluationService, org.mockito.Mockito.never()) + .evaluate(eq(RULE_ID), eq(flowContext), eq(TENANT_DOMAIN)); + } + + @Test + public void testSelectorMatchIsCaseInsensitive() throws PolicyManagementException, RuleEvaluationException { + + Policy policy = policyWithRule("iOS"); + when(policyManagementService.getPolicyByName(POLICY_NAME, TENANT_DOMAIN)).thenReturn(policy); + RuleEvaluationResult expected = mock(RuleEvaluationResult.class); + when(ruleEvaluationService.evaluate(RULE_ID, flowContext, TENANT_DOMAIN)).thenReturn(expected); + + RuleEvaluationResult result = policyEvaluationService.evaluate( + POLICY_NAME, "ios", flowContext, TENANT_DOMAIN); + + Assert.assertEquals(result, expected); + verify(ruleEvaluationService).evaluate(RULE_ID, flowContext, TENANT_DOMAIN); + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/service/PolicyManagementServiceImplTest.java b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/service/PolicyManagementServiceImplTest.java new file mode 100644 index 000000000000..31abda688c15 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/java/org/wso2/carbon/identity/policy/management/service/PolicyManagementServiceImplTest.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2026, 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.policy.management.service; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.wso2.carbon.identity.common.testng.WithCarbonHome; +import org.wso2.carbon.identity.common.testng.WithRealmService; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementClientException; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementException; +import org.wso2.carbon.identity.policy.management.api.exception.PolicyManagementServerException; +import org.wso2.carbon.identity.policy.management.api.model.Policy; +import org.wso2.carbon.identity.policy.management.api.model.PolicyResource; +import org.wso2.carbon.identity.policy.management.api.model.ResourceType; +import org.wso2.carbon.identity.policy.management.internal.component.PolicyMgtComponentServiceHolder; +import org.wso2.carbon.identity.policy.management.internal.dao.PolicyManagementDAO; +import org.wso2.carbon.identity.policy.management.internal.service.impl.PolicyManagementServiceImpl; +import org.wso2.carbon.identity.rule.management.api.exception.RuleManagementException; +import org.wso2.carbon.identity.rule.management.api.model.Rule; +import org.wso2.carbon.identity.rule.management.api.service.RuleManagementService; + +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Unit tests for PolicyManagementServiceImpl. + * Covers validation, rule-mgt orchestration, and best-effort saga compensation. + */ +@WithCarbonHome +@WithRealmService +public class PolicyManagementServiceImplTest { + + private static final String TENANT_DOMAIN = "carbon.super"; + private static final int TENANT_ID = -1234; + private static final String TEST_POLICY_NAME = "TestPolicy"; + private static final String TEST_POLICY_ID = UUID.randomUUID().toString(); + + @Mock + private PolicyManagementDAO policyManagementDAO; + + @Mock + private RuleManagementService ruleManagementService; + + private PolicyManagementServiceImpl policyManagementService; + private MockedStatic identityTenantUtil; + private AutoCloseable mocks; + // Reflection fields removed; tests now construct the service with a mock DAO. + + @BeforeClass + public void setUp() throws Exception { + + mocks = MockitoAnnotations.openMocks(this); + policyManagementService = new PolicyManagementServiceImpl(policyManagementDAO); + + PolicyMgtComponentServiceHolder.getInstance().setRuleManagementService(ruleManagementService); + + identityTenantUtil = mockStatic(IdentityTenantUtil.class); + identityTenantUtil.when(() -> IdentityTenantUtil.getTenantId(TENANT_DOMAIN)).thenReturn(TENANT_ID); + identityTenantUtil.when(() -> IdentityTenantUtil.getTenantDomain(TENANT_ID)).thenReturn(TENANT_DOMAIN); + } + + @AfterClass + public void tearDown() throws Exception { + identityTenantUtil.close(); + mocks.close(); + } + + @BeforeMethod + public void reset() { + + org.mockito.Mockito.reset(policyManagementDAO); + org.mockito.Mockito.reset(ruleManagementService); + } + + // --- Basic validation tests --- + + @Test + public void testAddPolicy() throws PolicyManagementException { + + Policy inputPolicy = new Policy(null, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.emptyList()); + Policy savedPolicy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.emptyList()); + + when(policyManagementDAO.addPolicy(any(Policy.class), eq(TENANT_ID))).thenReturn(savedPolicy); + + Policy result = policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN); + + Assert.assertNotNull(result); + Assert.assertEquals(result.getName(), TEST_POLICY_NAME); + verify(policyManagementDAO).addPolicy(any(Policy.class), eq(TENANT_ID)); + } + + @Test(expectedExceptions = PolicyManagementClientException.class) + public void testAddPolicy_EmptyName() throws PolicyManagementException { + + Policy inputPolicy = new Policy(null, "", TENANT_DOMAIN, + Collections.emptyList()); + policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN); + } + + @Test(expectedExceptions = PolicyManagementClientException.class) + public void testAddPolicy_NullName() throws PolicyManagementException { + + Policy inputPolicy = new Policy(null, null, TENANT_DOMAIN, + Collections.emptyList()); + policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN); + } + + @Test + public void testGetPolicyById() throws PolicyManagementException { + + Policy expectedPolicy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.emptyList()); + + when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(expectedPolicy); + + Policy result = policyManagementService.getPolicyById(TEST_POLICY_ID, TENANT_DOMAIN); + + Assert.assertNotNull(result); + Assert.assertEquals(result.getId(), TEST_POLICY_ID); + Assert.assertEquals(result.getName(), TEST_POLICY_NAME); + verify(policyManagementDAO).getPolicyById(TEST_POLICY_ID, TENANT_ID); + } + + @Test + public void testUpdatePolicy() throws PolicyManagementException { + + Policy existingPolicy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.emptyList()); + Policy updatedPolicy = new Policy(TEST_POLICY_ID, "UpdatedPolicy", TENANT_DOMAIN, + Collections.emptyList()); + + when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(existingPolicy); + when(policyManagementDAO.updatePolicy(any(Policy.class), eq(TENANT_ID))).thenReturn(updatedPolicy); + + Policy result = policyManagementService.updatePolicy(updatedPolicy, TENANT_DOMAIN); + + Assert.assertNotNull(result); + Assert.assertEquals(result.getName(), "UpdatedPolicy"); + verify(policyManagementDAO).updatePolicy(any(Policy.class), eq(TENANT_ID)); + } + + @Test(expectedExceptions = PolicyManagementClientException.class) + public void testUpdatePolicy_PolicyNotFound() throws PolicyManagementException { + + Policy policy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.emptyList()); + + when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(null); + + policyManagementService.updatePolicy(policy, TENANT_DOMAIN); + } + + @Test + public void testDeletePolicy() throws PolicyManagementException { + + Policy existingPolicy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.emptyList()); + + when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(existingPolicy); + + policyManagementService.deletePolicy(TEST_POLICY_ID, TENANT_DOMAIN); + + verify(policyManagementDAO).deletePolicy(TEST_POLICY_ID, TENANT_ID); + } + + @Test + public void testDeletePolicy_PolicyNotExists() throws PolicyManagementException { + + when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(null); + + policyManagementService.deletePolicy(TEST_POLICY_ID, TENANT_DOMAIN); + + verify(policyManagementDAO, org.mockito.Mockito.never()).deletePolicy(any(), eq(TENANT_ID)); + } + + // --- Rule orchestration and saga compensation tests --- + + @Test + public void testAddPolicyWithRuleResource_CreatesRuleAndPersistsWithResourceId() + throws PolicyManagementException, RuleManagementException { + + Rule created = mock(Rule.class); + when(created.getId()).thenReturn("rule-1"); + when(ruleManagementService.addRule(any(Rule.class), eq(TENANT_DOMAIN))).thenReturn(created); + + PolicyResource ruleRes = new PolicyResource(null, "ios", ResourceType.RULE, null, mock(Rule.class)); + Policy inputPolicy = new Policy(null, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.singletonList(ruleRes)); + Policy savedPolicy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.singletonList(ruleRes)); + when(policyManagementDAO.addPolicy(any(Policy.class), eq(TENANT_ID))).thenReturn(savedPolicy); + + policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN); + + verify(ruleManagementService).addRule(any(Rule.class), eq(TENANT_DOMAIN)); + ArgumentCaptor captor = ArgumentCaptor.forClass(Policy.class); + verify(policyManagementDAO).addPolicy(captor.capture(), eq(TENANT_ID)); + PolicyResource persisted = captor.getValue().getResources().get(0); + Assert.assertEquals(persisted.getResourceType(), ResourceType.RULE); + Assert.assertEquals(persisted.getResourceId(), "rule-1"); + Assert.assertEquals(persisted.getTarget(), "ios"); + } + + @Test + public void testAddPolicyWithActionResource_PassesThrough() + throws PolicyManagementException, RuleManagementException { + + PolicyResource action = new PolicyResource(null, "ios", ResourceType.ACTION, "action-1", null); + Policy inputPolicy = new Policy(null, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.singletonList(action)); + Policy savedPolicy = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.singletonList(action)); + when(policyManagementDAO.addPolicy(any(Policy.class), eq(TENANT_ID))).thenReturn(savedPolicy); + + policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN); + + // ACTION resources are not created through rule-mgt; they pass through unchanged. + verify(ruleManagementService, never()).addRule(any(Rule.class), eq(TENANT_DOMAIN)); + ArgumentCaptor captor = ArgumentCaptor.forClass(Policy.class); + verify(policyManagementDAO).addPolicy(captor.capture(), eq(TENANT_ID)); + PolicyResource persisted = captor.getValue().getResources().get(0); + Assert.assertEquals(persisted.getResourceType(), ResourceType.ACTION); + Assert.assertEquals(persisted.getResourceId(), "action-1"); + } + + @Test + public void testAddPolicyWithRule_CompensatesWhenRuleCreationFails() + throws RuleManagementException, PolicyManagementException { + + Rule created = mock(Rule.class); + when(created.getId()).thenReturn("rule-1"); + when(ruleManagementService.addRule(any(Rule.class), eq(TENANT_DOMAIN))) + .thenReturn(created) + .thenThrow(RuleManagementException.class); + + PolicyResource r1 = new PolicyResource(null, "ios", ResourceType.RULE, null, mock(Rule.class)); + PolicyResource r2 = new PolicyResource(null, "android", ResourceType.RULE, null, mock(Rule.class)); + Policy inputPolicy = new Policy(null, TEST_POLICY_NAME, TENANT_DOMAIN, Arrays.asList(r1, r2)); + + try { + policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN); + Assert.fail("Expected PolicyManagementException"); + } catch (PolicyManagementException expected) { + // Expected. + } + + // The first rule succeeded; it must be compensated after the second rule creation fails. + verify(ruleManagementService).deleteRule("rule-1", TENANT_DOMAIN); + verify(policyManagementDAO, never()).addPolicy(any(Policy.class), eq(TENANT_ID)); + } + + @Test + public void testAddPolicyWithRule_CompensatesWhenPersistenceFails() + throws RuleManagementException, PolicyManagementException { + + Rule created = mock(Rule.class); + when(created.getId()).thenReturn("rule-1"); + when(ruleManagementService.addRule(any(Rule.class), eq(TENANT_DOMAIN))).thenReturn(created); + when(policyManagementDAO.addPolicy(any(Policy.class), eq(TENANT_ID))) + .thenThrow(PolicyManagementServerException.class); + + PolicyResource ruleRes = new PolicyResource(null, "ios", ResourceType.RULE, null, mock(Rule.class)); + Policy inputPolicy = new Policy(null, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.singletonList(ruleRes)); + + try { + policyManagementService.addPolicy(inputPolicy, TENANT_DOMAIN); + Assert.fail("Expected PolicyManagementException"); + } catch (PolicyManagementException expected) { + // Expected. + } + + // The rule created before the DB write failed must be compensated. + verify(ruleManagementService).deleteRule("rule-1", TENANT_DOMAIN); + } + + @Test + public void testUpdatePolicyWithRuleResource_DeletesOldRulesAndAddsNew() + throws PolicyManagementException, RuleManagementException { + + Policy existing = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.singletonList( + new PolicyResource(null, "ios", ResourceType.RULE, "old-rule", null))); + when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(existing); + + Rule created = mock(Rule.class); + when(created.getId()).thenReturn("new-rule"); + when(ruleManagementService.addRule(any(Rule.class), eq(TENANT_DOMAIN))).thenReturn(created); + + PolicyResource ruleRes = new PolicyResource(null, "ios", ResourceType.RULE, null, mock(Rule.class)); + Policy update = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.singletonList(ruleRes)); + when(policyManagementDAO.updatePolicy(any(Policy.class), eq(TENANT_ID))).thenReturn(update); + + policyManagementService.updatePolicy(update, TENANT_DOMAIN); + + verify(ruleManagementService).addRule(any(Rule.class), eq(TENANT_DOMAIN)); + verify(policyManagementDAO).updatePolicy(any(Policy.class), eq(TENANT_ID)); + verify(ruleManagementService).deleteRule("old-rule", TENANT_DOMAIN); + } + + @Test + public void testUpdatePolicyWithRule_KeepsOldRulesWhenPersistenceFails() + throws PolicyManagementException, RuleManagementException { + + Policy existing = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.singletonList( + new PolicyResource(null, "ios", ResourceType.RULE, "old-rule", null))); + when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(existing); + + Rule created = mock(Rule.class); + when(created.getId()).thenReturn("new-rule"); + when(ruleManagementService.addRule(any(Rule.class), eq(TENANT_DOMAIN))).thenReturn(created); + when(policyManagementDAO.updatePolicy(any(Policy.class), eq(TENANT_ID))) + .thenThrow(PolicyManagementServerException.class); + + PolicyResource ruleRes = new PolicyResource(null, "ios", ResourceType.RULE, null, mock(Rule.class)); + Policy update = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.singletonList(ruleRes)); + + try { + policyManagementService.updatePolicy(update, TENANT_DOMAIN); + Assert.fail("Expected PolicyManagementException"); + } catch (PolicyManagementException expected) { + // Expected. + } + + // The newly created rule must be compensated, but the old rule must survive the failed DB commit. + verify(ruleManagementService).deleteRule("new-rule", TENANT_DOMAIN); + verify(ruleManagementService, never()).deleteRule("old-rule", TENANT_DOMAIN); + } + + @Test + public void testDeletePolicyWithRuleResource_RemovesRulesFromRuleMgt() + throws PolicyManagementException, RuleManagementException { + + Policy existing = new Policy(TEST_POLICY_ID, TEST_POLICY_NAME, TENANT_DOMAIN, + Collections.singletonList( + new PolicyResource(null, "ios", ResourceType.RULE, "rule-1", null))); + when(policyManagementDAO.getPolicyById(TEST_POLICY_ID, TENANT_ID)).thenReturn(existing); + + policyManagementService.deletePolicy(TEST_POLICY_ID, TENANT_DOMAIN); + + verify(policyManagementDAO).deletePolicy(TEST_POLICY_ID, TENANT_ID); + verify(ruleManagementService).deleteRule("rule-1", TENANT_DOMAIN); + } +} diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/dbscripts/h2.sql b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/dbscripts/h2.sql new file mode 100644 index 000000000000..73d754507af3 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/dbscripts/h2.sql @@ -0,0 +1,18 @@ +CREATE TABLE IF NOT EXISTS IDN_POLICY ( + ID CHAR(36) NOT NULL, + POLICY_NAME VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_NAME, TENANT_ID) +); + +CREATE TABLE IF NOT EXISTS IDN_POLICY_RESOURCE ( + ID CHAR(36) NOT NULL, + POLICY_ID CHAR(36) NOT NULL, + TARGET VARCHAR(255) NOT NULL, + RESOURCE_TYPE VARCHAR(50) NOT NULL, + RESOURCE_ID CHAR(36) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE), + FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE +); diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/repository/conf/carbon.xml b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/repository/conf/carbon.xml new file mode 100644 index 000000000000..a5a1a6470cbc --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/repository/conf/carbon.xml @@ -0,0 +1,686 @@ + + + + + + + + WSO2 Identity Server + + + IS + + + 5.3.0 + + + localhost + + + localhost + + + local:/${carbon.context}/services/ + + + + + + + IdentityServer + + + + + + + org.wso2.carbon + + + / + + + + + + + + + 15 + + + + + + + + + 0 + + + + + 9999 + + 11111 + + + + + + 10389 + + 8000 + + + + + + 10500 + + + + + + + + + org.wso2.carbon.tomcat.jndi.CarbonJavaURLContextFactory + + + + + + + + + java + + + + + + + + + + false + + + false + + + 600 + + + + false + + + + + + + + 30 + + + + + + + + + 15 + + + + + + ${carbon.home}/repository/deployment/server/ + + + 15 + + + ${carbon.home}/repository/conf/axis2/axis2.xml + + + 30000 + + + ${carbon.home}/repository/deployment/client/ + + ${carbon.home}/repository/conf/axis2/axis2_client.xml + + true + + + + + + + + + + admin + Default Administrator Role + + + user + Default User Role + + + + + + + + + + + + ${carbon.home}/repository/resources/security/wso2carbon.jks + + JKS + + wso2carbon + + wso2carbon + + wso2carbon + + + + + + ${carbon.home}/repository/resources/security/client-truststore.jks + + JKS + + wso2carbon + + + + + + + + + + + + + + + + + + + UserManager + + + false + + org.wso2.carbon.identity.provider.AttributeCallbackHandler + + + org.wso2.carbon.identity.sts.store.DBTokenStore + + + true + allow + + + + + + + claim_mgt_menu + identity_mgt_emailtemplate_menu + identity_security_questions_menu + + + + ${carbon.home}/tmp/work + + + + + + true + + + 10 + + + 30 + + + + + + 100 + + + + keystore + certificate + * + + org.wso2.carbon.ui.transports.fileupload.AnyFileUploadExecutor + + + + + jarZip + + org.wso2.carbon.ui.transports.fileupload.JarZipUploadExecutor + + + + dbs + + org.wso2.carbon.ui.transports.fileupload.DBSFileUploadExecutor + + + + tools + + org.wso2.carbon.ui.transports.fileupload.ToolsFileUploadExecutor + + + + toolsAny + + org.wso2.carbon.ui.transports.fileupload.ToolsAnyFileUploadExecutor + + + + + + + + + + info + org.wso2.carbon.core.transports.util.InfoProcessor + + + wsdl + org.wso2.carbon.core.transports.util.Wsdl11Processor + + + wsdl2 + org.wso2.carbon.core.transports.util.Wsdl20Processor + + + xsd + org.wso2.carbon.core.transports.util.XsdProcessor + + + + + + false + false + true + svn + http://svnrepo.example.com/repos/ + username + password + true + + + + + + + + + + + + + + + ${require.carbon.servlet} + + + + + true + + + + + + + default repository + http://product-dist.wso2.com/p2/carbon/releases/wilkes/ + + + + + + + + true + + + + + + true + + diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/repository/conf/identity/identity.xml b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/repository/conf/identity/identity.xml new file mode 100644 index 000000000000..07de6831dbf4 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/repository/conf/identity/identity.xml @@ -0,0 +1,743 @@ + + + + + + + + + jdbc/WSO2IdentityDB + + + + + true + true + 0 + + true + 20160 + 1140 + + + true + 720 + + + + + + + 15 + 20160 + + + + + + ${carbon.home}/conf/keystores + SunX509 + SunX509 + + + + SelfAndManaged + CertValidate + + + + + + + + + + + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/openidserver + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/openid + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/openid_login.do + + + false + + 7200 + + false + + + + + + + + + + + + + + + + + + + + + + -1 + -1 + -1 + -1 + + + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth/request-token + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth/authorize-url + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth/access-token + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/authorize + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/token + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/revoke + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/introspect + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/userinfo + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oidc/checksession + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oidc/logout + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/oauth2_authz.do + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/oauth2_error.do + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/oauth2_consent.do + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/oauth2_logout_consent.do + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/oauth2_logout.do + + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/.well-known/webfinger + + + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/identity/connect/register + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/jwks + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/oidcdiscovery + + + 300 + + 3600 + + 3600 + + 84600 + + 300 + + false + + true + + org.wso2.carbon.identity.oauth.tokenprocessor.PlainTextPersistenceProcessor + + + + false + + + + + + token + org.wso2.carbon.identity.oauth2.authz.handlers.AccessTokenResponseTypeHandler + + + code + org.wso2.carbon.identity.oauth2.authz.handlers.CodeResponseTypeHandler + + + id_token + org.wso2.carbon.identity.oauth2.authz.handlers.IDTokenResponseTypeHandler + + + id_token token + org.wso2.carbon.identity.oauth2.authz.handlers.IDTokenTokenResponseTypeHandler + + + + + + authorization_code + org.wso2.carbon.identity.oauth2.token.handlers.grant.AuthorizationCodeGrantHandler + + + password + org.wso2.carbon.identity.oauth2.token.handlers.grant.PasswordGrantHandler + + + refresh_token + org.wso2.carbon.identity.oauth2.token.handlers.grant.RefreshGrantHandler + + + client_credentials + org.wso2.carbon.identity.oauth2.token.handlers.grant.ClientCredentialsGrantHandler + + + urn:ietf:params:oauth:grant-type:saml2-bearer + org.wso2.carbon.identity.oauth2.token.handlers.grant.saml.SAML2BearerGrantHandler + + + iwa:ntlm + org.wso2.carbon.identity.oauth2.token.handlers.grant.iwa.ntlm.NTLMAuthenticationGrantHandler + + + idTokenNotAllowedGrantType + org.wso2.carbon.identity.oauth2.token.handlers.grant.idTokenNotAllowedGrantHandler + false + + + + + + + + + false + + + + false + + + + false + org.wso2.carbon.identity.oauth2.authcontext.JWTTokenGenerator + org.wso2.carbon.identity.oauth2.authcontext.DefaultClaimsRetriever + http://wso2.org/claims + SHA256withRSA + 10 + + + + + + org.wso2.carbon.identity.openidconnect.DefaultIDTokenBuilder + SHA256withRSA + + + + + + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/oauth2/token + org.wso2.carbon.identity.openidconnect.DefaultOIDCClaimsCallbackHandler + 3600 + org.wso2.carbon.identity.oauth.endpoint.user.impl.UserInfoUserStoreClaimRetriever + org.wso2.carbon.identity.oauth.endpoint.user.impl.UserInforRequestDefaultValidator + org.wso2.carbon.identity.oauth.endpoint.user.impl.UserInfoISAccessTokenValidator + org.wso2.carbon.identity.oauth.endpoint.user.impl.UserInfoJSONResponseBuilder + false + + + + + + + + gtalk + talk.google.com + 5222 + gmail.com + multifactor1@gmail.com + wso2carbon + + + + + + 157680000 + 157680000 + ${carbon.host} + + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/samlsso + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/samlsso_logout.do + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/samlsso_notification.do + 5 + 60000 + + false + http://wso2.org/claims + org.wso2.carbon.identity.sso.saml.builders.assertion.ExtendedDefaultAssertionBuilder + + org.wso2.carbon.identity.sso.saml.builders.encryption.DefaultSSOEncrypter + org.wso2.carbon.identity.sso.saml.builders.signature.DefaultSSOSigner + org.wso2.carbon.identity.sso.saml.validators.SAML2HTTPRedirectDeflateSignatureValidator + + + + 5 + false + http://www.w3.org/2000/09/xmldsig#rsa-sha1 + http://www.w3.org/2000/09/xmldsig#sha1 + true + + + + + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/services/wso2carbon-sts + + + + + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/passivests + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/retry.do + org.wso2.carbon.identity.sts.passive.utils.NoPersistenceTokenStore + true + + + + + false + ${Ports.ThriftEntitlementReceivePort} + 10000 + + ${carbon.home}/repository/resources/security/wso2carbon.jks + wso2carbon + + + ${carbon.host} + + + + + + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/wso2/scim/Users + ${carbon.protocol}://${carbon.host}:${carbon.management.port}/wso2/scim/Groups + + + 5 + + + 10 + local://services + + + + + + + + + + + + + org.wso2.carbon.identity.governance.store.JDBCIdentityDataStore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /permission/admin/manage/identity/identitymgt + + + + + + /permission/admin/manage/identity/usermgt/view + + + /permission/admin/manage/identity/usermgt/view + + + + /permission/admin/manage/identity/configmgt/list + + + + /permission/admin/manage/identity/configmgt/add + + + /permission/admin/manage/identity/configmgt/update + + + + /permission/admin/manage/identity/configmgt/delete + + + + /permission/admin/manage/identity/configmgt/add + + + /permission/admin/manage/identity/configmgt/update + + + + /permission/admin/manage/identity/configmgt/delete + + + + /permission/admin/manage/identity/configmgt/add + + + /permission/admin/manage/identity/configmgt/update + + + + /permission/admin/manage/identity/configmgt/delete + + + + + + + /permission/admin/manage/identity/consentmgt/add + + + + /permission/admin/manage/identity/consentmgt/delete + + + + /permission/admin/manage/identity/consentmgt/add + + + + /permission/admin/manage/identity/consentmgt/delete + + + + /permission/admin/manage/identity/consentmgt/add + + + + /permission/admin/manage/identity/consentmgt/delete + + + + /permission/admin/manage/identity/identitymgt + + + + /permission/admin/manage/identity/applicationmgt/create + + + /permission/admin/manage/identity/applicationmgt/delete + + + /permission/admin/manage/identity/applicationmgt/update + + + /permission/admin/manage/identity/applicationmgt/view + + + /permission/admin/manage/identity/applicationmgt/delete + + + /permission/admin/manage/identity/applicationmgt/create + + + /permission/admin/manage/identity/applicationmgt/view + + + /permission/admin/manage/identity/pep + + + /permission/admin/manage/identity/usermgt/create + + + /permission/admin/manage/identity/usermgt/list + + + /permission/admin/manage/identity/rolemgt/create + + + /permission/admin/manage/identity/rolemgt/view + + + /permission/admin/manage/identity/usermgt/view + + + /permission/admin/manage/identity/usermgt/update + + + /permission/admin/manage/identity/usermgt/update + + + /permission/admin/manage/identity/usermgt/delete + + + /permission/admin/manage/identity/rolemgt/view + + + /permission/admin/manage/identity/rolemgt/update + + + /permission/admin/manage/identity/rolemgt/update + + + /permission/admin/manage/identity/rolemgt/delete + + + /permission/admin/login + + + /permission/admin/manage/identity/usermgt/delete + + + /permission/admin/login + + + /permission/admin/login + + + /permission/admin/manage/identity/usermgt/create + + + + + + + + + /permission/admin/manage/identity/usermgt + + + /permission/admin/manage/identity/applicationmgt + + + + + + + /permission/admin/manage/identity/usermgt/update + + + + + + /permission/admin/manage/humantask/viewtasks + + + /permission/admin/login + + + /permission/admin/manage/identity/usermgt + + + /permission/admin/manage/identity/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /api/identity/user/v0.9 + /api/identity/recovery/v0.9 + /oauth2 + /api/identity/entitlement + + + /identity/(.*) + + + + + + applications,connections + + + + 300 + diff --git a/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/testng.xml b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/testng.xml new file mode 100644 index 000000000000..cd69d0cb8866 --- /dev/null +++ b/components/policy-mgt/org.wso2.carbon.identity.policy.management/src/test/resources/testng.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + diff --git a/components/policy-mgt/pom.xml b/components/policy-mgt/pom.xml new file mode 100644 index 000000000000..42288b408e08 --- /dev/null +++ b/components/policy-mgt/pom.xml @@ -0,0 +1,45 @@ + + + + + + + org.wso2.carbon.identity.framework + identity-framework + 7.11.135-SNAPSHOT + ../../pom.xml + + + 4.0.0 + policy-mgt + pom + WSO2 Carbon - Policy Management Aggregator Module + + This is a Carbon bundle that aggregates generic policy management modules. + + http://wso2.org + + + org.wso2.carbon.identity.policy.management + + + diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/db2.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/db2.sql index e2d877c479fe..e3fc4e8a92cf 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/db2.sql +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/db2.sql @@ -3182,3 +3182,23 @@ CREATE INDEX IDX_CONSUMER_KEY_STATE ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID / CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_SCOPE_HASH, TOKEN_STATE, IDP_ID) / + +CREATE TABLE IDN_POLICY ( + ID CHAR(36) NOT NULL, + POLICY_NAME VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_NAME, TENANT_ID) +) +/ +CREATE TABLE IDN_POLICY_RESOURCE ( + ID CHAR(36) NOT NULL, + POLICY_ID CHAR(36) NOT NULL, + TARGET VARCHAR(255) NOT NULL, + RESOURCE_TYPE VARCHAR(50) NOT NULL, + RESOURCE_ID CHAR(36) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE), + FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE +) +/ diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/h2.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/h2.sql index 7bafed8fa168..d83c94d4a77a 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/h2.sql +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/h2.sql @@ -2109,3 +2109,22 @@ CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_K -- WORKFLOWS -- CREATE INDEX IDX_WF_APPROVAL_STATE ON WF_WORKFLOW_APPROVAL_STATE(EVENT_ID,WORKFLOW_ID); CREATE INDEX IDX_WF_APPROVAL_RELATION ON WF_WORKFLOW_APPROVAL_RELATION(TASK_ID, APPROVER_TYPE, APPROVER_NAME); + +CREATE TABLE IF NOT EXISTS IDN_POLICY ( + ID CHAR(36) NOT NULL, + POLICY_NAME VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_NAME, TENANT_ID) +); + +CREATE TABLE IF NOT EXISTS IDN_POLICY_RESOURCE ( + ID CHAR(36) NOT NULL, + POLICY_ID CHAR(36) NOT NULL, + TARGET VARCHAR(255) NOT NULL, + RESOURCE_TYPE VARCHAR(50) NOT NULL, + RESOURCE_ID CHAR(36) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE), + FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE +); diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mssql.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mssql.sql index 7484a815c6aa..67f87e6d6fc5 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mssql.sql +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mssql.sql @@ -2317,3 +2317,24 @@ GO CREATE TRIGGER API_RESOURCE_DELETE_TRIGGER ON API_RESOURCE INSTEAD OF DELETE AS BEGIN DELETE FROM AUTHORIZED_API WHERE API_ID IN (SELECT ID FROM DELETED) DELETE FROM API_RESOURCE WHERE ID IN (SELECT ID FROM deleted) END; GO + +IF NOT EXISTS (SELECT * FROM SYS.OBJECTS WHERE OBJECT_ID = OBJECT_ID(N'[DBO].[IDN_POLICY]') AND TYPE IN (N'U')) +CREATE TABLE IDN_POLICY ( + ID CHAR(36) NOT NULL, + POLICY_NAME VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_NAME, TENANT_ID) +); + +IF NOT EXISTS (SELECT * FROM SYS.OBJECTS WHERE OBJECT_ID = OBJECT_ID(N'[DBO].[IDN_POLICY_RESOURCE]') AND TYPE IN (N'U')) +CREATE TABLE IDN_POLICY_RESOURCE ( + ID CHAR(36) NOT NULL, + POLICY_ID CHAR(36) NOT NULL, + TARGET VARCHAR(255) NOT NULL, + RESOURCE_TYPE VARCHAR(50) NOT NULL, + RESOURCE_ID CHAR(36) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE), + FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE +); diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql-cluster.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql-cluster.sql index 9e62af17c8e3..5433f2cd5ab1 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql-cluster.sql +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql-cluster.sql @@ -2324,3 +2324,22 @@ CREATE INDEX IDX_REFRESH_TOKEN_HASH ON IDN_OAUTH2_REFRESH_TOKEN (REFRESH_TOKEN_H CREATE INDEX IDX_AUTHZ_USER_TENANT_DOMAIN_STATE ON IDN_OAUTH2_REFRESH_TOKEN (AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_STATE); CREATE INDEX IDX_CONSUMER_KEY_STATE ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, TOKEN_STATE); CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_SCOPE_HASH, TOKEN_STATE, IDP_ID); + +CREATE TABLE IF NOT EXISTS IDN_POLICY ( + ID CHAR(36) NOT NULL, + POLICY_NAME VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_NAME, TENANT_ID) +)ENGINE NDB; + +CREATE TABLE IF NOT EXISTS IDN_POLICY_RESOURCE ( + ID CHAR(36) NOT NULL, + POLICY_ID CHAR(36) NOT NULL, + TARGET VARCHAR(255) NOT NULL, + RESOURCE_TYPE VARCHAR(50) NOT NULL, + RESOURCE_ID CHAR(36) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE), + FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE +)ENGINE NDB; diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql.sql index b5aa3b4e5684..5e69c28eba74 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql.sql +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/mysql.sql @@ -2136,3 +2136,22 @@ CREATE INDEX IDX_REFRESH_TOKEN_HASH ON IDN_OAUTH2_REFRESH_TOKEN (REFRESH_TOKEN_H CREATE INDEX IDX_AUTHZ_USER_TENANT_DOMAIN_STATE ON IDN_OAUTH2_REFRESH_TOKEN (AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_STATE); CREATE INDEX IDX_CONSUMER_KEY_STATE ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, TOKEN_STATE); CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_SCOPE_HASH, TOKEN_STATE, IDP_ID); + +CREATE TABLE IF NOT EXISTS IDN_POLICY ( + ID CHAR(36) NOT NULL, + POLICY_NAME VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_NAME, TENANT_ID) +)DEFAULT CHARACTER SET latin1 ENGINE INNODB; + +CREATE TABLE IF NOT EXISTS IDN_POLICY_RESOURCE ( + ID CHAR(36) NOT NULL, + POLICY_ID CHAR(36) NOT NULL, + TARGET VARCHAR(255) NOT NULL, + RESOURCE_TYPE VARCHAR(50) NOT NULL, + RESOURCE_ID CHAR(36) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE), + FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE +)DEFAULT CHARACTER SET latin1 ENGINE INNODB; diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle.sql index 49771a8a6d69..db792a30de4b 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle.sql +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle.sql @@ -3334,3 +3334,23 @@ CREATE INDEX IDX_CONSUMER_KEY_STATE ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID / CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_SCOPE_HASH, TOKEN_STATE, IDP_ID) / + +CREATE TABLE IDN_POLICY ( + ID CHAR(36) NOT NULL, + POLICY_NAME VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_NAME, TENANT_ID) +) +/ +CREATE TABLE IDN_POLICY_RESOURCE ( + ID CHAR(36) NOT NULL, + POLICY_ID CHAR(36) NOT NULL, + TARGET VARCHAR(255) NOT NULL, + RESOURCE_TYPE VARCHAR(50) NOT NULL, + RESOURCE_ID CHAR(36) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE), + FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE +) +/ diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle_rac.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle_rac.sql index 89c6ecaff150..f92769ccf542 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle_rac.sql +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/oracle_rac.sql @@ -3234,3 +3234,23 @@ CREATE INDEX IDX_CONSUMER_KEY_STATE ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID / CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_SCOPE_HASH, TOKEN_STATE, IDP_ID) / + +CREATE TABLE IDN_POLICY ( + ID CHAR(36) NOT NULL, + POLICY_NAME VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_NAME, TENANT_ID) +) +/ +CREATE TABLE IDN_POLICY_RESOURCE ( + ID CHAR(36) NOT NULL, + POLICY_ID CHAR(36) NOT NULL, + TARGET VARCHAR(255) NOT NULL, + RESOURCE_TYPE VARCHAR(50) NOT NULL, + RESOURCE_ID CHAR(36) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE), + FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE +) +/ diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/postgresql.sql b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/postgresql.sql index a68482e37fe2..caf771bb6a16 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/postgresql.sql +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/dbscripts/postgresql.sql @@ -2228,3 +2228,22 @@ CREATE INDEX IDX_REFRESH_TOKEN_HASH ON IDN_OAUTH2_REFRESH_TOKEN (REFRESH_TOKEN_H CREATE INDEX IDX_AUTHZ_USER_TENANT_DOMAIN_STATE ON IDN_OAUTH2_REFRESH_TOKEN (AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_STATE); CREATE INDEX IDX_CONSUMER_KEY_STATE ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, TOKEN_STATE); CREATE INDEX IDX_CONSUMER_USER_SCOPE_IDP ON IDN_OAUTH2_REFRESH_TOKEN (CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID, USER_DOMAIN, TOKEN_SCOPE_HASH, TOKEN_STATE, IDP_ID); + +CREATE TABLE IF NOT EXISTS IDN_POLICY ( + ID CHAR(36) NOT NULL, + POLICY_NAME VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_NAME, TENANT_ID) +); + +CREATE TABLE IF NOT EXISTS IDN_POLICY_RESOURCE ( + ID CHAR(36) NOT NULL, + POLICY_ID CHAR(36) NOT NULL, + TARGET VARCHAR(255) NOT NULL, + RESOURCE_TYPE VARCHAR(50) NOT NULL, + RESOURCE_ID CHAR(36) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (POLICY_ID, TARGET, RESOURCE_TYPE), + FOREIGN KEY (POLICY_ID) REFERENCES IDN_POLICY (ID) ON DELETE CASCADE +); diff --git a/pom.xml b/pom.xml index 1fe611983e85..d0cf760fc28a 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,7 @@ components/client-attestation-mgt components/trusted-app-mgt components/rule-mgt + components/policy-mgt components/webhook-mgt components/action-mgt components/ai-services-mgt