diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/pom.xml b/components/rule-mgt/org.wso2.carbon.identity.rule.management/pom.xml new file mode 100644 index 000000000000..eb0c64971890 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/pom.xml @@ -0,0 +1,219 @@ + + + + + + org.wso2.carbon.identity.framework + rule-mgt + 7.7.48-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.identity.rule.management + bundle + WSO2 Identity - Rule Management Component + Rule management backend component + http://wso2.org + + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.core + + + org.wso2.carbon.utils + org.wso2.carbon.database.utils + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.central.log.mgt + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.rule.metadata + + + com.fasterxml.jackson.core + jackson-databind + + + + org.testng + testng + test + + + org.mockito + mockito-core + test + + + org.mockito + mockito-testng + 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.rule.management.internal, + org.wso2.carbon.identity.rule.management.constant, + org.wso2.carbon.identity.rule.management.dao.*, + org.wso2.carbon.identity.rule.management.model.internal, + org.wso2.carbon.identity.rule.management.service.impl, + + + !org.wso2.carbon.identity.rule.management.internal, + !org.wso2.carbon.identity.rule.management.constant, + !org.wso2.carbon.identity.rule.management.dao.*, + !org.wso2.carbon.identity.rule.management.model.internal, + !org.wso2.carbon.identity.rule.management.service.impl, + org.wso2.carbon.identity.rule.management.*; + version="${carbon.identity.package.export.version}", + + + org.apache.commons.lang; version="${commons-lang.wso2.osgi.version.range}", + org.apache.commons.logging; version="${import.package.version.commons.logging}", + org.apache.commons.collections; version="${commons-collections.wso2.osgi.version.range}", + org.osgi.framework; version="${osgi.framework.imp.pkg.version.range}", + org.osgi.service.component; version="${osgi.service.component.imp.pkg.version.range}", + org.wso2.carbon.utils; version="${carbon.kernel.package.import.version.range}", + org.wso2.carbon.identity.central.log.mgt.utils; + version="${carbon.identity.package.import.version.range}", + com.fasterxml.jackson.core.*; version="${com.fasterxml.jackson.annotation.version.range}", + com.fasterxml.jackson.databind.*; + version="${com.fasterxml.jackson.annotation.version.range}", + com.fasterxml.jackson.annotation.*; + version="${com.fasterxml.jackson.annotation.version.range}", + org.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.cache; + version="${carbon.identity.package.import.version.range}", + org.wso2.carbon.identity.rule.metadata.*; + version="${carbon.identity.package.import.version.range}", + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire.plugin.version} + + + src/test/resources/testng.xml + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + default-prepare-agent + + prepare-agent + + + + default-prepare-agent-integration + + prepare-agent-integration + + + + default-report + + report + + + + default-report-integration + + report-integration + + + + default-check + + check + + + + + BUNDLE + + + LINE + COVEREDRATIO + 0.80 + + + COMPLEXITY + COVEREDRATIO + 0.60 + + + + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + + ../../../spotbugs-exclude.xml + Max + Low + true + true + + + + + diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/cache/RuleCache.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/cache/RuleCache.java new file mode 100644 index 000000000000..09c68b822f21 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/cache/RuleCache.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.cache; + +import org.wso2.carbon.identity.core.cache.BaseCache; +import org.wso2.carbon.utils.CarbonUtils; + +/** + * Cache for Rule Management. + */ +public class RuleCache extends BaseCache { + + private static final String CACHE_NAME = "RuleCache"; + private static final RuleCache INSTANCE = new RuleCache(); + + public RuleCache() { + + super(CACHE_NAME); + } + + public static RuleCache getInstance() { + + CarbonUtils.checkSecurity(); + return INSTANCE; + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/cache/RuleCacheEntry.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/cache/RuleCacheEntry.java new file mode 100644 index 000000000000..8080bf5c2934 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/cache/RuleCacheEntry.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.cache; + +import org.wso2.carbon.identity.core.cache.CacheEntry; +import org.wso2.carbon.identity.rule.management.model.Rule; + +/** + * Cache entry for Rule Management. + * This class is used to store Rule object in cache. + */ +public class RuleCacheEntry extends CacheEntry { + + private static final long serialVersionUID = -8205516043545934797L; + + private Rule rule; + + public RuleCacheEntry(Rule rule) { + + this.rule = rule; + } + + public Rule getRule() { + + return rule; + } + + public void setRule(Rule rule) { + + this.rule = rule; + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/cache/RuleCacheKey.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/cache/RuleCacheKey.java new file mode 100644 index 000000000000..2ace44abc42c --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/cache/RuleCacheKey.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.cache; + +import org.wso2.carbon.identity.core.cache.CacheKey; + +/** + * Cache key for Rule Management. + * This class is used to store Rule ID in cache. + */ +public class RuleCacheKey extends CacheKey { + + private static final long serialVersionUID = -6662958252110402724L; + + private final String ruleId; + + public RuleCacheKey(String ruleId) { + + this.ruleId = ruleId; + } + + public String getRuleId() { + + return ruleId; + } + + @Override + public boolean equals(Object o) { + + if (!(o instanceof RuleCacheKey)) { + return false; + } + return ruleId.equals(((RuleCacheKey) o).getRuleId()); + } + + @Override + public int hashCode() { + + return ruleId.hashCode(); + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/constant/RuleSQLConstants.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/constant/RuleSQLConstants.java new file mode 100644 index 000000000000..7dca06852288 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/constant/RuleSQLConstants.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.constant; + +/** + * SQL Constants for Rule Management. + * This class is used to store SQL queries and column names. + */ +public class RuleSQLConstants { + + private RuleSQLConstants() { + + } + + /** + * This class is used to store column names. + */ + public static class Column { + + public static final String RULE_INTERNAL_ID = "ID"; + public static final String RULE_EXTERNAL_ID = "UUID"; + public static final String RULE_CONTENT = "CONTENT"; + public static final String IS_ACTIVE = "IS_ACTIVE"; + public static final String TENANT_ID = "TENANT_ID"; + public static final String VERSION = "VERSION"; + public static final String RULE_REFERENCE_ID = "RULE_ID"; + public static final String FIELD_NAME = "FIELD_NAME"; + public static final String FIELD_REFERENCE = "FIELD_REFERENCE"; + + private Column() { + + } + } + + /** + * This class is used to store SQL queries. + */ + public static class Query { + + public static final String ADD_RULE = "INSERT INTO IDN_RULE (UUID, CONTENT, IS_ACTIVE, TENANT_ID, VERSION) " + + "VALUES (:UUID;, :CONTENT;, :IS_ACTIVE;, :TENANT_ID;, :VERSION;)"; + public static final String ADD_RULE_REFERENCES = "INSERT INTO IDN_RULE_REFERENCES (RULE_ID, " + + "FIELD_NAME, FIELD_REFERENCE, TENANT_ID) VALUES (:RULE_ID;, :FIELD_NAME;, :FIELD_REFERENCE;, " + + ":TENANT_ID;)"; + public static final String UPDATE_RULE = + "UPDATE IDN_RULE SET CONTENT = :CONTENT; WHERE UUID = :UUID; AND TENANT_ID = :TENANT_ID;"; + public static final String DELETE_RULE_REFERENCES = + "DELETE FROM IDN_RULE_REFERENCES WHERE RULE_ID = :RULE_ID; AND TENANT_ID = :TENANT_ID;"; + public static final String DELETE_RULE = "DELETE FROM IDN_RULE WHERE UUID = :UUID; AND TENANT_ID = :TENANT_ID;"; + public static final String CHANGE_RULE_STATUS = "UPDATE IDN_RULE SET IS_ACTIVE = :IS_ACTIVE; WHERE UUID = " + + ":UUID; AND TENANT_ID = :TENANT_ID;"; + public static final String GET_RULE_BY_ID = + "SELECT CONTENT, IS_ACTIVE FROM IDN_RULE WHERE UUID = :UUID; AND TENANT_ID = :TENANT_ID;"; + public static final String GET_RULE_INTERNAL_ID_BY_ID = + "SELECT ID FROM IDN_RULE WHERE UUID = :UUID; AND TENANT_ID = :TENANT_ID;"; + + private Query() { + + } + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/dao/RuleManagementDAO.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/dao/RuleManagementDAO.java new file mode 100644 index 000000000000..ccb873e7504e --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/dao/RuleManagementDAO.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.dao; + +import org.wso2.carbon.identity.rule.management.exception.RuleManagementException; +import org.wso2.carbon.identity.rule.management.model.Rule; + +/** + * Rule Management DAO. + * This class is used to perform CRUD operations on Rule in the datastore. + */ +public interface RuleManagementDAO { + + /** + * Add a new Rule. + * + * @param rule Rule object + * @param tenantId Tenant ID + * @throws RuleManagementException Rule Management Exception + */ + public void addRule(Rule rule, int tenantId) throws RuleManagementException; + + /** + * Update an existing Rule. + * + * @param rule Rule object + * @param tenantId Tenant ID + * @throws RuleManagementException Rule Management Exception + */ + public void updateRule(Rule rule, int tenantId) throws RuleManagementException; + + /** + * Delete a Rule. + * + * @param ruleId Rule ID + * @param tenantId Tenant ID + * @throws RuleManagementException Rule Management Exception + */ + public void deleteRule(String ruleId, int tenantId) throws RuleManagementException; + + /** + * Get a Rule by Rule ID. + * + * @param ruleId Rule ID + * @param tenantId Tenant ID + * @return Rule object + * @throws RuleManagementException Rule Management Exception + */ + public Rule getRuleByRuleId(String ruleId, int tenantId) throws RuleManagementException; + + /** + * Activate a Rule. + * + * @param ruleId Rule ID + * @param tenantId Tenant ID + * @throws RuleManagementException Rule Management Exception + */ + public void activateRule(String ruleId, int tenantId) throws RuleManagementException; + + /** + * Deactivate a Rule. + * + * @param ruleId Rule ID + * @param tenantId Tenant ID + * @throws RuleManagementException Rule Management Exception + */ + public void deactivateRule(String ruleId, int tenantId) throws RuleManagementException; +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/dao/impl/CacheBackedRuleManagementDAO.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/dao/impl/CacheBackedRuleManagementDAO.java new file mode 100644 index 000000000000..e926a34f2965 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/dao/impl/CacheBackedRuleManagementDAO.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.rule.management.cache.RuleCache; +import org.wso2.carbon.identity.rule.management.cache.RuleCacheEntry; +import org.wso2.carbon.identity.rule.management.cache.RuleCacheKey; +import org.wso2.carbon.identity.rule.management.dao.RuleManagementDAO; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementException; +import org.wso2.carbon.identity.rule.management.model.Rule; + +/** + * Cache backed Rule Management DAO. + * This class is used to implement the caching on top of the data layer operations. + * This caches the Rule object. + */ +public class CacheBackedRuleManagementDAO implements RuleManagementDAO { + + private static final Log LOG = LogFactory.getLog(CacheBackedRuleManagementDAO.class); + + private final RuleManagementDAO ruleManagementDAO; + private final RuleCache ruleCache; + + public CacheBackedRuleManagementDAO(RuleManagementDAO ruleManagementDAO) { + + this.ruleManagementDAO = ruleManagementDAO; + ruleCache = RuleCache.getInstance(); + } + + /** + * Add a new Rule. + * This method is used directly invokes the data layer operation to add the Rule. + * + * @param rule Rule object + * @param tenantId Tenant ID + * @throws RuleManagementException Rule Management Exception + */ + @Override + public void addRule(Rule rule, int tenantId) throws RuleManagementException { + + ruleManagementDAO.addRule(rule, tenantId); + } + + /** + * Update an existing Rule. + * This method clears the cache entry upon rule update. + * + * @param rule Rule object + * @param tenantId Tenant ID + * @throws RuleManagementException Rule Management Exception + */ + @Override + public void updateRule(Rule rule, int tenantId) throws RuleManagementException { + + ruleCache.clearCacheEntry(new RuleCacheKey(rule.getId()), tenantId); + LOG.debug("Rule cache entry is cleared for rule id: " + rule.getId() + " for rule update."); + ruleManagementDAO.updateRule(rule, tenantId); + } + + /** + * Delete a Rule. + * This method clears the cache entry upon rule deletion. + * + * @param ruleId Rule ID + * @param tenantId Tenant ID + * @throws RuleManagementException Rule Management Exception + */ + @Override + public void deleteRule(String ruleId, int tenantId) throws RuleManagementException { + + ruleCache.clearCacheEntry(new RuleCacheKey(ruleId), tenantId); + LOG.debug("Rule cache entry is cleared for rule id: " + ruleId + " for rule deletion."); + ruleManagementDAO.deleteRule(ruleId, tenantId); + } + + /** + * Get a Rule by Rule ID. + * This method first checks the cache for the Rule object. + * If the Rule object is not found in the cache, it invokes the data layer operation to get the Rule. + * + * @param ruleId Rule ID + * @param tenantId Tenant ID + * @return Rule object + * @throws RuleManagementException Rule Management Exception + */ + @Override + public Rule getRuleByRuleId(String ruleId, int tenantId) throws RuleManagementException { + + RuleCacheEntry ruleCacheEntry = ruleCache.getValueFromCache(new RuleCacheKey(ruleId), tenantId); + if (ruleCacheEntry != null && ruleCacheEntry.getRule() != null) { + LOG.debug("Rule cache hit for rule id: " + ruleId + ". Returning from cache."); + return ruleCacheEntry.getRule(); + } + + Rule rule = ruleManagementDAO.getRuleByRuleId(ruleId, tenantId); + if (rule != null) { + LOG.debug("Rule cache miss for rule id: " + ruleId + ". Adding to cache."); + ruleCache.addToCache(new RuleCacheKey(ruleId), new RuleCacheEntry(rule), tenantId); + } + return rule; + } + + /** + * Activate a Rule. + * This method clears the cache entry upon rule activation. + * + * @param ruleId Rule ID + * @param tenantId Tenant ID + * @throws RuleManagementException Rule Management Exception + */ + @Override + public void activateRule(String ruleId, int tenantId) throws RuleManagementException { + + ruleCache.clearCacheEntry(new RuleCacheKey(ruleId), tenantId); + LOG.debug("Rule cache entry is cleared for rule id: " + ruleId + " for rule activation."); + ruleManagementDAO.activateRule(ruleId, tenantId); + } + + /** + * Deactivate a Rule. + * This method clears the cache entry upon rule deactivation. + * + * @param ruleId Rule ID + * @param tenantId Tenant ID + * @throws RuleManagementException Rule Management Exception + */ + @Override + public void deactivateRule(String ruleId, int tenantId) throws RuleManagementException { + + ruleCache.clearCacheEntry(new RuleCacheKey(ruleId), tenantId); + LOG.debug("Rule cache entry is cleared for rule id: " + ruleId + " for rule deactivation."); + ruleManagementDAO.deactivateRule(ruleId, tenantId); + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/dao/impl/RuleManagementDAOImpl.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/dao/impl/RuleManagementDAOImpl.java new file mode 100644 index 000000000000..d87dab7cca1c --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/dao/impl/RuleManagementDAOImpl.java @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.dao.impl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.wso2.carbon.database.utils.jdbc.NamedJdbcTemplate; +import org.wso2.carbon.database.utils.jdbc.exceptions.TransactionException; +import org.wso2.carbon.identity.core.util.IdentityDatabaseUtil; +import org.wso2.carbon.identity.rule.management.constant.RuleSQLConstants; +import org.wso2.carbon.identity.rule.management.dao.RuleManagementDAO; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementException; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementServerException; +import org.wso2.carbon.identity.rule.management.model.Expression; +import org.wso2.carbon.identity.rule.management.model.Rule; +import org.wso2.carbon.identity.rule.management.model.Value; +import org.wso2.carbon.identity.rule.management.model.internal.ANDCombinedRule; +import org.wso2.carbon.identity.rule.management.model.internal.ORCombinedRule; +import org.wso2.carbon.identity.rule.management.model.internal.RuleData; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * Rule Management DAO Implementation. + * This class is used to perform CRUD operations on Rule in the database. + */ +public class RuleManagementDAOImpl implements RuleManagementDAO { + + private static final String V1 = "1.0.0"; + + /** + * This method will add the Rule to the database and add the Rule Value References to the database. + * + * @param rule Rule object + * @param tenantId Tenant ID + * @throws RuleManagementException If an error occurs while adding the rule to the database. + */ + @Override + public void addRule(Rule rule, int tenantId) throws RuleManagementException { + + NamedJdbcTemplate jdbcTemplate = new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + jdbcTemplate.withTransaction(template -> { + int internalId = addRuleToDB(rule, tenantId); + addRuleValueReferencesToDB(internalId, rule, tenantId); + return null; + }); + } catch (TransactionException e) { + throw new RuleManagementServerException("Error while creating the rule in the system.", e); + } + } + + /** + * This method will update the Rule in the database and update the Rule Value References in the database, + * by deleting all and adding back reference values for the updated rule. + * + * @param rule Rule object + * @param tenantId Tenant ID + * @throws RuleManagementException If an error occurs while updating the rule in the database. + */ + @Override + public void updateRule(Rule rule, int tenantId) throws RuleManagementException { + + NamedJdbcTemplate jdbcTemplate = new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + jdbcTemplate.withTransaction(template -> { + updateRuleInDB(rule, tenantId); + int internalRuleId = getInternalRuleIdByRuleId(rule.getId(), tenantId); + deleteRuleReferencesInDB(internalRuleId, tenantId); + addRuleValueReferencesToDB(internalRuleId, rule, tenantId); + return null; + }); + } catch (TransactionException e) { + throw new RuleManagementServerException("Error while updating the rule in the system.", e); + } + } + + /** + * This method will delete the Rule from the database. + * + * @param ruleId Rule ID + * @param tenantId Tenant ID + * @throws RuleManagementException If an error occurs while deleting the rule from the database. + */ + @Override + public void deleteRule(String ruleId, int tenantId) throws RuleManagementException { + + NamedJdbcTemplate jdbcTemplate = new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + jdbcTemplate.withTransaction(template -> { + template.executeUpdate(RuleSQLConstants.Query.DELETE_RULE, + statement -> { + statement.setString(RuleSQLConstants.Column.RULE_EXTERNAL_ID, ruleId); + statement.setInt(RuleSQLConstants.Column.TENANT_ID, tenantId); + }); + + return null; + }); + } catch (TransactionException e) { + throw new RuleManagementServerException("Error while deleting the rule in the system.", e); + } + } + + /** + * This method will retrieve the Rule from the database. + * + * @param ruleId Rule ID + * @param tenantId Tenant ID + * @return Rule object + * @throws RuleManagementException If an error occurs while retrieving the rule from the database. + */ + @Override + public Rule getRuleByRuleId(String ruleId, int tenantId) throws RuleManagementException { + + NamedJdbcTemplate jdbcTemplate = new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + RuleData ruleData = new RuleData(); + try { + jdbcTemplate.withTransaction( + template -> template.fetchSingleRecord(RuleSQLConstants.Query.GET_RULE_BY_ID, + (resultSet, rowNumber) -> { + ruleData.setRuleJson(resultSet.getString(RuleSQLConstants.Column.RULE_CONTENT)); + ruleData.setActive(resultSet.getBoolean(RuleSQLConstants.Column.IS_ACTIVE)); + return null; + }, + statement -> { + statement.setString(RuleSQLConstants.Column.RULE_EXTERNAL_ID, ruleId); + statement.setInt(RuleSQLConstants.Column.TENANT_ID, tenantId); + })); + + if (ruleData.getRuleJson() == null) { + return null; + } + + return new ORCombinedRule.Builder(convertJsonToRule(ruleData.getRuleJson())) + .setId(ruleId) + .setActive(ruleData.isActive()) + .build(); + } catch (TransactionException e) { + throw new RuleManagementServerException("Error while retrieving the rule from the system.", e); + } + } + + /** + * This method will activate the Rule in the database. + * + * @param ruleId Rule ID + * @param tenantId Tenant ID + * @throws RuleManagementException If an error occurs while activating the rule in the database. + */ + @Override + public void activateRule(String ruleId, int tenantId) throws RuleManagementException { + + NamedJdbcTemplate jdbcTemplate = new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + jdbcTemplate.withTransaction(template -> { + template.executeUpdate(RuleSQLConstants.Query.CHANGE_RULE_STATUS, + statement -> { + statement.setBoolean(RuleSQLConstants.Column.IS_ACTIVE, true); + statement.setString(RuleSQLConstants.Column.RULE_EXTERNAL_ID, ruleId); + statement.setInt(RuleSQLConstants.Column.TENANT_ID, tenantId); + }); + return null; + }); + } catch (TransactionException e) { + throw new RuleManagementServerException("Error while activating the rule in the system.", e); + } + } + + /** + * This method will deactivate the Rule in the database. + * + * @param ruleId Rule ID + * @param tenantId Tenant ID + * @throws RuleManagementException If an error occurs while deactivating the rule in the database. + */ + @Override + public void deactivateRule(String ruleId, int tenantId) throws RuleManagementException { + + NamedJdbcTemplate jdbcTemplate = new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + jdbcTemplate.withTransaction(template -> { + template.executeUpdate(RuleSQLConstants.Query.CHANGE_RULE_STATUS, + statement -> { + statement.setBoolean(RuleSQLConstants.Column.IS_ACTIVE, false); + statement.setString(RuleSQLConstants.Column.RULE_EXTERNAL_ID, ruleId); + statement.setInt(RuleSQLConstants.Column.TENANT_ID, tenantId); + }); + return null; + }); + } catch (TransactionException e) { + throw new RuleManagementServerException("Error while deactivating the rule in the system.", e); + } + } + + private int addRuleToDB(Rule rule, int tenantId) + throws TransactionException, IOException, RuleManagementException { + + InputStream ruleJsonAsInputStream = convertRuleToJson(rule); + int ruleJsonStreamLength = ruleJsonAsInputStream.available(); + NamedJdbcTemplate jdbcTemplate = new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + int internalRuleId = + jdbcTemplate.withTransaction(template -> template.executeInsert(RuleSQLConstants.Query.ADD_RULE, + statement -> { + statement.setString(RuleSQLConstants.Column.RULE_EXTERNAL_ID, rule.getId()); + statement.setBinaryStream(RuleSQLConstants.Column.RULE_CONTENT, ruleJsonAsInputStream, + ruleJsonStreamLength); + statement.setBoolean(RuleSQLConstants.Column.IS_ACTIVE, rule.isActive()); + statement.setInt(RuleSQLConstants.Column.TENANT_ID, tenantId); + statement.setString(RuleSQLConstants.Column.VERSION, V1); + }, rule, true)); + // Not all JDBC drivers support getting the auto generated database ID. + // So if the ID is not returned, get the ID by querying the database. + if (internalRuleId == 0) { + internalRuleId = getInternalRuleIdByRuleId(rule.getId(), tenantId); + } + + return internalRuleId; + } + + private int getInternalRuleIdByRuleId(String ruleId, int tenantId) throws RuleManagementException { + + NamedJdbcTemplate jdbcTemplate = new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + try { + return jdbcTemplate.withTransaction( + template -> template.fetchSingleRecord(RuleSQLConstants.Query.GET_RULE_INTERNAL_ID_BY_ID, + (resultSet, rowNumber) -> resultSet.getInt(RuleSQLConstants.Column.RULE_INTERNAL_ID), + statement -> { + statement.setString(RuleSQLConstants.Column.RULE_EXTERNAL_ID, ruleId); + statement.setInt(RuleSQLConstants.Column.TENANT_ID, tenantId); + })); + } catch (TransactionException e) { + throw new RuleManagementServerException("Error while retrieving the rule from the system.", e); + } + } + + private void addRuleValueReferencesToDB(int internalRuleId, Rule rule, int tenantId) throws TransactionException { + + ORCombinedRule orCombinedRule = (ORCombinedRule) rule; + NamedJdbcTemplate jdbcTemplate = new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + jdbcTemplate.withTransaction(template -> template.executeBatchInsert(RuleSQLConstants.Query.ADD_RULE_REFERENCES, + statement -> { + for (ANDCombinedRule rule1 : orCombinedRule.getRules()) { + for (Expression expression : rule1.getExpressions()) { + if (expression.getValue().getType() == Value.Type.REFERENCE) { + statement.setInt(RuleSQLConstants.Column.RULE_REFERENCE_ID, internalRuleId); + statement.setString(RuleSQLConstants.Column.FIELD_NAME, expression.getField()); + statement.setString(RuleSQLConstants.Column.FIELD_REFERENCE, + expression.getValue().getFieldValue()); + statement.setInt(RuleSQLConstants.Column.TENANT_ID, tenantId); + statement.addBatch(); + } + } + } + }, null)); + } + + private void updateRuleInDB(Rule rule, int tenantId) + throws IOException, TransactionException, RuleManagementServerException { + + InputStream ruleJsonAsInputStream = convertRuleToJson(rule); + int ruleJsonStreamLength = ruleJsonAsInputStream.available(); + NamedJdbcTemplate jdbcTemplate = new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + jdbcTemplate.withTransaction(template -> { + template.executeUpdate(RuleSQLConstants.Query.UPDATE_RULE, + statement -> { + statement.setBinaryStream(RuleSQLConstants.Column.RULE_CONTENT, ruleJsonAsInputStream, + ruleJsonStreamLength); + statement.setString(RuleSQLConstants.Column.RULE_EXTERNAL_ID, rule.getId()); + statement.setInt(RuleSQLConstants.Column.TENANT_ID, tenantId); + }); + return null; + }); + } + + private void deleteRuleReferencesInDB(int internalRuleId, int tenantId) throws TransactionException { + + NamedJdbcTemplate jdbcTemplate = new NamedJdbcTemplate(IdentityDatabaseUtil.getDataSource()); + jdbcTemplate.withTransaction(template -> { + template.executeUpdate(RuleSQLConstants.Query.DELETE_RULE_REFERENCES, + statement -> { + statement.setInt(RuleSQLConstants.Column.RULE_REFERENCE_ID, internalRuleId); + statement.setInt(RuleSQLConstants.Column.TENANT_ID, tenantId); + }); + return null; + }); + } + + private InputStream convertRuleToJson(Rule rule) throws RuleManagementServerException { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + return new ByteArrayInputStream(objectMapper.writeValueAsString(rule).getBytes(StandardCharsets.UTF_8)); + } catch (JsonProcessingException e) { + throw new RuleManagementServerException("Failed to convert rule to JSON.", e); + } + } + + private ORCombinedRule convertJsonToRule(String ruleJson) throws RuleManagementServerException { + + try { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(ruleJson, ORCombinedRule.class); + } catch (JsonProcessingException e) { + throw new RuleManagementServerException("Failed to convert JSON to rule.", e); + } + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/exception/RuleManagementClientException.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/exception/RuleManagementClientException.java new file mode 100644 index 000000000000..2375fcef1fba --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/exception/RuleManagementClientException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.exception; + +/** + * Rule Management Client Exception. + * This class is used to handle client exceptions in Rule Management. + */ +public class RuleManagementClientException extends RuleManagementException { + + public RuleManagementClientException(String message) { + + super(message); + } + + public RuleManagementClientException(String message, Throwable cause) { + + super(message, cause); + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/exception/RuleManagementException.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/exception/RuleManagementException.java new file mode 100644 index 000000000000..f821bf7db653 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/exception/RuleManagementException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.exception; + +/** + * Rule Management Exception. + * This class is used to handle exceptions in Rule Management. + */ +public class RuleManagementException extends Exception { + + public RuleManagementException(String message) { + + super(message); + } + + public RuleManagementException(String message, Throwable cause) { + + super(message, cause); + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/exception/RuleManagementServerException.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/exception/RuleManagementServerException.java new file mode 100644 index 000000000000..fe214941bb6e --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/exception/RuleManagementServerException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.exception; + +/** + * Rule Management Server Exception. + * This class is used to handle server exceptions in Rule Management. + */ +public class RuleManagementServerException extends RuleManagementException { + + public RuleManagementServerException(String message) { + + super(message); + } + + public RuleManagementServerException(String message, Throwable cause) { + + super(message, cause); + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/internal/RuleManagementComponentServiceHolder.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/internal/RuleManagementComponentServiceHolder.java new file mode 100644 index 000000000000..f64827a3eeec --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/internal/RuleManagementComponentServiceHolder.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.internal; + +import org.wso2.carbon.identity.rule.metadata.service.RuleMetadataService; + +/** + * Rule Management Component Service Holder. + */ +public class RuleManagementComponentServiceHolder { + + private static final RuleManagementComponentServiceHolder INSTANCE = new RuleManagementComponentServiceHolder(); + + private RuleMetadataService ruleMetadataService; + + private RuleManagementComponentServiceHolder() { + + } + + public static RuleManagementComponentServiceHolder getInstance() { + + return INSTANCE; + } + + public RuleMetadataService getRuleMetadataService() { + + return ruleMetadataService; + } + + public void setRuleMetadataService(RuleMetadataService ruleMetadataService) { + + this.ruleMetadataService = ruleMetadataService; + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/internal/RuleManagementServiceComponent.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/internal/RuleManagementServiceComponent.java new file mode 100644 index 000000000000..5d42ce9675de --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/internal/RuleManagementServiceComponent.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.wso2.carbon.identity.rule.management.service.RuleManagementService; +import org.wso2.carbon.identity.rule.management.service.impl.RuleManagementServiceImpl; +import org.wso2.carbon.identity.rule.metadata.service.RuleMetadataService; + +/** + * Rule Management Service Component. + */ +@Component( + name = "rule.management.service.component", + immediate = true +) +public class RuleManagementServiceComponent { + + private static final Log LOG = LogFactory.getLog(RuleManagementServiceComponent.class); + + @Activate + protected void activate(ComponentContext context) { + + try { + BundleContext bundleCtx = context.getBundleContext(); + + bundleCtx.registerService(RuleManagementService.class.getName(), + RuleManagementServiceImpl.getInstance(), null); + LOG.debug("Rule management bundle is activated."); + } catch (Throwable e) { + LOG.error("Error while initializing rule management service component.", e); + } + } + + @Deactivate + protected void deactivate(ComponentContext context) { + + try { + BundleContext bundleCtx = context.getBundleContext(); + bundleCtx.ungetService(bundleCtx.getServiceReference(RuleManagementService.class)); + LOG.debug("Rule management bundle is deactivated."); + } catch (Throwable e) { + LOG.error("Error while deactivating rule management service component.", e); + } + } + + @Reference( + name = "rule.metadata.service.component", + service = RuleMetadataService.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetRuleMetadataService" + ) + protected void setRuleMetadataService(RuleMetadataService ruleMetadataService) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Registering a reference for RuleMetadataService in the rule management service component."); + } + + RuleManagementComponentServiceHolder.getInstance().setRuleMetadataService(ruleMetadataService); + } + + protected void unsetRuleMetadataService(RuleMetadataService ruleMetadataService) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Unregistering a reference for RuleMetadataService in the rule management service component."); + } + + RuleManagementComponentServiceHolder.getInstance().setRuleMetadataService(null); + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/Condition.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/Condition.java new file mode 100644 index 000000000000..21b27ea43915 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/Condition.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.model; + +/** + * This class is used to define the conditions in Rule Management. + */ +public enum Condition { + AND, OR +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/Expression.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/Expression.java new file mode 100644 index 000000000000..d68cd2372a5c --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/Expression.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.model; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +/** + * Represents an expression in Rule Management. + * This class has a field, an operator and a value. + */ +@JsonDeserialize(builder = Expression.Builder.class) +public class Expression { + + private final String field; + private final String operator; + private final Value value; + + private Expression(Builder builder) { + + this.field = builder.field; + this.operator = builder.operator; + this.value = builder.value; + } + + public String getField() { + + return field; + } + + public String getOperator() { + + return operator; + } + + public Value getValue() { + + return value; + } + + /** + * Builder for the Expression. + */ + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + + private String field; + private String operator; + private Value value; + + public Builder field(String field) { + + this.field = field; + return this; + } + + public Builder operator(String operator) { + + this.operator = operator; + return this; + } + + public Builder value(Value value) { + + this.value = value; + return this; + } + + public Expression build() { + + return new Expression(this); + } + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/FlowType.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/FlowType.java new file mode 100644 index 000000000000..5f8d469f8b8e --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/FlowType.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.model; + +/** + * This class is used to define the flow types in Rule Management. + */ +public enum FlowType { + + PRE_ISSUE_ACCESS_TOKEN; +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/Rule.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/Rule.java new file mode 100644 index 000000000000..b752a68834b9 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/Rule.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * Represents a rule in Rule Management. + * This class has an id, a condition and a status. + */ +public abstract class Rule { + + protected String id; + protected Condition condition; + protected boolean isActive; + + /** + * @JsonIgnore annotation is used to ignore the id field when serializing and deserializing the object, + * to and from JSON in order to store in the database. + */ + @JsonIgnore + public String getId() { + + return id; + } + + /** + * @JsonIgnore annotation is used to ignore the active field when serializing and deserializing the object, + * to and from JSON in order to store in the database. + */ + @JsonIgnore + public boolean isActive() { + + return isActive; + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/Value.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/Value.java new file mode 100644 index 000000000000..0549ee2bcc60 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/Value.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents a value in Rule Management. + * This class has a type and a value. + */ +public class Value { + + private final Type type; + private final String fieldValue; + + @JsonCreator + public Value(@JsonProperty("type") Type type, @JsonProperty("value") String fieldValue) { + + this.type = type; + this.fieldValue = fieldValue; + } + + public Type getType() { + return type; + } + + public String getFieldValue() { + return fieldValue; + } + + /** + * Represents the type of the value. + */ + public enum Type { + STRING, NUMBER, BOOLEAN, DATE_TIME, REFERENCE + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/internal/ANDCombinedRule.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/internal/ANDCombinedRule.java new file mode 100644 index 000000000000..9bdc57381cfb --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/internal/ANDCombinedRule.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.model.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import org.wso2.carbon.identity.rule.management.model.Condition; +import org.wso2.carbon.identity.rule.management.model.Expression; +import org.wso2.carbon.identity.rule.management.model.Rule; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * Represents an AND combined rule. + * This class extends the Rule class and has a list of expressions. + */ +@JsonDeserialize(builder = ANDCombinedRule.Builder.class) +public class ANDCombinedRule extends Rule { + + private final List expressions; + + private ANDCombinedRule(Builder builder) { + + this.id = builder.id; + this.expressions = builder.expressions; + this.condition = Condition.AND; + this.isActive = true; + } + + public Condition getCondition() { + + return condition; + } + + public List getExpressions() { + + return expressions; + } + + /** + * Builder for the ANDCombinedRule. + */ + @JsonPOJOBuilder(withPrefix = "set") + public static class Builder { + + private String id; + private List expressions = new ArrayList<>(); + + public Builder addExpression(Expression expression) { + + expressions.add(expression); + return this; + } + + public Builder setCondition(Condition condition) { + + if (condition != Condition.AND) { + throw new IllegalArgumentException("Condition must be AND for ANDCombinedRule"); + } + + return this; + } + + public Builder setExpressions(List expressions) { + + this.expressions = expressions; + return this; + } + + public ANDCombinedRule build() { + + this.id = UUID.randomUUID().toString(); + return new ANDCombinedRule(this); + } + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/internal/ORCombinedRule.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/internal/ORCombinedRule.java new file mode 100644 index 000000000000..85124b6a5606 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/internal/ORCombinedRule.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.model.internal; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import org.wso2.carbon.identity.rule.management.model.Condition; +import org.wso2.carbon.identity.rule.management.model.Rule; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * Represents an OR combined rule. + * This class extends the Rule class and has a list of ANDCombinedRules. + */ +@JsonDeserialize(builder = ORCombinedRule.Builder.class) +public class ORCombinedRule extends Rule { + + private List rules; + + /** + * Builder for the ORCombinedRule. + */ + private ORCombinedRule(Builder builder) { + + this.id = builder.id; + this.isActive = builder.isActive; + this.rules = builder.rules; + this.condition = Condition.OR; + } + + public Condition getCondition() { + + return condition; + } + + public List getRules() { + + return rules; + } + + /** + * Builder for the ORCombinedRule. + */ + @JsonPOJOBuilder(withPrefix = "set") + public static class Builder { + + private String id; + private boolean isActive = true; + private List rules = new ArrayList<>(); + + public Builder() { + + } + + public Builder(ORCombinedRule orCombinedRule) { + + this.id = orCombinedRule.id; + this.rules = orCombinedRule.rules; + } + + public Builder addRule(ANDCombinedRule andCombinedRule) { + + rules.add(andCombinedRule); + return this; + } + + public Builder setId(String id) { + + this.id = id; + return this; + } + + public Builder setActive(boolean isActive) { + + this.isActive = isActive; + return this; + } + + public Builder setCondition(Condition condition) { + + if (condition != Condition.OR) { + throw new IllegalArgumentException("Condition must be OR for ORCombinedRule"); + } + + return this; + } + + public Builder setRules(List rules) { + + this.rules = rules; + return this; + } + + public ORCombinedRule build() { + + this.id = (this.id == null) ? UUID.randomUUID().toString() : this.id; + return new ORCombinedRule(this); + } + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/internal/RuleData.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/internal/RuleData.java new file mode 100644 index 000000000000..f3e592259c72 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/model/internal/RuleData.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.model.internal; + +/** + * Represents a rule in the data layer. + * This class has the rule JSON and the active status of the rule which is stored in the database. + */ +public class RuleData { + + private String ruleJson; + private boolean isActive; + + public String getRuleJson() { + + return ruleJson; + } + + public void setRuleJson(String ruleJson) { + + this.ruleJson = ruleJson; + } + + public boolean isActive() { + + return isActive; + } + + public void setActive(boolean active) { + + isActive = active; + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/service/RuleManagementService.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/service/RuleManagementService.java new file mode 100644 index 000000000000..57f1b9678bdf --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/service/RuleManagementService.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.service; + +import org.wso2.carbon.identity.rule.management.exception.RuleManagementException; +import org.wso2.carbon.identity.rule.management.model.Rule; + +/** + * This interface is used to define the Rule Management Service. + * This interface has the methods to add, update, delete, get and deactivate rules. + */ +public interface RuleManagementService { + + /** + * Adds a new rule. + * + * @param rule Rule to be added. + * @param tenantDomain Tenant domain. + * @return Added rule. + * @throws RuleManagementException If an error occurs while adding the rule. + */ + public Rule addRule(Rule rule, String tenantDomain) throws RuleManagementException; + + /** + * Updates an existing rule. + * + * @param rule Rule to be updated. + * @param tenantDomain Tenant domain. + * @return Updated rule. + * @throws RuleManagementException If an error occurs while updating the rule. + */ + public Rule updateRule(Rule rule, String tenantDomain) throws RuleManagementException; + + /** + * Deletes a rule. + * + * @param ruleId Rule ID. + * @param tenantDomain Tenant domain. + * @throws RuleManagementException If an error occurs while deleting the rule. + */ + public void deleteRule(String ruleId, String tenantDomain) throws RuleManagementException; + + /** + * Retrieves a rule by rule ID. + * + * @param ruleId Rule ID. + * @param tenantDomain Tenant domain. + * @return Rule. + * @throws RuleManagementException If an error occurs while retrieving the rule. + */ + public Rule getRuleByRuleId(String ruleId, String tenantDomain) throws RuleManagementException; + + /** + * Deactivates a rule. + * + * @param ruleId Rule ID. + * @param tenantDomain Tenant domain. + * @return Deactivated rule. + * @throws RuleManagementException If an error occurs while deactivating the rule. + */ + public Rule deactivateRule(String ruleId, String tenantDomain) throws RuleManagementException; +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/service/impl/RuleManagementServiceImpl.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/service/impl/RuleManagementServiceImpl.java new file mode 100644 index 000000000000..f60a40d38356 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/service/impl/RuleManagementServiceImpl.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.service.impl; + +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.rule.management.dao.RuleManagementDAO; +import org.wso2.carbon.identity.rule.management.dao.impl.CacheBackedRuleManagementDAO; +import org.wso2.carbon.identity.rule.management.dao.impl.RuleManagementDAOImpl; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementClientException; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementException; +import org.wso2.carbon.identity.rule.management.model.Rule; +import org.wso2.carbon.identity.rule.management.service.RuleManagementService; + +/** + * Implementation of Rule Management Service. + */ +public class RuleManagementServiceImpl implements RuleManagementService { + + private static final RuleManagementServiceImpl ruleManagementService = new RuleManagementServiceImpl(); + private final RuleManagementDAO ruleManagementDAO; + + private RuleManagementServiceImpl() { + + ruleManagementDAO = new CacheBackedRuleManagementDAO(new RuleManagementDAOImpl()); + } + + public static RuleManagementServiceImpl getInstance() { + + return ruleManagementService; + } + + /** + * Add a new rule. + * + * @param rule Rule to be added. + * @param tenantDomain Tenant domain. + * @return Added rule. + * @throws RuleManagementException If an error occurs while adding the rule. + */ + @Override + public Rule addRule(Rule rule, String tenantDomain) throws RuleManagementException { + + int tenantId = IdentityTenantUtil.getTenantId(tenantDomain); + ruleManagementDAO.addRule(rule, tenantId); + return ruleManagementDAO.getRuleByRuleId(rule.getId(), tenantId); + } + + /** + * Update an existing rule. + * + * @param rule Rule to be updated. + * @param tenantDomain Tenant domain. + * @return Updated rule. + * @throws RuleManagementException If an error occurs while updating the rule. + */ + @Override + public Rule updateRule(Rule rule, String tenantDomain) throws RuleManagementException { + + validateIfRuleExists(rule.getId(), tenantDomain); + + ruleManagementDAO.updateRule(rule, IdentityTenantUtil.getTenantId(tenantDomain)); + return ruleManagementDAO.getRuleByRuleId(rule.getId(), IdentityTenantUtil.getTenantId(tenantDomain)); + } + + /** + * Delete a rule. + * + * @param ruleId Rule ID. + * @param tenantDomain Tenant domain. + * @throws RuleManagementException If an error occurs while deleting the rule. + */ + @Override + public void deleteRule(String ruleId, String tenantDomain) throws RuleManagementException { + + if (isRuleExists(ruleId, tenantDomain)) { + ruleManagementDAO.deleteRule(ruleId, IdentityTenantUtil.getTenantId(tenantDomain)); + } + } + + /** + * Retrieve a rule by rule ID. + * + * @param ruleId Rule ID. + * @param tenantDomain Tenant domain. + * @return Rule. + * @throws RuleManagementException If an error occurs while retrieving the rule. + */ + @Override + public Rule getRuleByRuleId(String ruleId, String tenantDomain) throws RuleManagementException { + + return ruleManagementDAO.getRuleByRuleId(ruleId, IdentityTenantUtil.getTenantId(tenantDomain)); + } + + /** + * Deactivate a rule. + * + * @param ruleId Rule ID. + * @param tenantDomain Tenant domain. + * @return Deactivated rule. + * @throws RuleManagementException If an error occurs while deactivating the rule. + */ + @Override + public Rule deactivateRule(String ruleId, String tenantDomain) throws RuleManagementException { + + validateIfRuleExists(ruleId, tenantDomain); + + ruleManagementDAO.deactivateRule(ruleId, IdentityTenantUtil.getTenantId(tenantDomain)); + return ruleManagementDAO.getRuleByRuleId(ruleId, IdentityTenantUtil.getTenantId(tenantDomain)); + } + + private void validateIfRuleExists(String ruleId, String tenantDomain) throws RuleManagementException { + + if (!isRuleExists(ruleId, tenantDomain)) { + throw new RuleManagementClientException("Rule not found for the given rule id: " + ruleId); + } + } + + private boolean isRuleExists(String ruleId, String tenantDomain) throws RuleManagementException { + + Rule existingRule = + ruleManagementDAO.getRuleByRuleId(ruleId, IdentityTenantUtil.getTenantId(tenantDomain)); + return existingRule != null; + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/util/RuleBuilder.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/util/RuleBuilder.java new file mode 100644 index 000000000000..e970988d53cd --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/main/java/org/wso2/carbon/identity/rule/management/util/RuleBuilder.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.util; + +import org.wso2.carbon.identity.rule.management.exception.RuleManagementClientException; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementException; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementServerException; +import org.wso2.carbon.identity.rule.management.internal.RuleManagementComponentServiceHolder; +import org.wso2.carbon.identity.rule.management.model.Expression; +import org.wso2.carbon.identity.rule.management.model.FlowType; +import org.wso2.carbon.identity.rule.management.model.Rule; +import org.wso2.carbon.identity.rule.management.model.internal.ANDCombinedRule; +import org.wso2.carbon.identity.rule.management.model.internal.ORCombinedRule; +import org.wso2.carbon.identity.rule.metadata.exception.RuleMetadataException; +import org.wso2.carbon.identity.rule.metadata.model.FieldDefinition; +import org.wso2.carbon.identity.rule.metadata.model.OptionsInputValue; + +import java.util.List; +import java.util.Map; + +/** + * RuleBuilder class is used to build a rule. + * The RuleBuilder instance can be created using the create method for a given flow type and tenant domain. + * This class provides methods to add expressions and conditions to the rule and to build the rule. + * Validations are done while adding expressions and conditions to the rule. + */ +public class RuleBuilder { + + private static final int MAX_EXPRESSIONS_COMBINED_WITH_AND = 5; + private static final int MAX_RULES_COMBINED_WITH_OR = 10; + + private final ORCombinedRule.Builder orCombinedRuleBuilder = new ORCombinedRule.Builder(); + private ANDCombinedRule.Builder andCombinedRuleBuilder = new ANDCombinedRule.Builder(); + private final Map expressionMetadataFieldsMap; + + private boolean isError = false; + private String errorMessage; + + private int andRuleCount = 0; + private int orRuleCount = 0; + + private RuleBuilder(List expressionMetadataFields) { + + this.expressionMetadataFieldsMap = expressionMetadataFields.stream() + .collect(java.util.stream.Collectors.toMap(fieldDefinition -> fieldDefinition.getField().getName(), + fieldDefinition -> fieldDefinition)); + } + + /** + * Add an expression to the rule. + * + * @param expression Expression to be added. + * @return RuleBuilder + */ + public RuleBuilder addAndExpression(Expression expression) { + + validateExpression(expression); + addExpressionForANDCombinedRule(expression); + validateMaxAllowedANDCombinedExpressions(); + return this; + } + + /** + * Add an OR condition to the rule. + * + * @return RuleBuilder + */ + public RuleBuilder addOrCondition() { + + addORCombinedRule(); + validateMaxAllowedORCombinedRules(); + initANDCombinedRule(); + return this; + } + + /** + * Build the rule. + * + * @return Rule + * @throws RuleManagementClientException If an error occurs while building the rule. + */ + public Rule build() throws RuleManagementClientException { + + if (isError) { + // The very first validation error will be thrown as an exception. + throw new RuleManagementClientException( + "Building rule failed due to validation errors. Error: " + errorMessage); + } + + orCombinedRuleBuilder.addRule(andCombinedRuleBuilder.build()); + return orCombinedRuleBuilder.build(); + } + + /** + * Create a RuleBuilder instance. + * + * @param flowType Flow type. + * @param tenantDomain Tenant domain. + * @return RuleBuilder + * @throws RuleManagementException If an error occurs while creating the RuleBuilder instance. + */ + public static RuleBuilder create(FlowType flowType, String tenantDomain) throws RuleManagementException { + + try { + List fieldDefinitionList = + RuleManagementComponentServiceHolder.getInstance().getRuleMetadataService() + .getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.valueOf(flowType.name()), + tenantDomain); + + if (fieldDefinitionList == null || fieldDefinitionList.isEmpty()) { + throw new RuleManagementClientException( + "Expression metadata from RuleMetadataService is null or empty."); + } + + return new RuleBuilder(fieldDefinitionList); + } catch (RuleMetadataException e) { + throw new RuleManagementServerException( + "Error while retrieving expression metadata from RuleMetadataService.", e); + } + } + + private void addExpressionForANDCombinedRule(Expression expression) { + + andCombinedRuleBuilder.addExpression(expression); + andRuleCount++; + } + + private void initANDCombinedRule() { + + andCombinedRuleBuilder = new ANDCombinedRule.Builder(); + andRuleCount = 0; + } + + private void addORCombinedRule() { + + orCombinedRuleBuilder.addRule(andCombinedRuleBuilder.build()); + orRuleCount++; + } + + private void validateExpression(Expression expression) { + + FieldDefinition fieldDefinition = expressionMetadataFieldsMap.get(expression.getField()); + + if (isError) { + return; + } + + if (fieldDefinition == null) { + setValidationError("Field " + expression.getField() + " is not supported"); + return; + } + + if (fieldDefinition.getOperators().stream() + .noneMatch(operator -> operator.getName().equals(expression.getOperator()))) { + setValidationError("Operator " + expression.getOperator() + " is not supported for field " + + expression.getField()); + return; + } + + if (!fieldDefinition.getValue().getValueType().name().equals(expression.getValue().getType().name())) { + setValidationError("Value type " + expression.getValue().getType().name() + " is not supported for field " + + expression.getField()); + } + + if (fieldDefinition.getValue() instanceof OptionsInputValue && + ((OptionsInputValue) fieldDefinition.getValue()).getValues().stream().noneMatch( + optionsValue -> optionsValue.getName().equals(expression.getValue().getFieldValue()))) { + setValidationError("Value " + expression.getValue().getFieldValue() + " is not supported for field " + + expression.getField()); + } + } + + private void validateMaxAllowedANDCombinedExpressions() { + + if (isError) { + return; + } + + if (andRuleCount > MAX_EXPRESSIONS_COMBINED_WITH_AND) { + setValidationError("Maximum number of expressions combined with AND exceeded. Maximum allowed: " + + MAX_EXPRESSIONS_COMBINED_WITH_AND + " Provided: " + andRuleCount); + } + } + + private void validateMaxAllowedORCombinedRules() { + + if (isError) { + return; + } + + if (orRuleCount > MAX_RULES_COMBINED_WITH_OR) { + setValidationError("Maximum number of rules combined with OR exceeded. Maximum allowed: " + + MAX_RULES_COMBINED_WITH_OR + " Provided: " + orRuleCount); + } + } + + private void setValidationError(String message) { + + isError = true; + errorMessage = message; + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/java/org/wso2/carbon/identity/rule/management/dao/CacheBackedRuleManagementDAOTest.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/java/org/wso2/carbon/identity/rule/management/dao/CacheBackedRuleManagementDAOTest.java new file mode 100644 index 000000000000..fbe7f6661041 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/java/org/wso2/carbon/identity/rule/management/dao/CacheBackedRuleManagementDAOTest.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.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.IdentityCoreServiceDataHolder; +import org.wso2.carbon.identity.rule.management.cache.RuleCache; +import org.wso2.carbon.identity.rule.management.cache.RuleCacheEntry; +import org.wso2.carbon.identity.rule.management.cache.RuleCacheKey; +import org.wso2.carbon.identity.rule.management.dao.impl.CacheBackedRuleManagementDAO; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementException; +import org.wso2.carbon.identity.rule.management.model.Rule; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; + +@WithCarbonHome +@WithRealmService(injectToSingletons = {IdentityCoreServiceDataHolder.class}) +public class CacheBackedRuleManagementDAOTest { + + private RuleManagementDAO ruleManagementDAO; + private CacheBackedRuleManagementDAO cacheBackedRuleManagementDAO; + private RuleCache ruleCache; + + public static final String RULE_ID = "ruleId"; + public static final int TENANT_ID = 1; + + @BeforeClass + public void setUpClass() { + + ruleCache = RuleCache.getInstance(); + } + + @BeforeMethod + public void setUp() { + + ruleManagementDAO = mock(RuleManagementDAO.class); + cacheBackedRuleManagementDAO = new CacheBackedRuleManagementDAO(ruleManagementDAO); + } + + @Test + public void testAddRule() throws RuleManagementException { + + Rule rule = mock(Rule.class); + + cacheBackedRuleManagementDAO.addRule(rule, TENANT_ID); + + verify(ruleManagementDAO).addRule(rule, TENANT_ID); + } + + @Test + public void testUpdateRule() throws RuleManagementException { + + Rule rule = mock(Rule.class); + when(rule.getId()).thenReturn(RULE_ID); + + cacheBackedRuleManagementDAO.updateRule(rule, TENANT_ID); + + verify(ruleManagementDAO).updateRule(rule, TENANT_ID); + assertNull(ruleCache.getValueFromCache(new RuleCacheKey(RULE_ID), TENANT_ID)); + } + + @Test + public void testUpdateRuleWhenCacheIsPopulated() throws RuleManagementException { + + Rule rule = mock(Rule.class); + when(rule.getId()).thenReturn(RULE_ID); + RuleCacheEntry cacheEntry = new RuleCacheEntry(rule); + ruleCache.addToCache(new RuleCacheKey(RULE_ID), cacheEntry, TENANT_ID); + + cacheBackedRuleManagementDAO.updateRule(rule, TENANT_ID); + + verify(ruleManagementDAO).updateRule(rule, TENANT_ID); + assertNull(ruleCache.getValueFromCache(new RuleCacheKey(RULE_ID), TENANT_ID)); + } + + @Test + public void testDeleteRule() throws RuleManagementException { + + cacheBackedRuleManagementDAO.deleteRule(RULE_ID, TENANT_ID); + + verify(ruleManagementDAO).deleteRule(RULE_ID, TENANT_ID); + assertNull(ruleCache.getValueFromCache(new RuleCacheKey(RULE_ID), TENANT_ID)); + } + + @Test + public void testDeleteRuleWhenCacheIsPopulated() throws RuleManagementException { + + Rule rule = mock(Rule.class); + when(rule.getId()).thenReturn(RULE_ID); + RuleCacheEntry cacheEntry = new RuleCacheEntry(rule); + ruleCache.addToCache(new RuleCacheKey(RULE_ID), cacheEntry, TENANT_ID); + + cacheBackedRuleManagementDAO.deleteRule(RULE_ID, TENANT_ID); + + verify(ruleManagementDAO).deleteRule(RULE_ID, TENANT_ID); + assertNull(ruleCache.getValueFromCache(new RuleCacheKey(RULE_ID), TENANT_ID)); + } + + @Test + public void testGetRuleByRuleIdCacheHit() throws RuleManagementException { + + Rule rule = mock(Rule.class); + RuleCacheEntry cacheEntry = new RuleCacheEntry(rule); + ruleCache.addToCache(new RuleCacheKey(RULE_ID), cacheEntry, TENANT_ID); + + Rule result = cacheBackedRuleManagementDAO.getRuleByRuleId(RULE_ID, TENANT_ID); + + assertEquals(result, rule); + verify(ruleManagementDAO, never()).getRuleByRuleId(RULE_ID, TENANT_ID); + } + + @Test + public void testGetRuleByRuleIdCacheMiss() throws RuleManagementException { + + ruleCache.clear(TENANT_ID); + + Rule rule = mock(Rule.class); + when(rule.getId()).thenReturn(RULE_ID); + when(ruleManagementDAO.getRuleByRuleId(RULE_ID, TENANT_ID)).thenReturn(rule); + + Rule result = cacheBackedRuleManagementDAO.getRuleByRuleId(RULE_ID, TENANT_ID); + + assertEquals(result, rule); + verify(ruleManagementDAO).getRuleByRuleId(RULE_ID, TENANT_ID); + // Verify that the rule is added to the cache + assertEquals(rule, ruleCache.getValueFromCache(new RuleCacheKey(RULE_ID), TENANT_ID).getRule()); + } + + @Test + public void testActivateRule() throws RuleManagementException { + + cacheBackedRuleManagementDAO.activateRule(RULE_ID, TENANT_ID); + + verify(ruleManagementDAO).activateRule(RULE_ID, TENANT_ID); + assertNull(ruleCache.getValueFromCache(new RuleCacheKey(RULE_ID), TENANT_ID)); + } + + @Test + public void testActivateRuleWhenCacheIsPopulated() throws RuleManagementException { + + Rule rule = mock(Rule.class); + when(rule.getId()).thenReturn(RULE_ID); + RuleCacheEntry cacheEntry = new RuleCacheEntry(rule); + ruleCache.addToCache(new RuleCacheKey(RULE_ID), cacheEntry, TENANT_ID); + + cacheBackedRuleManagementDAO.activateRule(RULE_ID, TENANT_ID); + + verify(ruleManagementDAO).activateRule(RULE_ID, TENANT_ID); + assertNull(ruleCache.getValueFromCache(new RuleCacheKey(RULE_ID), TENANT_ID)); + } + + @Test + public void testDeactivateRule() throws RuleManagementException { + + cacheBackedRuleManagementDAO.deactivateRule(RULE_ID, TENANT_ID); + + verify(ruleManagementDAO).deactivateRule(RULE_ID, TENANT_ID); + assertNull(ruleCache.getValueFromCache(new RuleCacheKey(RULE_ID), TENANT_ID)); + } + + @Test + public void testDeactivateRuleWhenCacheIsPopulated() throws RuleManagementException { + + Rule rule = mock(Rule.class); + when(rule.getId()).thenReturn(RULE_ID); + RuleCacheEntry cacheEntry = new RuleCacheEntry(rule); + ruleCache.addToCache(new RuleCacheKey(RULE_ID), cacheEntry, TENANT_ID); + + cacheBackedRuleManagementDAO.deactivateRule(RULE_ID, TENANT_ID); + + verify(ruleManagementDAO).deactivateRule(RULE_ID, TENANT_ID); + assertNull(ruleCache.getValueFromCache(new RuleCacheKey(RULE_ID), TENANT_ID)); + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/java/org/wso2/carbon/identity/rule/management/dao/RuleManagementDAOImplTest.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/java/org/wso2/carbon/identity/rule/management/dao/RuleManagementDAOImplTest.java new file mode 100644 index 000000000000..63a54980ec53 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/java/org/wso2/carbon/identity/rule/management/dao/RuleManagementDAOImplTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.dao; + +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.rule.management.dao.impl.RuleManagementDAOImpl; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementException; +import org.wso2.carbon.identity.rule.management.model.Expression; +import org.wso2.carbon.identity.rule.management.model.Rule; +import org.wso2.carbon.identity.rule.management.model.Value; +import org.wso2.carbon.identity.rule.management.model.internal.ANDCombinedRule; +import org.wso2.carbon.identity.rule.management.model.internal.ORCombinedRule; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +@WithH2Database(files = {"dbscripts/h2.sql"}) +@WithCarbonHome +public class RuleManagementDAOImplTest { + + public static final int TENANT_ID = 1; + private Rule createdRule; + RuleManagementDAOImpl ruleManagementDAOImpl = new RuleManagementDAOImpl(); + + @Test + public void testAddRule() throws RuleManagementException { + + Expression expression1 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp1")).build(); + + Expression expression2 = new Expression.Builder().field("grantType").operator("equals") + .value(new Value(Value.Type.STRING, "authorization_code")).build(); + ANDCombinedRule andCombinedRule1 = + new ANDCombinedRule.Builder().addExpression(expression1).addExpression(expression2).build(); + + Expression expression3 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp2")).build(); + ANDCombinedRule andCombinedRule2 = + new ANDCombinedRule.Builder().addExpression(expression3).build(); + + ORCombinedRule orCombinedRule = + new ORCombinedRule.Builder().addRule(andCombinedRule1).addRule(andCombinedRule2).build(); + + ruleManagementDAOImpl.addRule(orCombinedRule, TENANT_ID); + + createdRule = ruleManagementDAOImpl.getRuleByRuleId(orCombinedRule.getId(), TENANT_ID); + assertNotNull(createdRule); + assertEquals(orCombinedRule.getId(), createdRule.getId()); + assertTrue(createdRule.isActive()); + + ORCombinedRule retrievedORCombinedRule = assertOrCombinedRule(createdRule, 2); + ANDCombinedRule retrievedAndCombinedRule1 = assertAndCombinedRule(retrievedORCombinedRule.getRules().get(0), 2); + assertExpressions(retrievedAndCombinedRule1, expression1, expression2); + ANDCombinedRule retrievedAndCombinedRule2 = assertAndCombinedRule(retrievedORCombinedRule.getRules().get(1), 1); + assertExpressions(retrievedAndCombinedRule2, expression3); + } + + @Test(dependsOnMethods = {"testAddRule"}) + public void testAddRuleWithMultipleExpressionsUsingSameFieldReferenceAndOR() throws RuleManagementException { + + Expression expression1 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp1")).build(); + + Expression expression2 = new Expression.Builder().field("grantType").operator("equals") + .value(new Value(Value.Type.STRING, "authorization_code")).build(); + + ANDCombinedRule andCombinedRule1 = + new ANDCombinedRule.Builder().addExpression(expression1).addExpression(expression2).build(); + + Expression expression3 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp1")).build(); + + Expression expression4 = new Expression.Builder().field("grantType").operator("equals") + .value(new Value(Value.Type.STRING, "client_credentials")).build(); + + ANDCombinedRule andCombinedRule2 = + new ANDCombinedRule.Builder().addExpression(expression3).addExpression(expression4).build(); + + Expression expression5 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp2")).build(); + ANDCombinedRule andCombinedRule3 = + new ANDCombinedRule.Builder().addExpression(expression5).build(); + + ORCombinedRule orCombinedRule = + new ORCombinedRule.Builder().addRule(andCombinedRule1).addRule(andCombinedRule2) + .addRule(andCombinedRule3).build(); + + ruleManagementDAOImpl.addRule(orCombinedRule, TENANT_ID); + + createdRule = ruleManagementDAOImpl.getRuleByRuleId(orCombinedRule.getId(), TENANT_ID); + assertNotNull(createdRule); + assertEquals(orCombinedRule.getId(), createdRule.getId()); + assertTrue(createdRule.isActive()); + + ORCombinedRule retrievedORCombinedRule = assertOrCombinedRule(createdRule, 3); + ANDCombinedRule retrievedAndCombinedRule1 = assertAndCombinedRule(retrievedORCombinedRule.getRules().get(0), 2); + assertExpressions(retrievedAndCombinedRule1, expression1, expression2); + ANDCombinedRule retrievedAndCombinedRule2 = assertAndCombinedRule(retrievedORCombinedRule.getRules().get(1), 2); + assertExpressions(retrievedAndCombinedRule2, expression3, expression4); + ANDCombinedRule retrievedAndCombinedRule3 = assertAndCombinedRule(retrievedORCombinedRule.getRules().get(2), 1); + assertExpressions(retrievedAndCombinedRule3, expression5); + } + + @Test(dependsOnMethods = {"testAddRuleWithMultipleExpressionsUsingSameFieldReferenceAndOR"}) + public void testUpdateRule() throws RuleManagementException { + + Expression expression1 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp1")).build(); + + Expression expression2 = new Expression.Builder().field("grantType").operator("equals") + .value(new Value(Value.Type.STRING, "password")).build(); + ANDCombinedRule andCombinedRule1 = + new ANDCombinedRule.Builder().addExpression(expression1).addExpression(expression2).build(); + + Expression expression3 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp2")).build(); + ANDCombinedRule andCombinedRule2 = + new ANDCombinedRule.Builder().addExpression(expression3).build(); + + Expression expression4 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp4")).build(); + ANDCombinedRule andCombinedRule3 = + new ANDCombinedRule.Builder().addExpression(expression4).build(); + + ORCombinedRule orCombinedRule = + new ORCombinedRule.Builder().setId(createdRule.getId()).addRule(andCombinedRule1) + .addRule(andCombinedRule2) + .addRule(andCombinedRule3).build(); + + ruleManagementDAOImpl.updateRule(orCombinedRule, TENANT_ID); + + Rule updatedRule = ruleManagementDAOImpl.getRuleByRuleId(createdRule.getId(), TENANT_ID); + assertNotNull(updatedRule); + assertEquals(createdRule.getId(), updatedRule.getId()); + assertTrue(updatedRule.isActive()); + + ORCombinedRule retrievedORCombinedRule = assertOrCombinedRule(updatedRule, 3); + ANDCombinedRule retrievedAndCombinedRule1 = assertAndCombinedRule(retrievedORCombinedRule.getRules().get(0), 2); + assertExpressions(retrievedAndCombinedRule1, expression1, expression2); + ANDCombinedRule retrievedAndCombinedRule2 = assertAndCombinedRule(retrievedORCombinedRule.getRules().get(1), 1); + assertExpressions(retrievedAndCombinedRule2, expression3); + ANDCombinedRule retrievedAndCombinedRule3 = assertAndCombinedRule(retrievedORCombinedRule.getRules().get(2), 1); + assertExpressions(retrievedAndCombinedRule3, expression4); + } + + @Test(dependsOnMethods = {"testUpdateRule"}) + public void testDeactivateRule() throws RuleManagementException { + + ruleManagementDAOImpl.deactivateRule(createdRule.getId(), TENANT_ID); + Rule deactivatedRule = ruleManagementDAOImpl.getRuleByRuleId(createdRule.getId(), TENANT_ID); + assertNotNull(deactivatedRule); + assertEquals(deactivatedRule.getId(), createdRule.getId()); + assertFalse(deactivatedRule.isActive()); + } + + @Test(dependsOnMethods = {"testDeactivateRule"}) + public void testActivateRule() throws RuleManagementException { + + ruleManagementDAOImpl.activateRule(createdRule.getId(), TENANT_ID); + Rule activatedRule = ruleManagementDAOImpl.getRuleByRuleId(createdRule.getId(), TENANT_ID); + assertNotNull(activatedRule); + assertEquals(activatedRule.getId(), createdRule.getId()); + assertTrue(activatedRule.isActive()); + } + + @Test(dependsOnMethods = {"testActivateRule"}) + public void testDeleteRule() throws RuleManagementException { + + ruleManagementDAOImpl.deleteRule(createdRule.getId(), TENANT_ID); + Rule deletedRule = ruleManagementDAOImpl.getRuleByRuleId(createdRule.getId(), TENANT_ID); + assertNull(deletedRule); + } + + private static void assertExpressions(ANDCombinedRule andCombinedRule, Expression... expressions) { + + assertEquals(andCombinedRule.getExpressions().size(), expressions.length); + for (int i = 0; i < expressions.length; i++) { + assertEquals(andCombinedRule.getExpressions().get(i).getField(), expressions[i].getField()); + assertEquals(andCombinedRule.getExpressions().get(i).getOperator(), expressions[i].getOperator()); + assertEquals(andCombinedRule.getExpressions().get(i).getValue().getType(), + expressions[i].getValue().getType()); + assertEquals(andCombinedRule.getExpressions().get(i).getValue().getFieldValue(), + expressions[i].getValue().getFieldValue()); + } + } + + private static ANDCombinedRule assertAndCombinedRule(Rule andRule, int expectedExpressionsSize) { + + assertTrue(andRule instanceof ANDCombinedRule); + + ANDCombinedRule andCombinedRule = (ANDCombinedRule) andRule; + assertNotNull(andCombinedRule.getId()); + assertTrue(andCombinedRule.isActive()); + assertNotNull(andCombinedRule.getExpressions()); + assertEquals(andCombinedRule.getExpressions().size(), expectedExpressionsSize); + return andCombinedRule; + } + + private static ORCombinedRule assertOrCombinedRule(Rule rule, int expectedRulesSize) { + + assertTrue(rule instanceof ORCombinedRule); + ORCombinedRule orCombinedRule = (ORCombinedRule) rule; + assertNotNull(orCombinedRule.getRules()); + assertEquals(orCombinedRule.getRules().size(), expectedRulesSize); + return orCombinedRule; + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/java/org/wso2/carbon/identity/rule/management/service/RuleManagementServiceImplTest.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/java/org/wso2/carbon/identity/rule/management/service/RuleManagementServiceImplTest.java new file mode 100644 index 000000000000..f32f420ff919 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/java/org/wso2/carbon/identity/rule/management/service/RuleManagementServiceImplTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.service; + +import org.mockito.MockedStatic; +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.internal.IdentityCoreServiceDataHolder; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.rule.management.dao.RuleManagementDAO; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementClientException; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementException; +import org.wso2.carbon.identity.rule.management.model.Rule; +import org.wso2.carbon.identity.rule.management.service.impl.RuleManagementServiceImpl; + +import java.lang.reflect.Field; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + +@WithCarbonHome +@WithRealmService(injectToSingletons = {IdentityCoreServiceDataHolder.class}) +public class RuleManagementServiceImplTest { + + private RuleManagementServiceImpl ruleManagementService; + private RuleManagementDAO ruleManagementDAO; + + private MockedStatic identityTenantUtilMockedStatic; + + public static final String RULE_ID = "ruleId"; + public static final String TENANT_DOMAIN = "test.com"; + public static final int TENANT_ID = 1; + + @BeforeClass + public void setUpClass() { + + ruleManagementService = RuleManagementServiceImpl.getInstance(); + + identityTenantUtilMockedStatic = mockStatic(IdentityTenantUtil.class); + when(IdentityTenantUtil.getTenantId(TENANT_DOMAIN)).thenReturn(TENANT_ID); + } + + @AfterClass + public void tearDownClass() { + + identityTenantUtilMockedStatic.close(); + } + + @BeforeMethod + public void setUp() throws Exception { + + ruleManagementDAO = mock(RuleManagementDAO.class); + Field daoField = RuleManagementServiceImpl.class.getDeclaredField("ruleManagementDAO"); + daoField.setAccessible(true); + daoField.set(ruleManagementService, ruleManagementDAO); + } + + @Test + public void testAddRule() throws RuleManagementException { + + Rule rule = mock(Rule.class); + when(rule.getId()).thenReturn(RULE_ID); + + when(ruleManagementDAO.getRuleByRuleId(RULE_ID, TENANT_ID)).thenReturn(rule); + + Rule result = ruleManagementService.addRule(rule, TENANT_DOMAIN); + + verify(ruleManagementDAO).addRule(rule, TENANT_ID); + verify(ruleManagementDAO).getRuleByRuleId(RULE_ID, TENANT_ID); + + assertEquals(result, rule); + } + + @Test + public void testUpdateRule() throws RuleManagementException { + + Rule rule = mock(Rule.class); + when(rule.getId()).thenReturn(RULE_ID); + when(ruleManagementDAO.getRuleByRuleId(RULE_ID, TENANT_ID)).thenReturn(rule); + + Rule result = ruleManagementService.updateRule(rule, TENANT_DOMAIN); + + verify(ruleManagementDAO).updateRule(rule, TENANT_ID); + verify(ruleManagementDAO, times(2)).getRuleByRuleId(RULE_ID, TENANT_ID); + assertEquals(result, rule); + } + + @Test + public void testDeleteRule() throws RuleManagementException { + + Rule rule = mock(Rule.class); + when(ruleManagementDAO.getRuleByRuleId(RULE_ID, TENANT_ID)).thenReturn(rule); + + ruleManagementService.deleteRule(RULE_ID, TENANT_DOMAIN); + + verify(ruleManagementDAO).deleteRule(RULE_ID, TENANT_ID); + } + + @Test + public void testGetRuleByRuleId() throws RuleManagementException { + + Rule rule = mock(Rule.class); + when(ruleManagementDAO.getRuleByRuleId(RULE_ID, TENANT_ID)).thenReturn(rule); + + Rule result = ruleManagementService.getRuleByRuleId(RULE_ID, TENANT_DOMAIN); + + verify(ruleManagementDAO).getRuleByRuleId(RULE_ID, TENANT_ID); + assertEquals(result, rule); + } + + @Test + public void testDeactivateRule() throws RuleManagementException { + + Rule rule = mock(Rule.class); + when(ruleManagementDAO.getRuleByRuleId(RULE_ID, TENANT_ID)).thenReturn(rule); + + Rule result = ruleManagementService.deactivateRule(RULE_ID, TENANT_DOMAIN); + + verify(ruleManagementDAO).deactivateRule(RULE_ID, TENANT_ID); + verify(ruleManagementDAO, times(2)).getRuleByRuleId(RULE_ID, TENANT_ID); + assertEquals(result, rule); + } + + @Test(expectedExceptions = RuleManagementClientException.class, expectedExceptionsMessageRegExp = + "Rule not found for the given rule id: " + RULE_ID) + public void testUpdateIfRuleNotExist() throws RuleManagementException { + + when(ruleManagementDAO.getRuleByRuleId(RULE_ID, TENANT_ID)).thenReturn(null); + + Rule rule = mock(Rule.class); + when(rule.getId()).thenReturn(RULE_ID); + ruleManagementService.updateRule(rule, TENANT_DOMAIN); + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/java/org/wso2/carbon/identity/rule/management/util/RuleBuilderTest.java b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/java/org/wso2/carbon/identity/rule/management/util/RuleBuilderTest.java new file mode 100644 index 000000000000..dc8751dc2d91 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/java/org/wso2/carbon/identity/rule/management/util/RuleBuilderTest.java @@ -0,0 +1,476 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.rule.management.util; + +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementClientException; +import org.wso2.carbon.identity.rule.management.exception.RuleManagementServerException; +import org.wso2.carbon.identity.rule.management.internal.RuleManagementComponentServiceHolder; +import org.wso2.carbon.identity.rule.management.model.Expression; +import org.wso2.carbon.identity.rule.management.model.FlowType; +import org.wso2.carbon.identity.rule.management.model.Rule; +import org.wso2.carbon.identity.rule.management.model.Value; +import org.wso2.carbon.identity.rule.management.model.internal.ANDCombinedRule; +import org.wso2.carbon.identity.rule.management.model.internal.ORCombinedRule; +import org.wso2.carbon.identity.rule.metadata.config.OperatorConfig; +import org.wso2.carbon.identity.rule.metadata.config.RuleMetadataConfigFactory; +import org.wso2.carbon.identity.rule.metadata.exception.RuleMetadataException; +import org.wso2.carbon.identity.rule.metadata.model.Field; +import org.wso2.carbon.identity.rule.metadata.model.FieldDefinition; +import org.wso2.carbon.identity.rule.metadata.model.Link; +import org.wso2.carbon.identity.rule.metadata.model.Operator; +import org.wso2.carbon.identity.rule.metadata.model.OptionsInputValue; +import org.wso2.carbon.identity.rule.metadata.model.OptionsReferenceValue; +import org.wso2.carbon.identity.rule.metadata.model.OptionsValue; +import org.wso2.carbon.identity.rule.metadata.service.RuleMetadataService; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +public class RuleBuilderTest { + + @Mock + RuleMetadataService ruleMetadataService; + private MockedStatic ruleMetadataConfigFactoryMockedStatic; + OperatorConfig operatorConfig; + + @BeforeClass + public void setUpClass() throws Exception { + + String filePath = Objects.requireNonNull(getClass().getClassLoader().getResource( + "configs/valid-operators.json")).getFile(); + operatorConfig = OperatorConfig.load(new File(filePath)); + } + + @BeforeMethod + public void setUpMethod() { + + ruleMetadataConfigFactoryMockedStatic = mockStatic(RuleMetadataConfigFactory.class); + ruleMetadataConfigFactoryMockedStatic.when(RuleMetadataConfigFactory::getOperatorConfig) + .thenReturn(operatorConfig); + + MockitoAnnotations.openMocks(this); + RuleManagementComponentServiceHolder.getInstance().setRuleMetadataService(ruleMetadataService); + } + + @AfterMethod + public void tearDownMethod() { + + ruleMetadataConfigFactoryMockedStatic.close(); + } + + @Test(expectedExceptions = RuleManagementClientException.class, + expectedExceptionsMessageRegExp = "Expression metadata from RuleMetadataService is null or empty.") + public void testCreateRuleBuilderWhenExpressionMetaReturnedFromMetadataServiceIsNull() + throws Exception { + + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")).thenReturn( + null); + + RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + } + + @Test(expectedExceptions = RuleManagementClientException.class, + expectedExceptionsMessageRegExp = "Expression metadata from RuleMetadataService is null or empty.") + public void testCreateRuleBuilderWhenExpressionMetaReturnedFromMetadataServiceIsEmpty() + throws Exception { + + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")).thenReturn( + Collections.emptyList()); + + RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + } + + @Test(expectedExceptions = RuleManagementServerException.class, + expectedExceptionsMessageRegExp = "Error while retrieving expression metadata from RuleMetadataService.") + public void testCreateRuleBuilderWhenMetadataServiceThrowsException() + throws Exception { + + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")).thenThrow( + new RuleMetadataException("RULEMETA-60005", "Error while retrieving expression metadata.", + "Failed to load data from configurations.")); + + RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + } + + @Test + public void testCreateRuleBuilder() throws Exception { + + List mockedFieldDefinitions = getMockedFieldDefinitions(); + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")).thenReturn( + mockedFieldDefinitions); + + RuleBuilder ruleBuilder = RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + assertNotNull(ruleBuilder); + } + + @Test + public void testCreateRuleWithValidExpressions() throws Exception { + + List mockedFieldDefinitions = getMockedFieldDefinitions(); + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")).thenReturn( + mockedFieldDefinitions); + + RuleBuilder ruleBuilder = RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + + Expression expression1 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp")).build(); + ruleBuilder.addAndExpression(expression1); + + Expression expression2 = new Expression.Builder().field("grantType").operator("equals") + .value(new Value(Value.Type.STRING, "authorization_code")).build(); + ruleBuilder.addAndExpression(expression2); + Rule rule = ruleBuilder.build(); + + assertNotNull(rule); + assertNotNull(rule.getId()); + assertTrue(rule.isActive()); + + ORCombinedRule orCombinedRule = assertOrCombinedRule(rule, 1); + + orCombinedRule.getRules() + .forEach(andRule -> assertExpressions(assertAndCombinedRule(andRule, 2), expression1, expression2)); + } + + @Test(expectedExceptions = RuleManagementClientException.class, + expectedExceptionsMessageRegExp = "Building rule failed due to validation errors. " + + "Error: Field invalid is not supported") + public void testCreateRuleWithAnExpressionWithInvalidField() throws Exception { + + List mockedFieldDefinitions = getMockedFieldDefinitions(); + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")).thenReturn( + mockedFieldDefinitions); + + RuleBuilder ruleBuilder = RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + + Expression expression = new Expression.Builder().field("invalid").operator("equals") + .value(new Value(Value.Type.STRING, "value")).build(); + ruleBuilder.addAndExpression(expression); + + ruleBuilder.build(); + } + + @Test(expectedExceptions = RuleManagementClientException.class, + expectedExceptionsMessageRegExp = "Building rule failed due to validation errors. " + + "Error: Operator invalid is not supported for field application") + public void testCreateRuleWithAnExpressionWithInvalidOperator() throws Exception { + + List mockedFieldDefinitions = getMockedFieldDefinitions(); + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")) + .thenReturn(mockedFieldDefinitions); + + RuleBuilder ruleBuilder = RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + + Expression expression = new Expression.Builder().field("application").operator("invalid") + .value(new Value(Value.Type.STRING, "value")).build(); + ruleBuilder.addAndExpression(expression); + + ruleBuilder.build(); + } + + @Test(expectedExceptions = RuleManagementClientException.class, + expectedExceptionsMessageRegExp = "Building rule failed due to validation errors. " + + "Error: Value invalid is not supported for field grantType") + public void testCreateRuleWithAnExpressionWithInvalidValue() throws Exception { + + List mockedFieldDefinitions = getMockedFieldDefinitions(); + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")) + .thenReturn(mockedFieldDefinitions); + + RuleBuilder ruleBuilder = RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + + Expression expression = new Expression.Builder().field("grantType").operator("equals") + .value(new Value(Value.Type.STRING, "invalid")).build(); + ruleBuilder.addAndExpression(expression); + + ruleBuilder.build(); + } + + @Test(expectedExceptions = RuleManagementClientException.class, + expectedExceptionsMessageRegExp = "Building rule failed due to validation errors. " + + "Error: Value type STRING is not supported for field application") + public void testCreateRuleWithAnExpressionWithInvalidValueType() throws Exception { + + List mockedFieldDefinitions = getMockedFieldDefinitions(); + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")) + .thenReturn(mockedFieldDefinitions); + + RuleBuilder ruleBuilder = RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + + Expression expression = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.STRING, "testapp")).build(); + ruleBuilder.addAndExpression(expression); + + ruleBuilder.build(); + } + + /** + * This test case is to test the scenario where multiple validation failures occur when building a rule. + * In this case, only the very first validation failure is expected to be thrown. + * + * @throws Exception Client exception when building the rule + */ + @Test(expectedExceptions = RuleManagementClientException.class, + expectedExceptionsMessageRegExp = "Building rule failed due to validation errors. " + + "Error: Field invalid is not supported") + public void testCreateRuleWithMultipleValidationFailures() throws Exception { + + List mockedFieldDefinitions = getMockedFieldDefinitions(); + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")) + .thenReturn(mockedFieldDefinitions); + + RuleBuilder ruleBuilder = RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + + // Expression with invalid field + Expression expression1 = new Expression.Builder().field("invalid").operator("equals") + .value(new Value(Value.Type.STRING, "value")).build(); + ruleBuilder.addAndExpression(expression1); + + // Expression with invalid operator + Expression expression2 = new Expression.Builder().field("grantType").operator("equals") + .value(new Value(Value.Type.STRING, "invalid")).build(); + ruleBuilder.addAndExpression(expression2); + + ruleBuilder.addOrCondition(); + + // Expression with invalid value type + Expression expression3 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.STRING, "testapp")).build(); + ruleBuilder.addAndExpression(expression3); + + ruleBuilder.build(); + } + + @Test(expectedExceptions = RuleManagementClientException.class, + expectedExceptionsMessageRegExp = "Building rule failed due to validation errors. " + + "Error: Maximum number of expressions combined with AND exceeded. Maximum allowed: 5 Provided: 6") + public void testCreateRuleWithMaxAllowedExpressionsCombinedWithAND() throws Exception { + + List mockedFieldDefinitions = getMockedFieldDefinitions(); + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")).thenReturn( + mockedFieldDefinitions); + + RuleBuilder ruleBuilder = RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + + for (int i = 0; i < 6; i++) { + Expression expression = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp" + i)).build(); + ruleBuilder.addAndExpression(expression); + } + + ruleBuilder.build(); + } + + @Test(expectedExceptions = RuleManagementClientException.class, + expectedExceptionsMessageRegExp = "Building rule failed due to validation errors. " + + "Error: Maximum number of rules combined with OR exceeded. Maximum allowed: 10 Provided: 11") + public void testCreateRuleWithMaxAllowedRulesCombinedWithOR() throws Exception { + + List mockedFieldDefinitions = getMockedFieldDefinitions(); + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")).thenReturn( + mockedFieldDefinitions); + + RuleBuilder ruleBuilder = RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + + for (int i = 0; i < 11; i++) { + Expression expression = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp" + i)).build(); + ruleBuilder.addAndExpression(expression); + ruleBuilder.addOrCondition(); + } + + ruleBuilder.build(); + } + + @Test + public void testCreateRuleWithValidExpressionsAndOR() throws Exception { + + List mockedFieldDefinitions = getMockedFieldDefinitions(); + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")).thenReturn( + mockedFieldDefinitions); + + RuleBuilder ruleBuilder = RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + + Expression expression1 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp1")).build(); + ruleBuilder.addAndExpression(expression1); + + Expression expression2 = new Expression.Builder().field("grantType").operator("equals") + .value(new Value(Value.Type.STRING, "authorization_code")).build(); + ruleBuilder.addAndExpression(expression2); + + ruleBuilder.addOrCondition(); + + Expression expression3 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp2")).build(); + ruleBuilder.addAndExpression(expression3); + + Rule rule = ruleBuilder.build(); + + assertNotNull(rule); + assertNotNull(rule.getId()); + assertTrue(rule.isActive()); + + ORCombinedRule orCombinedRule = assertOrCombinedRule(rule, 2); + ANDCombinedRule andCombinedRule = assertAndCombinedRule(orCombinedRule.getRules().get(0), 2); + assertExpressions(andCombinedRule, expression1, expression2); + + andCombinedRule = assertAndCombinedRule(orCombinedRule.getRules().get(1), 1); + assertExpressions(andCombinedRule, expression3); + } + + @Test + public void testCreateRuleWithMultipleExpressionsUsingSameFieldReferenceAndOR() throws Exception { + + List mockedFieldDefinitions = getMockedFieldDefinitions(); + when(ruleMetadataService.getExpressionMeta( + org.wso2.carbon.identity.rule.metadata.model.FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1")).thenReturn( + mockedFieldDefinitions); + + RuleBuilder ruleBuilder = RuleBuilder.create(FlowType.PRE_ISSUE_ACCESS_TOKEN, "tenant1"); + + Expression expression1 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp1")).build(); + ruleBuilder.addAndExpression(expression1); + + Expression expression2 = new Expression.Builder().field("grantType").operator("equals") + .value(new Value(Value.Type.STRING, "authorization_code")).build(); + ruleBuilder.addAndExpression(expression2); + + ruleBuilder.addOrCondition(); + + Expression expression3 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp1")).build(); + ruleBuilder.addAndExpression(expression3); + + Expression expression4 = new Expression.Builder().field("grantType").operator("equals") + .value(new Value(Value.Type.STRING, "client_credentials")).build(); + ruleBuilder.addAndExpression(expression4); + + ruleBuilder.addOrCondition(); + + Expression expression5 = new Expression.Builder().field("application").operator("equals") + .value(new Value(Value.Type.REFERENCE, "testapp2")).build(); + ruleBuilder.addAndExpression(expression5); + + Rule rule = ruleBuilder.build(); + + assertNotNull(rule); + assertNotNull(rule.getId()); + assertTrue(rule.isActive()); + + ORCombinedRule orCombinedRule = assertOrCombinedRule(rule, 3); + ANDCombinedRule andCombinedRule = assertAndCombinedRule(orCombinedRule.getRules().get(0), 2); + assertExpressions(andCombinedRule, expression1, expression2); + + andCombinedRule = assertAndCombinedRule(orCombinedRule.getRules().get(1), 2); + assertExpressions(andCombinedRule, expression3, expression4); + + andCombinedRule = assertAndCombinedRule(orCombinedRule.getRules().get(2), 1); + assertExpressions(andCombinedRule, expression5); + } + + private List getMockedFieldDefinitions() { + + List fieldDefinitionList = new ArrayList<>(); + + Field applicationField = new Field("application", "application"); + List operators = Arrays.asList(new Operator("equals", "equals"), + new Operator("notEquals", "not equals")); + + List links = Arrays.asList(new Link("/applications?offset=0&limit=10", "GET", "values"), + new Link("/applications?filter=name+eq+*&limit=10", "GET", "filter")); + org.wso2.carbon.identity.rule.metadata.model.Value + applicationValue = new OptionsReferenceValue.Builder().valueReferenceAttribute("id") + .valueDisplayAttribute("name").valueType( + org.wso2.carbon.identity.rule.metadata.model.Value.ValueType.REFERENCE).links(links).build(); + fieldDefinitionList.add(new FieldDefinition(applicationField, operators, applicationValue)); + + Field grantTypeField = new Field("grantType", "grantType"); + List optionsValues = Arrays.asList(new OptionsValue("authorization_code", "authorization code"), + new OptionsValue("password", "password"), new OptionsValue("refresh_token", "refresh token"), + new OptionsValue("client_credentials", "client credentials"), + new OptionsValue("urn:ietf:params:oauth:grant-type:token-exchange", "token exchange")); + org.wso2.carbon.identity.rule.metadata.model.Value + grantTypeValue = + new OptionsInputValue(org.wso2.carbon.identity.rule.metadata.model.Value.ValueType.STRING, + optionsValues); + fieldDefinitionList.add(new FieldDefinition(grantTypeField, operators, grantTypeValue)); + + return fieldDefinitionList; + } + + private static void assertExpressions(ANDCombinedRule andCombinedRule, Expression... expressions) { + + assertEquals(andCombinedRule.getExpressions().size(), expressions.length); + for (int i = 0; i < expressions.length; i++) { + assertEquals(andCombinedRule.getExpressions().get(i), expressions[i]); + } + } + + private static ANDCombinedRule assertAndCombinedRule(Rule andRule, int expectedExpressionsSize) { + + assertTrue(andRule instanceof ANDCombinedRule); + + ANDCombinedRule andCombinedRule = (ANDCombinedRule) andRule; + assertNotNull(andCombinedRule.getId()); + assertTrue(andCombinedRule.isActive()); + assertNotNull(andCombinedRule.getExpressions()); + assertEquals(andCombinedRule.getExpressions().size(), expectedExpressionsSize); + return andCombinedRule; + } + + private static ORCombinedRule assertOrCombinedRule(Rule rule, int expectedRulesSize) { + + assertTrue(rule instanceof ORCombinedRule); + ORCombinedRule orCombinedRule = (ORCombinedRule) rule; + assertNotNull(orCombinedRule.getRules()); + assertEquals(orCombinedRule.getRules().size(), expectedRulesSize); + return orCombinedRule; + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/configs/valid-operators.json b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/configs/valid-operators.json new file mode 100644 index 000000000000..440832b8f460 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/configs/valid-operators.json @@ -0,0 +1,14 @@ +{ + "equals": { + "name": "equals", + "displayName": "equals" + }, + "notEquals": { + "name": "notEquals", + "displayName": "not equals" + }, + "contains": { + "name": "contains", + "displayName": "contains" + } +} diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/dbscripts/h2.sql b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/dbscripts/h2.sql new file mode 100644 index 000000000000..6e6236d38fa6 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/dbscripts/h2.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS IDN_RULE ( + ID INTEGER NOT NULL AUTO_INCREMENT, + UUID CHAR(36) NOT NULL, + CONTENT BLOB NOT NULL, + IS_ACTIVE BOOLEAN DEFAULT TRUE, + TENANT_ID INTEGER NOT NULL, + VERSION VARCHAR(15) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (UUID) +); + +CREATE TABLE IF NOT EXISTS IDN_RULE_REFERENCES ( + ID INTEGER NOT NULL AUTO_INCREMENT, + RULE_ID INTEGER NOT NULL, + FIELD_NAME VARCHAR(100) NOT NULL, + FIELD_REFERENCE VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + FOREIGN KEY (RULE_ID) REFERENCES IDN_RULE(ID) ON DELETE CASCADE +); diff --git a/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/repository/conf/carbon.xml b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/repository/conf/carbon.xml new file mode 100644 index 000000000000..a5a1a6470cbc --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.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/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/repository/conf/identity/identity.xml b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/repository/conf/identity/identity.xml new file mode 100644 index 000000000000..07de6831dbf4 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.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/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/testng.xml b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/testng.xml new file mode 100644 index 000000000000..90613c3d2d42 --- /dev/null +++ b/components/rule-mgt/org.wso2.carbon.identity.rule.management/src/test/resources/testng.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + 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 e75cc05b5f8b..b029ed8dd200 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 @@ -2119,6 +2119,43 @@ REFERENCING NEW AS NEW FOR EACH ROW MODE DB2SQL = (NEXTVAL FOR IDN_OAUTH2_TOKEN_CLAIMS_SEQ); END / +CREATE TABLE IDN_RULE ( + ID INTEGER NOT NULL, + UUID CHAR(36) NOT NULL, + CONTENT BLOB NOT NULL, + IS_ACTIVE BOOLEAN DEFAULT TRUE, + TENANT_ID INTEGER NOT NULL, + VERSION VARCHAR(15) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (UUID) +) +/ +CREATE SEQUENCE IDN_RULE_SEQ START WITH 1 INCREMENT BY 1 NOCACHE +/ +CREATE TRIGGER IDN_RULE_TRIG NO CASCADE BEFORE INSERT ON IDN_RULE_REFERENCES +REFERENCING NEW AS NEW FOR EACH ROW MODE DB2SQL + BEGIN ATOMIC + SET (NEW.ID) = (NEXTVAL FOR IDN_RULE_SEQ); + END +/ +CREATE TABLE IDN_RULE_REFERENCES ( + ID INTEGER NOT NULL, + RULE_ID INTEGER NOT NULL, + FIELD_NAME VARCHAR(100) NOT NULL, + FIELD_REFERENCE VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + FOREIGN KEY (RULE_ID) REFERENCES IDN_RULE(ID) ON DELETE CASCADE +) +/ +CREATE SEQUENCE IDN_RULE_REFERENCES_SEQ START WITH 1 INCREMENT BY 1 NOCACHE +/ +CREATE TRIGGER IDN_RULE_REFERENCES_TRIG NO CASCADE BEFORE INSERT ON IDN_RULE_REFERENCES +REFERENCING NEW AS NEW FOR EACH ROW MODE DB2SQL + BEGIN ATOMIC + SET (NEW.ID) = (NEXTVAL FOR IDN_RULE_REFERENCES_SEQ); + END +/ -- --------------------------- INDEX CREATION ----------------------------- -- IDN_OAUTH2_ACCESS_TOKEN -- @@ -2284,3 +2321,9 @@ CREATE INDEX IDX_IDN_CERTIFICATE_ID_TID ON IDN_CERTIFICATE (ID, TENANT_ID) / CREATE INDEX IDX_IDN_CERTIFICATE_UUID_TID ON IDN_CERTIFICATE (UUID, TENANT_ID) / + +-- RULES -- +CREATE INDEX IDX_IDN_RULE_UUID_TID ON IDN_RULE (UUID, TENANT_ID) +/ +CREATE INDEX IDX_IDN_RULE_REF_RID_TID ON IDN_RULE_REFERENCES (RULE_ID, TENANT_ID) +/ 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 437ba45ddbef..56f35ea09d30 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 @@ -1389,6 +1389,27 @@ CREATE TABLE IF NOT EXISTS IDN_OAUTH2_TOKEN_CLAIMS ( FOREIGN KEY (APP_ID) REFERENCES IDN_OAUTH_CONSUMER_APPS(ID) ON DELETE CASCADE ); +CREATE TABLE IF NOT EXISTS IDN_RULE ( + ID INTEGER NOT NULL AUTO_INCREMENT, + UUID CHAR(36) NOT NULL, + CONTENT BLOB NOT NULL, + IS_ACTIVE BOOLEAN DEFAULT TRUE, + TENANT_ID INTEGER NOT NULL, + VERSION VARCHAR(15) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (UUID) +); + +CREATE TABLE IF NOT EXISTS IDN_RULE_REFERENCES ( + ID INTEGER NOT NULL AUTO_INCREMENT, + RULE_ID INTEGER NOT NULL, + FIELD_NAME VARCHAR(100) NOT NULL, + FIELD_REFERENCE VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + FOREIGN KEY (RULE_ID) REFERENCES IDN_RULE(ID) ON DELETE CASCADE +); + -- --------------------------- INDEX CREATION ----------------------------- -- IDN_OAUTH2_ACCESS_TOKEN -- CREATE INDEX IDX_TC ON IDN_OAUTH2_ACCESS_TOKEN(TIME_CREATED); @@ -1501,3 +1522,7 @@ CREATE INDEX IDX_IDN_ACTION_PROPERTIES_AU_TI ON IDN_ACTION_PROPERTIES (ACTION_UU -- CERTIFICATE -- CREATE INDEX IDX_IDN_CERTIFICATE_ID_TID ON IDN_CERTIFICATE (ID, TENANT_ID); CREATE INDEX IDX_IDN_CERTIFICATE_UUID_TID ON IDN_CERTIFICATE (UUID, TENANT_ID); + +-- RULES -- +CREATE INDEX IDX_IDN_RULE_UUID_TID ON IDN_RULE (UUID, TENANT_ID); +CREATE INDEX IDX_IDN_RULE_REF_RID_TID ON IDN_RULE_REFERENCES (RULE_ID, TENANT_ID); 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 04f2181996a8..199d5d9d2404 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 @@ -1539,6 +1539,29 @@ CREATE TABLE IDN_OAUTH2_TOKEN_CLAIMS ( FOREIGN KEY (APP_ID) REFERENCES IDN_OAUTH_CONSUMER_APPS(ID) ON DELETE CASCADE ); +IF NOT EXISTS (SELECT * FROM SYS.OBJECTS WHERE OBJECT_ID = OBJECT_ID(N'[DBO].[IDN_RULE]') AND TYPE IN (N'U')) +CREATE TABLE IDN_RULE ( + ID INTEGER IDENTITY, + UUID CHAR(36) NOT NULL, + CONTENT VARBINARY(MAX) NOT NULL, + IS_ACTIVE BIT DEFAULT 1, + TENANT_ID INTEGER NOT NULL, + VERSION VARCHAR(15) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (UUID) +); + +IF NOT EXISTS (SELECT * FROM SYS.OBJECTS WHERE OBJECT_ID = OBJECT_ID(N'[DBO].[IDN_RULE_REFERENCES]') AND TYPE IN (N'U')) +CREATE TABLE IDN_RULE_REFERENCES ( + ID INTEGER IDENTITY, + RULE_ID INTEGER NOT NULL, + FIELD_NAME VARCHAR(100) NOT NULL, + FIELD_REFERENCE VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + FOREIGN KEY (RULE_ID) REFERENCES IDN_RULE(ID) ON DELETE CASCADE +); + -- --------------------------- INDEX CREATION ----------------------------- -- IDN_OAUTH2_ACCESS_TOKEN -- CREATE INDEX IDX_TC ON IDN_OAUTH2_ACCESS_TOKEN(TIME_CREATED); @@ -1653,6 +1676,10 @@ CREATE INDEX IDX_IDN_ACTION_PROPERTIES_AU_TI ON IDN_ACTION_PROPERTIES (ACTION_UU CREATE INDEX IDX_IDN_CERTIFICATE_ID_TID ON IDN_CERTIFICATE (ID, TENANT_ID); CREATE INDEX IDX_IDN_CERTIFICATE_UUID_TID ON IDN_CERTIFICATE (UUID, TENANT_ID); +-- RULES -- +CREATE INDEX IDX_IDN_RULE_UUID_TID ON IDN_RULE (UUID, TENANT_ID); +CREATE INDEX IDX_IDN_RULE_REF_RID_TID ON IDN_RULE_REFERENCES (RULE_ID, TENANT_ID); + GO -- Trigger IDN_CLAIM delete by dialect on IDN_CLAIM_DIALECT deletion -- 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 a08513b13e90..e53f3c0f542f 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 @@ -1552,6 +1552,27 @@ CREATE TABLE IF NOT EXISTS IDN_OAUTH2_TOKEN_CLAIMS ( FOREIGN KEY (APP_ID) REFERENCES IDN_OAUTH_CONSUMER_APPS(ID) ON DELETE CASCADE )ENGINE NDB; +CREATE TABLE IF NOT EXISTS IDN_RULE ( + ID INTEGER AUTO_INCREMENT, + UUID CHAR(36) NOT NULL, + CONTENT BLOB NOT NULL, + IS_ACTIVE BOOLEAN DEFAULT TRUE, + TENANT_ID INTEGER NOT NULL, + VERSION VARCHAR(15) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (UUID) +)ENGINE NDB; + +CREATE TABLE IF NOT EXISTS IDN_RULE_REFERENCES ( + ID INTEGER AUTO_INCREMENT, + RULE_ID INTEGER NOT NULL, + FIELD_NAME VARCHAR(100) NOT NULL, + FIELD_REFERENCE VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + FOREIGN KEY (RULE_ID) REFERENCES IDN_RULE(ID) ON DELETE CASCADE +)ENGINE NDB; + -- --------------------------- INDEX CREATION ----------------------------- -- IDN_OAUTH2_ACCESS_TOKEN -- CREATE INDEX IDX_TC @@ -1693,3 +1714,7 @@ CREATE INDEX IDX_IDN_ACTION_PROPERTIES_AU_TI ON IDN_ACTION_PROPERTIES (ACTION_UU -- CERTIFICATE -- CREATE INDEX IDX_IDN_CERTIFICATE_ID_TID ON IDN_CERTIFICATE (ID, TENANT_ID); CREATE INDEX IDX_IDN_CERTIFICATE_UUID_TID ON IDN_CERTIFICATE (UUID, TENANT_ID); + +-- RULES -- +CREATE INDEX IDX_IDN_RULE_UUID_TID ON IDN_RULE (UUID, TENANT_ID); +CREATE INDEX IDX_IDN_RULE_REF_RID_TID ON IDN_RULE_REFERENCES (RULE_ID, TENANT_ID); 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 34ec2b9e5918..6a1eb37a6492 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 @@ -1420,6 +1420,27 @@ CREATE TABLE IF NOT EXISTS IDN_OAUTH2_TOKEN_CLAIMS ( FOREIGN KEY (APP_ID) REFERENCES IDN_OAUTH_CONSUMER_APPS(ID) ON DELETE CASCADE )DEFAULT CHARACTER SET latin1 ENGINE INNODB; +CREATE TABLE IF NOT EXISTS IDN_RULE ( + ID INTEGER AUTO_INCREMENT, + UUID CHAR(36) NOT NULL, + CONTENT BLOB NOT NULL, + IS_ACTIVE BOOLEAN DEFAULT TRUE, + TENANT_ID INTEGER NOT NULL, + VERSION VARCHAR(15) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (UUID) +)DEFAULT CHARACTER SET latin1 ENGINE INNODB; + +CREATE TABLE IF NOT EXISTS IDN_RULE_REFERENCES ( + ID INTEGER AUTO_INCREMENT, + RULE_ID INTEGER NOT NULL, + FIELD_NAME VARCHAR(100) NOT NULL, + FIELD_REFERENCE VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + FOREIGN KEY (RULE_ID) REFERENCES IDN_RULE(ID) ON DELETE CASCADE +)DEFAULT CHARACTER SET latin1 ENGINE INNODB; + -- --------------------------- INDEX CREATION ----------------------------- -- IDN_OAUTH2_ACCESS_TOKEN -- CREATE INDEX IDX_TC ON IDN_OAUTH2_ACCESS_TOKEN(TIME_CREATED); @@ -1529,3 +1550,7 @@ CREATE INDEX IDX_IDN_ACTION_PROPERTIES_AU_TI ON IDN_ACTION_PROPERTIES (ACTION_UU -- CERTIFICATE -- CREATE INDEX IDX_IDN_CERTIFICATE_ID_TID ON IDN_CERTIFICATE (ID, TENANT_ID); CREATE INDEX IDX_IDN_CERTIFICATE_UUID_TID ON IDN_CERTIFICATE (UUID, TENANT_ID); + +-- RULES -- +CREATE INDEX IDX_IDN_RULE_UUID_TID ON IDN_RULE (UUID, TENANT_ID); +CREATE INDEX IDX_IDN_RULE_REF_RID_TID ON IDN_RULE_REFERENCES (RULE_ID, TENANT_ID); 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 5c94891b5a4d..9e127808dceb 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 @@ -2184,6 +2184,45 @@ CREATE OR REPLACE TRIGGER IDN_OAUTH2_TOKEN_CLAIMS_TRIG SELECT IDN_OAUTH2_TOKEN_CLAIMS_SEQ.nextval INTO :NEW.ID FROM dual; END; / +CREATE TABLE IDN_RULE ( + ID INTEGER, + UUID CHAR(36) NOT NULL, + CONTENT BLOB NOT NULL, + IS_ACTIVE CHAR (1) DEFAULT '1', + TENANT_ID INTEGER NOT NULL, + VERSION VARCHAR(15) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (UUID) +) +/ +CREATE SEQUENCE IDN_RULE_SEQ START WITH 1 INCREMENT BY 1 NOCACHE +/ +CREATE OR REPLACE TRIGGER IDN_RULE_TRIGGER + BEFORE INSERT ON IDN_RULE + REFERENCING NEW AS NEW FOR EACH ROW + BEGIN + SELECT IDN_RULE_SEQ.nextval INTO :NEW.ID FROM dual; + END; +/ +CREATE TABLE IDN_RULE_REFERENCES ( + ID INTEGER, + RULE_ID INTEGER NOT NULL, + FIELD_NAME VARCHAR(100) NOT NULL, + FIELD_REFERENCE VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + FOREIGN KEY (RULE_ID) REFERENCES IDN_RULE(ID) ON DELETE CASCADE +) +/ +CREATE SEQUENCE IDN_RULE_REFERENCES_SEQ START WITH 1 INCREMENT BY 1 NOCACHE +/ +CREATE OR REPLACE TRIGGER IDN_RULE_REFERENCES_TRIGGER + BEFORE INSERT ON IDN_RULE_REFERENCES + REFERENCING NEW AS NEW FOR EACH ROW + BEGIN + SELECT IDN_RULE_REFERENCES_SEQ.nextval INTO :NEW.ID FROM dual; + END; +/ -- --------------------------- INDEX CREATION ----------------------------- -- IDN_OAUTH2_ACCESS_TOKEN -- @@ -2341,3 +2380,9 @@ CREATE INDEX IDX_IDN_CERTIFICATE_ID_TID ON IDN_CERTIFICATE (ID, TENANT_ID) / CREATE INDEX IDX_IDN_CERTIFICATE_UUID_TID ON IDN_CERTIFICATE (UUID, TENANT_ID) / + +-- RULES -- +CREATE INDEX IDX_IDN_RULE_UUID_TID ON IDN_RULE (UUID, TENANT_ID) +/ +CREATE INDEX IDX_IDN_RULE_REF_RID_TID ON IDN_RULE_REFERENCES (RULE_ID, TENANT_ID) +/ 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 d8b1eb15c5a8..9b90807829d4 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 @@ -2117,7 +2117,45 @@ CREATE OR REPLACE TRIGGER IDN_OAUTH2_TOKEN_CLAIMS_TRIG SELECT IDN_OAUTH2_TOKEN_CLAIMS_SEQ.nextval INTO :NEW.ID FROM dual; END; / - +CREATE TABLE IDN_RULE ( + ID INTEGER, + UUID CHAR(36) NOT NULL, + CONTENT BLOB NOT NULL, + IS_ACTIVE CHAR (1) DEFAULT '1', + TENANT_ID INTEGER NOT NULL, + VERSION VARCHAR(15) NOT NULL, + PRIMARY KEY (ID), + UNIQUE (UUID) +) +/ +CREATE SEQUENCE IDN_RULE_SEQ START WITH 1 INCREMENT BY 1 NOCACHE +/ +CREATE OR REPLACE TRIGGER IDN_RULE_TRIGGER + BEFORE INSERT ON IDN_RULE + REFERENCING NEW AS NEW FOR EACH ROW + BEGIN + SELECT IDN_RULE_SEQ.nextval INTO :NEW.ID FROM dual; + END; +/ +CREATE TABLE IDN_RULE_REFERENCES ( + ID INTEGER, + RULE_ID INTEGER NOT NULL, + FIELD_NAME VARCHAR(100) NOT NULL, + FIELD_REFERENCE VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + FOREIGN KEY (RULE_ID) REFERENCES IDN_RULE(ID) ON DELETE CASCADE +) +/ +CREATE SEQUENCE IDN_RULE_REFERENCES_SEQ START WITH 1 INCREMENT BY 1 NOCACHE +/ +CREATE OR REPLACE TRIGGER IDN_RULE_REFERENCES_TRIGGER + BEFORE INSERT ON IDN_RULE_REFERENCES + REFERENCING NEW AS NEW FOR EACH ROW + BEGIN + SELECT IDN_RULE_REFERENCES_SEQ.nextval INTO :NEW.ID FROM dual; + END; +/ -- --------------------------- INDEX CREATION ----------------------------- -- IDN_OAUTH2_ACCESS_TOKEN -- CREATE INDEX IDX_TC ON IDN_OAUTH2_ACCESS_TOKEN(TIME_CREATED) @@ -2246,3 +2284,9 @@ CREATE INDEX IDX_IDN_CERTIFICATE_ID_TID ON IDN_CERTIFICATE (ID, TENANT_ID) / CREATE INDEX IDX_IDN_CERTIFICATE_UUID_TID ON IDN_CERTIFICATE (UUID, TENANT_ID) / + +-- RULES -- +CREATE INDEX IDX_IDN_RULE_UUID_TID ON IDN_RULE (UUID, TENANT_ID) +/ +CREATE INDEX IDX_IDN_RULE_REF_RID_TID ON IDN_RULE_REFERENCES (RULE_ID, TENANT_ID) +/ 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 30fc69db8e60..185115350c3b 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 @@ -1661,6 +1661,33 @@ CREATE TABLE IDN_OAUTH2_TOKEN_CLAIMS ( FOREIGN KEY (APP_ID) REFERENCES IDN_OAUTH_CONSUMER_APPS(ID) ON DELETE CASCADE ); +DROP TABLE IF EXISTS IDN_RULE; +DROP SEQUENCE IF EXISTS IDN_RULE_SEQ; +CREATE SEQUENCE IDN_RULE_SEQ; +CREATE TABLE IDN_RULE ( + ID INTEGER NOT NULL DEFAULT NEXTVAL('IDN_RULE_SEQ'), + UUID CHAR(36) NOT NULL, + CONTENT BYTEA NOT NULL, + IS_ACTIVE BOOLEAN DEFAULT TRUE, + TENANT_ID INTEGER NOT NULL, + VERSION VARCHAR(15) NOT NULL, + PRIMARY KEY (ID) + UNIQUE (UUID) +); + +DROP TABLE IF EXISTS IDN_RULE_REFERENCES; +DROP SEQUENCE IF EXISTS IDN_RULE_REFERENCES_SEQ; +CREATE SEQUENCE IDN_RULE_REFERENCES_SEQ; +CREATE TABLE IDN_RULE_REFERENCES ( + ID INTEGER NOT NULL DEFAULT NEXTVAL('IDN_RULE_REFERENCES_SEQ'), + RULE_ID INTEGER NOT NULL, + FIELD_NAME VARCHAR(100) NOT NULL, + FIELD_REFERENCE VARCHAR(255) NOT NULL, + TENANT_ID INTEGER NOT NULL, + PRIMARY KEY (ID), + FOREIGN KEY (RULE_ID) REFERENCES IDN_RULE(ID) ON DELETE CASCADE +); + -- --------------------------- INDEX CREATION ----------------------------- -- IDN_OAUTH2_ACCESS_TOKEN -- CREATE INDEX IDX_TC ON IDN_OAUTH2_ACCESS_TOKEN(TIME_CREATED); @@ -1776,3 +1803,7 @@ CREATE INDEX IDX_IDN_ACTION_PROPERTIES_AU_TI ON IDN_ACTION_PROPERTIES (ACTION_UU -- CERTIFICATE -- CREATE INDEX IDX_IDN_CERTIFICATE_ID_TID ON IDN_CERTIFICATE (ID, TENANT_ID); CREATE INDEX IDX_IDN_CERTIFICATE_UUID_TID ON IDN_CERTIFICATE (UUID, TENANT_ID); + +-- RULES -- +CREATE INDEX IDX_IDN_RULE_UUID_TID ON IDN_RULE (UUID, TENANT_ID); +CREATE INDEX IDX_IDN_RULE_REF_RID_TID ON IDN_RULE_REFERENCES (RULE_ID, TENANT_ID); diff --git a/pom.xml b/pom.xml index a50c576aeced..ac9ebe7063e1 100644 --- a/pom.xml +++ b/pom.xml @@ -335,6 +335,11 @@ org.wso2.carbon.identity.rule.metadata ${project.version} + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.rule.management + ${project.version} + org.wso2.carbon.identity.framework @@ -1843,7 +1848,7 @@ 4.7.39 [4.7.2, 5.0.0) - 2.1.7 + 2.2.2 [2.0.0,3.0.0)