diff --git a/addons/hbase-bridge/pom.xml b/addons/hbase-bridge/pom.xml index 5cb14f650f9..fe6da29d61b 100644 --- a/addons/hbase-bridge/pom.xml +++ b/addons/hbase-bridge/pom.xml @@ -111,6 +111,10 @@ hadoop-hdfs ${hadoop.version} + + com.google.code.gson + gson + javax.servlet servlet-api diff --git a/addons/hbase-testing-util/pom.xml b/addons/hbase-testing-util/pom.xml index 2fb66c557d1..2face398bc0 100644 --- a/addons/hbase-testing-util/pom.xml +++ b/addons/hbase-testing-util/pom.xml @@ -100,6 +100,10 @@ ${hadoop.version} compile + + com.google.code.gson + gson + org.apache.commons commons-configuration2 diff --git a/addons/hive-bridge-shim/pom.xml b/addons/hive-bridge-shim/pom.xml index 08a9ee7c42f..f3712d4f253 100755 --- a/addons/hive-bridge-shim/pom.xml +++ b/addons/hive-bridge-shim/pom.xml @@ -44,6 +44,10 @@ ${hive.version} provided + + com.google.code.gson + gson + org.apache.commons commons-text diff --git a/addons/hive-bridge/pom.xml b/addons/hive-bridge/pom.xml index 38c54ea0248..bc93e7ea964 100755 --- a/addons/hive-bridge/pom.xml +++ b/addons/hive-bridge/pom.xml @@ -113,6 +113,10 @@ ${hive.version} provided + + com.google.code.gson + gson + javax.servlet * diff --git a/addons/kafka-bridge/pom.xml b/addons/kafka-bridge/pom.xml index a1dd3a66605..42b6f57292f 100644 --- a/addons/kafka-bridge/pom.xml +++ b/addons/kafka-bridge/pom.xml @@ -70,6 +70,10 @@ ${hadoop.version} compile + + com.google.code.gson + gson + javax.servlet servlet-api diff --git a/addons/models/0000-Area0/0010-base_model.json b/addons/models/0000-Area0/0010-base_model.json index 0ee7767644a..fab67611dc5 100644 --- a/addons/models/0000-Area0/0010-base_model.json +++ b/addons/models/0000-Area0/0010-base_model.json @@ -586,6 +586,64 @@ { "name": "processingStartTime", "typeName": "string", "isOptional": false, "cardinality": "SINGLE", "isUnique": false, "isIndexable": false }, { "name": "completedTime", "typeName": "string", "isOptional": false, "cardinality": "SINGLE", "isUnique": false, "isIndexable": false } ] + }, + { + "name": "__AtlasRule", + "superTypes": [ + "__internal" + ], + "serviceType": "atlas_core", + "typeVersion": "1.0", + "attributeDefs": [ + { + "name": "ruleName", + "typeName": "string", + "cardinality": "SINGLE", + "isIndexable": true, + "isOptional": false, + "isUnique": true + }, + { + "name": "desc", + "typeName": "string", + "cardinality": "SINGLE", + "isIndexable": false, + "isOptional": true, + "isUnique": false + }, + { + "name": "action", + "typeName": "string", + "cardinality": "SINGLE", + "isIndexable": true, + "isOptional": false, + "isUnique": false + }, + { + "name": "ruleExpr", + "typeName": "string", + "cardinality": "SINGLE", + "isIndexable": false, + "isOptional": false, + "isUnique": true + }, + { + "name": "createdTime", + "typeName": "date", + "cardinality": "SINGLE", + "isIndexable": false, + "isOptional": false, + "isUnique": false + }, + { + "name": "updatedTime", + "typeName": "date", + "cardinality": "SINGLE", + "isIndexable": false, + "isOptional": true, + "isUnique": false + } + ] } ], "relationshipDefs": [ diff --git a/addons/sqoop-bridge/pom.xml b/addons/sqoop-bridge/pom.xml index ea1bc94bab3..8ec30f83beb 100644 --- a/addons/sqoop-bridge/pom.xml +++ b/addons/sqoop-bridge/pom.xml @@ -110,6 +110,12 @@ hive-exec ${hive.version} provided + + + com.google.code.gson + gson + + diff --git a/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java b/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java index a485ea329f4..740b954a914 100644 --- a/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java +++ b/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java @@ -58,6 +58,7 @@ import org.apache.atlas.model.instance.AtlasRelatedObjectId; import org.apache.atlas.model.instance.AtlasRelationship; import org.apache.atlas.model.instance.AtlasRelationship.AtlasRelationshipWithExtInfo; +import org.apache.atlas.model.instance.AtlasRule; import org.apache.atlas.model.instance.ClassificationAssociateRequest; import org.apache.atlas.model.instance.EntityMutationResponse; import org.apache.atlas.model.lineage.AtlasLineageInfo; @@ -115,6 +116,7 @@ public class AtlasClientV2 extends AtlasBaseClient { private static final String ADMIN_API = BASE_URI + "admin/"; private static final String ENTITY_PURGE_API = ADMIN_API + "purge/"; private static final String ATLAS_AUDIT_API = ADMIN_API + "audits/"; + private static final String ATLAS_RULES_API = ATLAS_AUDIT_API + "rules/"; // Lineage APIs private static final String LINEAGE_URI = BASE_URI + "v2/lineage/"; @@ -574,6 +576,22 @@ public String getTemplateForBulkUpdateBusinessAttributes() throws AtlasServiceEx return readStreamContents(inputStream); } + public AtlasRule createRule(AtlasRule atlasRule) throws AtlasServiceException { + return callAPI(API_V2.CREATE_RULE, AtlasRule.class, atlasRule); + } + + public List getAllRules() throws AtlasServiceException { + return callAPI(API_V2.GET_RULES, List.class, null); + } + + public EntityMutationResponse deleteRuleByGuid(String guid) throws AtlasServiceException { + return callAPI(formatPathParameters(API_V2.DELETE_RULE_BY_GUID, guid), EntityMutationResponse.class, null, guid); + } + + public EntityMutationResponse deleteRulesByGuid(List guidList) throws AtlasServiceException { + return callAPI(API_V2.DELETE_RULES_BY_GUID, EntityMutationResponse.class, null, guidList); + } + public BulkImportResponse bulkUpdateBusinessAttributes(String fileName) throws AtlasServiceException { MultiPart multipartEntity = getMultiPartData(fileName); @@ -1347,6 +1365,10 @@ public static class API_V2 extends API { public static final API_V2 DISASSOCIATE_TERM_FROM_ENTITIES = new API_V2(GLOSSARY_TERMS + "/%s/assignedEntities", HttpMethod.PUT, Response.Status.NO_CONTENT); public static final API_V2 GET_IMPORT_GLOSSARY_TEMPLATE = new API_V2(GLOSSARY_URI + "/import/template", HttpMethod.GET, Response.Status.OK, MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM); public static final API_V2 IMPORT_GLOSSARY = new API_V2(GLOSSARY_URI + "/import", HttpMethod.POST, Response.Status.OK, MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_JSON); + public static final API_V2 CREATE_RULE = new API_V2(ATLAS_RULES_API, HttpMethod.POST, Response.Status.OK); + public static final API_V2 GET_RULES = new API_V2(ATLAS_RULES_API, HttpMethod.GET, Response.Status.OK); + public static final API_V2 DELETE_RULE_BY_GUID = new API_V2(ATLAS_RULES_API + "guid/", HttpMethod.DELETE, Response.Status.OK); + public static final API_V2 DELETE_RULES_BY_GUID = new API_V2(ATLAS_RULES_API, HttpMethod.DELETE, Response.Status.OK); private API_V2(String path, String method, Response.Status status) { super(path, method, status); diff --git a/distro/src/conf/atlas-application.properties b/distro/src/conf/atlas-application.properties index b5734d7a8de..37009fd91ae 100755 --- a/distro/src/conf/atlas-application.properties +++ b/distro/src/conf/atlas-application.properties @@ -281,4 +281,7 @@ atlas.search.gremlin.enable=false ######### Skip check for the same attribute name in Parent type and Child type ######### -#atlas.skip.check.for.parent.child.attribute.name=true \ No newline at end of file +#atlas.skip.check.for.parent.child.attribute.name=true + +atlas.entity.audit.filter.enabled=false +atlas.entity.audit.filter.default.action=ACCEPT diff --git a/graphdb/api/pom.xml b/graphdb/api/pom.xml index 47875803346..9c6f004b2a9 100644 --- a/graphdb/api/pom.xml +++ b/graphdb/api/pom.xml @@ -37,6 +37,11 @@ + + com.google.code.gson + gson + ${gson.version} + org.apache.atlas atlas-common diff --git a/intg/pom.xml b/intg/pom.xml index 4c22bd61fce..2119444f1c0 100644 --- a/intg/pom.xml +++ b/intg/pom.xml @@ -88,6 +88,10 @@ org.apache.hadoop hadoop-common + + com.google.code.gson + gson + io.netty netty-handler diff --git a/intg/src/main/java/org/apache/atlas/AtlasConfiguration.java b/intg/src/main/java/org/apache/atlas/AtlasConfiguration.java index 6a5ace48f3f..c7148c5dbc3 100644 --- a/intg/src/main/java/org/apache/atlas/AtlasConfiguration.java +++ b/intg/src/main/java/org/apache/atlas/AtlasConfiguration.java @@ -115,7 +115,9 @@ public enum AtlasConfiguration { ATLAS_ASYNC_IMPORT_MIN_DURATION_OVERRIDE_TEST_AUTOMATION("atlas.async.import.min.duration.override.test.automation", false), ASYNC_IMPORT_TOPIC_PREFIX("atlas.async.import.topic.prefix", "ATLAS_IMPORT_"), ASYNC_IMPORT_REQUEST_ID_PREFIX("atlas.async.import.request_id.prefix", "async_import_"), - REPLACE_HUGE_SPARK_PROCESS_ATTRIBUTES_PATCH("atlas.process.spark.attributes.update.patch", false); + REPLACE_HUGE_SPARK_PROCESS_ATTRIBUTES_PATCH("atlas.process.spark.attributes.update.patch", false), + ENTITY_AUDIT_FILTER_ENABLED("atlas.entity.audit.filter.enabled", false), + DEFAULT_ENTITY_AUDIT_FILTER_ACTION("atlas.entity.audit.filter.default.action", "ACCEPT"); private static final Configuration APPLICATION_PROPERTIES; private final String propertyName; diff --git a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java index 9256d0f8865..d0a82ffce30 100644 --- a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java +++ b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java @@ -182,7 +182,16 @@ public enum AtlasErrorCode { INVALID_OPERATOR(400, "ATLAS-400-00-103", "Invalid operator specified for attribute: {0}"), BLANK_NAME_ATTRIBUTE(400, "ATLAS-400-00-104", "Name Attribute can't be empty!"), BLANK_VALUE_ATTRIBUTE(400, "ATLAS-400-00-105", "Value Attribute can't be empty!"), - + CUSTOM_AUDIT_FILTERS_NOT_ENABLED(400, "ATLAS-400-00-106", "Custom Audit Filters config: {0} is not enabled "), + INVALID_RULE_ACTION(400, "ATLAS-400-00-107", "Invalid action. Allowed values are ACCEPT or DISCARD"), + MISSING_ATTRIBUTE_NAME_IN_RULE_EXPR(400, "ATLAS-400-00-108", "attributeName is missing/null in specified criteria"), + MISSING_ATTRIBUTE_VALUE_IN_RULE_EXPR(400, "ATLAS-400-00-109", "attributeValue is missing/null for attributeName {0}"), + MISSING_CRITERIA_CONDITION(400, "ATLAS-400-00-110", "{0} is missing; specify condition along with criteria for multiple conditions"), + MISSING_MANDATORY_TYPENAME_IN_RULE_EXPR(400, "ATLAS-400-00-111", "typeName is required for every rule expression"), + MISSING_MANDATORY_OPERATOR_IN_RULE_EXPR_CRITERIA(400, "ATLAS-400-00-112", "operator is missing in specified criteria"), + INVALID_OPERATOR_ON_ATTRIBUTE(400, "ATLAS-400-00-113", "Operator {0} can not be applied on attribute {1}"), + DUPLICATE_TYPENAME_IN_RULE_EXPR(400, "ATLAS-400-00-114", "Duplicate value {0} found for typeName in same rule expression."), + DUPLICATE_CONDITION_IN_SAME_RULE_EXPR(400, "ATLAS-400-00-115", "Duplicate condition mentioned in same rule expression object."), UNAUTHORIZED_ACCESS(403, "ATLAS-403-00-001", "{0} is not authorized to perform {1}"), // All Not found enums go here @@ -228,6 +237,8 @@ public enum AtlasErrorCode { METRICSSTAT_ALREADY_EXISTS(409, "ATLAS-409-00-012", "Metric Statistics already collected at {0}"), PENDING_TASKS_ALREADY_IN_PROGRESS(409, "ATLAS-409-00-013", "There are already {0} pending tasks in queue"), IMPORT_ABORT_NOT_ALLOWED(409, "ATLAS-409-00-016", "Import id {0} is currently in state {1}, cannot be aborted"), + RULE_NAME_ALREADY_EXISTS(410, "ATLAS-409-00-014", "Rule with given ruleName {0} already exists"), + RULE_EXPRESSION_ALREADY_EXISTS(411, "ATLAS-409-00-015", "Rule expression already exists for rule {0}"), // All internal errors go here INTERNAL_ERROR(500, "ATLAS-500-00-001", "Internal server error {0}"), diff --git a/intg/src/main/java/org/apache/atlas/model/audit/EntityAuditEventV2.java b/intg/src/main/java/org/apache/atlas/model/audit/EntityAuditEventV2.java index 76cf2e9df39..2d4f075914e 100644 --- a/intg/src/main/java/org/apache/atlas/model/audit/EntityAuditEventV2.java +++ b/intg/src/main/java/org/apache/atlas/model/audit/EntityAuditEventV2.java @@ -61,6 +61,7 @@ public class EntityAuditEventV2 implements Serializable, Clearable { private String eventKey; private AtlasEntity entity; private EntityAuditType type; + private boolean isDiscarded; public EntityAuditEventV2() { } @@ -178,6 +179,16 @@ public void setEntityDefinition(String entityDefinition) { this.entity = AtlasType.fromJson(entityDefinition, AtlasEntity.class); } + @JsonIgnore + public boolean isDiscarded() { + return isDiscarded; + } + + @JsonIgnore + public void setDiscarded(boolean discarded) { + isDiscarded = discarded; + } + @Override public int hashCode() { return Objects.hash(entityId, timestamp, user, action, details, eventKey, entity, type); @@ -213,6 +224,7 @@ public String toString() { ", eventKey='" + eventKey + '\'' + ", entity=" + entity + ", type=" + type + + ", isDiscarded=" + isDiscarded + '}'; } @@ -231,14 +243,15 @@ public AtlasEntityHeader getEntityHeader() { @JsonIgnore @Override public void clear() { - entityId = null; - timestamp = 0L; - user = null; - action = null; - details = null; - eventKey = null; - entity = null; - type = null; + entityId = null; + timestamp = 0L; + user = null; + action = null; + details = null; + eventKey = null; + entity = null; + type = null; + isDiscarded = false; } private String getJsonPartFromDetails() { diff --git a/intg/src/main/java/org/apache/atlas/model/instance/AtlasRule.java b/intg/src/main/java/org/apache/atlas/model/instance/AtlasRule.java new file mode 100644 index 00000000000..20ccb5dbdb4 --- /dev/null +++ b/intg/src/main/java/org/apache/atlas/model/instance/AtlasRule.java @@ -0,0 +1,494 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.atlas.model.instance; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonValue; +import org.apache.atlas.model.AtlasBaseModelObject; +import org.apache.atlas.model.annotation.AtlasJSON; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.dumpDateField; +import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.dumpObjects; + +@AtlasJSON +public class AtlasRule extends AtlasBaseModelObject implements Serializable { + private static final long serialVersionUID = 1L; + + public String desc; + public String action; + public String ruleName; + public RuleExpr ruleExpr; + private Date createdTime; + private Date updatedTime; + + public AtlasRule() { + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getRuleName() { + return ruleName; + } + + public void setRuleName(String ruleName) { + this.ruleName = ruleName; + } + + public RuleExpr getRuleExpr() { + return ruleExpr; + } + + public void setRuleExpr(RuleExpr ruleExpr) { + this.ruleExpr = ruleExpr; + } + + public Date getCreatedTime() { + return createdTime; + } + + public void setCreatedTime(Date createdTime) { + this.createdTime = createdTime; + } + + public Date getUpdatedTime() { + return updatedTime; + } + + public void setUpdatedTime(Date updatedTime) { + this.updatedTime = updatedTime; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), desc, action, ruleName, ruleExpr, createdTime, updatedTime); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + AtlasRule atlasRule = (AtlasRule) o; + return Objects.equals(desc, atlasRule.desc) && Objects.equals(action, atlasRule.action) && Objects.equals(ruleName, atlasRule.ruleName) && Objects.equals(ruleExpr, atlasRule.ruleExpr) && Objects.equals(createdTime, atlasRule.createdTime) && Objects.equals(updatedTime, atlasRule.updatedTime); + } + + @Override + public StringBuilder toString(StringBuilder sb) { + if (sb == null) { + sb = new StringBuilder(); + } + sb.append(", ruleName=").append(ruleName); + sb.append(", desc=").append(desc); + sb.append(", action=").append(action); + sb.append(", ruleExpr=").append(ruleExpr); + dumpDateField(", createdTime=", createdTime, sb); + dumpDateField(", updatedTime=", updatedTime, sb); + + return sb; + } + + public enum Condition { AND, OR} + + public static class RuleExpr { + public List ruleExprObjList; + + public RuleExpr() { + } + + public RuleExpr(List ruleExprObjList) { + this.ruleExprObjList = ruleExprObjList; + } + + public List getRuleExprObjList() { + return ruleExprObjList; + } + + public void setRuleExprObjList(List ruleExprObjList) { + this.ruleExprObjList = ruleExprObjList; + } + + @Override + public int hashCode() { + return Objects.hash(ruleExprObjList); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RuleExpr ruleExpr = (RuleExpr) o; + return Objects.equals(ruleExprObjList, ruleExpr.ruleExprObjList); + } + + @Override + public String toString() { + return toString(new StringBuilder()).toString(); + } + + public StringBuilder toString(StringBuilder sb) { + if (sb == null) { + sb = new StringBuilder(); + } + + sb.append("{\"ruleExprObjList\":["); + dumpObjects(ruleExprObjList, sb); + sb.append("]}"); + return sb; + } + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class RuleExprObject { + public String typeName; + public Condition condition; + public List criterion; + public String attributeName; + public Operator operator; + public String attributeValue; + public boolean includeSubTypes; + + public RuleExprObject() { + } + + public RuleExprObject(String typeName, String attributeName, Operator operator, String attributeValue, boolean includeSubTypes) { + this.typeName = typeName; + this.includeSubTypes = includeSubTypes; + this.attributeName = attributeName; + this.operator = operator; + this.attributeValue = attributeValue; + } + + public RuleExprObject(String typeName, Condition condition, List criterion, boolean includeSubTypes) { + this.typeName = typeName; + this.includeSubTypes = includeSubTypes; + this.condition = condition; + this.criterion = criterion; + } + + public RuleExprObject(String typeName, boolean includeSubTypes) { + this.typeName = typeName; + this.includeSubTypes = includeSubTypes; + } + + public String getTypeName() { + return typeName; + } + + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + public boolean getIncludeSubTypes() { + return includeSubTypes; + } + + public void setIncludeSubTypes(boolean includeSubTypes) { + this.includeSubTypes = includeSubTypes; + } + + public Condition getCondition() { + return condition; + } + + public void setCondition(Condition condition) { + this.condition = condition; + } + + public List getCriterion() { + return criterion; + } + + public void setCriterion(List criterion) { + this.criterion = criterion; + } + + public String getAttributeName() { + return attributeName; + } + + public void setAttributeName(String attributeName) { + this.attributeName = attributeName; + } + + public Operator getOperator() { + return operator; + } + + public void setOperator(Operator operator) { + this.operator = operator; + } + + public String getAttributeValue() { + return attributeValue; + } + + public void setAttributeValue(String attributeValue) { + this.attributeValue = attributeValue; + } + + @Override + public int hashCode() { + return Objects.hash(typeName, condition, criterion, attributeName, operator, attributeValue, includeSubTypes); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RuleExprObject that = (RuleExprObject) o; + return includeSubTypes == that.includeSubTypes && Objects.equals(typeName, that.typeName) && condition == that.condition && Objects.equals(criterion, that.criterion) && Objects.equals(attributeName, that.attributeName) && operator == that.operator && Objects.equals(attributeValue, that.attributeValue); + } + + @Override + public String toString() { + return toString(new StringBuilder()).toString(); + } + + public StringBuilder toString(StringBuilder sb) { + if (sb == null) { + sb = new StringBuilder(); + } + sb.append('{'); + if (typeName != null) { + sb.append("\"typeName\":\"").append(typeName).append('"'); + } + if (attributeName != null) { + sb.append(",\"attributeName\":\"").append(attributeName).append('"'); + } + if (operator != null) { + sb.append(",\"operator\":\"").append(operator).append('"'); + } + if (attributeValue != null) { + sb.append(",\"attributeValue\":\"").append(attributeValue).append('"'); + } + if (condition != null) { + sb.append(",\"condition\":").append(condition); + } + if (criterion != null) { + sb.append(", \"criterion\":").append(criterion); + } + sb.append(", includeSubTypes='").append(includeSubTypes).append('\''); + sb.append('}'); + + return sb; + } + + public enum Operator { + LT("<"), + GT(">"), + LTE("<="), + GTE(">="), + EQ("=="), + NEQ("!="), + STARTS_WITH("startsWith"), + ENDS_WITH("endsWith"), + CONTAINS("contains"), + NOT_CONTAINS("notContains"), + CONTAINS_IGNORECASE("containsIgnoreCase"), + NOT_CONTAINS_IGNORECASE("notContainsIgnoreCase"), + IS_NULL(OperatorType.UNARY, "isNull"), + NOT_NULL(OperatorType.UNARY, "notNull"); + + private final String symbol; + private OperatorType type = OperatorType.BINARY; + Operator(String symbol) { + this.symbol = symbol; + } + + Operator(OperatorType type, String symbol) { + this.symbol = symbol; + this.type = type; + } + + @JsonValue + public String getSymbol() { + return symbol; + } + + public boolean isBinary() { + return type == OperatorType.BINARY; + } + + @Override + public String toString() { + return getSymbol(); + } + + enum TYPE { + UNARY, + BINARY + } + } + + public enum OperatorType { + UNARY("Unary"), BINARY("Binary"); + private final String displayName; + + OperatorType(final String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Criterion { + public Operator operator; + public String attributeName; + public String attributeValue; + public Condition condition; + public List criterion; + + public Criterion() { + } + + public Criterion(Operator operator, String attributeName, String attributeValue) { + this.operator = operator; + this.attributeName = attributeName; + this.attributeValue = attributeValue; + } + + public Criterion(Condition condition, List criterion) { + this.condition = condition; + this.criterion = criterion; + } + + public Operator getOperator() { + return operator; + } + + public void setOperator(Operator operator) { + this.operator = operator; + } + + public String getAttributeName() { + return attributeName; + } + + public void setAttributeName(String attributeName) { + this.attributeName = attributeName; + } + + public String getAttributeValue() { + return attributeValue; + } + + public void setAttributeValue(String attributeValue) { + this.attributeValue = attributeValue; + } + + public Condition getCondition() { + return condition; + } + + public void setCondition(Condition condition) { + this.condition = condition; + } + + public List getCriterion() { + return criterion; + } + + public void setCriterion(List criterion) { + this.criterion = criterion; + } + + @Override + public int hashCode() { + return Objects.hash(operator, attributeName, attributeValue, condition, criterion); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Criterion criterion1 = (Criterion) o; + return operator == criterion1.operator && Objects.equals(attributeName, criterion1.attributeName) && Objects.equals(attributeValue, criterion1.attributeValue) && condition == criterion1.condition && Objects.equals(criterion, criterion1.criterion); + } + + @Override + public String toString() { + return toString(new StringBuilder()).toString(); + } + + public StringBuilder toString(StringBuilder sb) { + if (sb == null) { + sb = new StringBuilder(); + } + + sb.append('{'); + if (attributeName != null) { + sb.append("\"attributeName\":\"").append(attributeName).append('"'); + } + if (operator != null) { + sb.append(",\"operator\":\"").append(operator).append('"'); + } + if (attributeValue != null) { + sb.append(",\"attributeValue\":\"").append(attributeValue).append('"'); + } + if (condition != null) { + sb.append(" \"condition\":").append(condition); + } + if (criterion != null) { + sb.append(", \"criterion\":").append(criterion); + } + sb.append('}'); + + return sb; + } + } + } +} diff --git a/repository/pom.xml b/repository/pom.xml index fcadcd19768..47f08912cfe 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -80,6 +80,12 @@ ${commons-codec.version} + + io.github.jamsesso + json-logic-java + 1.0.7 + + joda-time joda-time diff --git a/repository/src/main/java/org/apache/atlas/repository/audit/EntityAuditListenerV2.java b/repository/src/main/java/org/apache/atlas/repository/audit/EntityAuditListenerV2.java index f0ac8a9eced..a36175db49d 100644 --- a/repository/src/main/java/org/apache/atlas/repository/audit/EntityAuditListenerV2.java +++ b/repository/src/main/java/org/apache/atlas/repository/audit/EntityAuditListenerV2.java @@ -17,6 +17,7 @@ */ package org.apache.atlas.repository.audit; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.atlas.AtlasConfiguration; import org.apache.atlas.EntityAuditEvent.EntityAuditAction; import org.apache.atlas.RequestContext; @@ -31,18 +32,22 @@ import org.apache.atlas.model.instance.AtlasRelationship; import org.apache.atlas.model.instance.AtlasStruct; import org.apache.atlas.repository.converters.AtlasInstanceConverter; +import org.apache.atlas.rulesengine.AtlasEntityAuditFilterService; +import org.apache.atlas.rulesengine.AtlasRulesEngine; import org.apache.atlas.type.AtlasEntityType; import org.apache.atlas.type.AtlasStructType.AtlasAttribute; import org.apache.atlas.type.AtlasType; import org.apache.atlas.type.AtlasTypeRegistry; import org.apache.atlas.utils.AtlasJson; import org.apache.atlas.utils.AtlasPerfMetrics.MetricRecorder; +import org.apache.atlas.utils.AtlasPerfTracer; import org.apache.atlas.utils.FixedBufferList; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.inject.Inject; @@ -74,16 +79,22 @@ import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.PROPAGATED_CLASSIFICATION_UPDATE; import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.TERM_ADD; import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.TERM_DELETE; +import static org.apache.atlas.rulesengine.AtlasEntityAuditFilterService.ATLAS_RULE_ENTITY_NAME; +import static org.apache.atlas.rulesengine.AtlasEntityAuditFilterService.ATTR_OPERATION_TYPE; +import static org.apache.atlas.type.AtlasTypeUtil.ATTRIBUTE_QUALIFIED_NAME; @Component public class EntityAuditListenerV2 implements EntityChangeListenerV2 { - private static final Logger LOG = LoggerFactory.getLogger(EntityAuditListenerV2.class); + private static final Logger LOG = LoggerFactory.getLogger(EntityAuditListenerV2.class); + private static final Logger PERF_LOG = AtlasPerfTracer.getPerfLogger("EntityChangeListenerV2"); private static final ThreadLocal> AUDIT_EVENTS_BUFFER = ThreadLocal.withInitial(() -> new FixedBufferList<>(EntityAuditEventV2.class, AtlasConfiguration.NOTIFICATION_FIXED_BUFFER_ITEMS_INCREMENT_COUNT.getInt())); - private final EntityAuditRepository auditRepository; - private final AtlasTypeRegistry typeRegistry; - private final AtlasInstanceConverter instanceConverter; + private final EntityAuditRepository auditRepository; + private final AtlasTypeRegistry typeRegistry; + private final AtlasInstanceConverter instanceConverter; + private AtlasEntityAuditFilterService auditFilterService; + private AtlasRulesEngine rulesEngine; @Inject public EntityAuditListenerV2(EntityAuditRepository auditRepository, AtlasTypeRegistry typeRegistry, AtlasInstanceConverter instanceConverter) { @@ -92,6 +103,12 @@ public EntityAuditListenerV2(EntityAuditRepository auditRepository, AtlasTypeReg this.instanceConverter = instanceConverter; } + @Autowired + public void setAuditFilterService(AtlasEntityAuditFilterService auditFilterService) throws AtlasBaseException { + this.auditFilterService = auditFilterService; + LOG.info("AtlasEntityAuditFilterService injected into EntityAuditListenerV2"); + } + @Override public void onEntitiesAdded(List entities, boolean isImport) throws AtlasBaseException { MetricRecorder metric = RequestContext.get().startMetricRecord("entityAudit"); @@ -411,10 +428,63 @@ private EntityAuditEventV2 createEvent(EntityAuditEventV2 entityAuditEventV2, At entityAuditEventV2.setAction(action); entityAuditEventV2.setDetails(details); entityAuditEventV2.setEntity(entity); - + if (auditFilterService != null && auditFilterService.isEntityAuditCustomFilterEnabled()) { + entityAuditEventV2.setDiscarded(auditFilterService.isDiscardByDefault()); + try { + applyRules(entity, entityAuditEventV2); + } catch (Exception e) { + throw new RuntimeException("Exception occurred while applying rules on the entity", e); + } + } else { + entityAuditEventV2.setDiscarded(false); + } return entityAuditEventV2; } + private void applyRules(AtlasEntity entity, EntityAuditEventV2 entityAuditEventV2) throws AtlasBaseException { + AtlasPerfTracer perf = null; + + if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { + perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityGraphMapper.applyRules"); + } + + if (ATLAS_RULE_ENTITY_NAME.equals(entity.getTypeName())) { + return; + } + + Map dataObj = prepareDataObject(entity, entityAuditEventV2); + + try { + boolean accept = getRulesEngine().accept(dataObj); + + entityAuditEventV2.setDiscarded(!accept); + + LOG.debug("Entity audit for type: {} qualifiedName: {} is {}", + entity.getTypeName(), + entity.getAttribute(ATTRIBUTE_QUALIFIED_NAME), + accept ? "accepted" : "discarded"); + } catch (Exception e) { + LOG.warn("Failed to apply rules for entity: {} due to:", entity.getTypeName(), e); + throw new AtlasBaseException(e); + } finally { + AtlasPerfTracer.log(perf); + } + } + + private AtlasRulesEngine getRulesEngine() throws AtlasBaseException { + if (rulesEngine == null) { + rulesEngine = new AtlasRulesEngine(auditFilterService); + } + return rulesEngine; + } + + private Map prepareDataObject(AtlasEntity entity, EntityAuditEventV2 entityAuditEventV2) { + ObjectMapper oMapper = new ObjectMapper(); + Map preparedDataObj = oMapper.convertValue(entity, Map.class); + preparedDataObj.put(ATTR_OPERATION_TYPE, entityAuditEventV2.getAction().name()); + return preparedDataObj; + } + private EntityAuditEventV2 createEvent(EntityAuditEventV2 event, AtlasEntity entity, EntityAuditActionV2 action) { String detail = getAuditEventDetail(entity, action); diff --git a/repository/src/main/java/org/apache/atlas/repository/audit/HBaseBasedAuditRepository.java b/repository/src/main/java/org/apache/atlas/repository/audit/HBaseBasedAuditRepository.java index 5f3e679511f..585e4060dca 100644 --- a/repository/src/main/java/org/apache/atlas/repository/audit/HBaseBasedAuditRepository.java +++ b/repository/src/main/java/org/apache/atlas/repository/audit/HBaseBasedAuditRepository.java @@ -283,6 +283,11 @@ public void putEventsV2(List events) throws AtlasBaseExcepti for (int index = 0; index < events.size(); index++) { EntityAuditEventV2 event = events.get(index); + if (event.isDiscarded()) { + LOG.debug("Discarding entity audit event {}", event); + continue; + } + LOG.debug("Adding entity audit event {}", event); Put put = new Put(getKey(event.getEntityId(), event.getTimestamp(), index)); diff --git a/repository/src/main/java/org/apache/atlas/repository/ogm/AtlasRuleDTO.java b/repository/src/main/java/org/apache/atlas/repository/ogm/AtlasRuleDTO.java new file mode 100644 index 00000000000..c226a298d90 --- /dev/null +++ b/repository/src/main/java/org/apache/atlas/repository/ogm/AtlasRuleDTO.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.atlas.repository.ogm; + +import org.apache.atlas.exception.AtlasBaseException; +import org.apache.atlas.model.instance.AtlasEntity; +import org.apache.atlas.model.instance.AtlasRule; +import org.apache.atlas.type.AtlasType; +import org.apache.atlas.type.AtlasTypeRegistry; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; + +import java.util.HashMap; +import java.util.Map; + +import static java.util.Objects.requireNonNull; + +@Component +public class AtlasRuleDTO extends AbstractDataTransferObject { + private static final Logger LOG = LoggerFactory.getLogger(AtlasRuleDTO.class); + public static final String ENTITY_TYPE_NAME = "__AtlasRule"; + public static final String PROPERTY_RULE_NAME = "ruleName"; + public static final String PROPERTY_RULE_EXPR = "ruleExpr"; + private static final String PROPERTY_DESC = "desc"; + private static final String PROPERTY_ACTION = "action"; + + @Inject + public AtlasRuleDTO(AtlasTypeRegistry typeRegistry) { + super(typeRegistry, AtlasRule.class, ENTITY_TYPE_NAME); + } + + @Override + public AtlasRule from(AtlasEntity entity) { + AtlasRule atlasRule = new AtlasRule(); + + atlasRule.setGuid(entity.getGuid()); + atlasRule.setRuleName((String) entity.getAttribute(PROPERTY_RULE_NAME)); + atlasRule.setDesc((String) entity.getAttribute(PROPERTY_DESC)); + atlasRule.setAction((String) entity.getAttribute(PROPERTY_ACTION)); + String jsonRuleExpr = (String) entity.getAttribute(PROPERTY_RULE_EXPR); + if (StringUtils.isNotEmpty(jsonRuleExpr)) { + atlasRule.setRuleExpr(AtlasType.fromJson(jsonRuleExpr, AtlasRule.RuleExpr.class)); + } + atlasRule.setCreatedTime(entity.getCreateTime()); + atlasRule.setUpdatedTime(entity.getUpdateTime()); + + return atlasRule; + } + + @Override + public AtlasRule from(AtlasEntity.AtlasEntityWithExtInfo entityWithExtInfo) { + LOG.debug("==> AtlasRuleDTO.from({})", entityWithExtInfo); + + requireNonNull(entityWithExtInfo, "entity"); + AtlasRule ret = from(entityWithExtInfo.getEntity()); + + LOG.debug("<== AtlasRuleDTO.from() : {}", ret); + + return ret; + } + + @Override + public AtlasEntity toEntity(AtlasRule atlasRule) throws AtlasBaseException { + LOG.debug("==> AtlasRuleDTO.toEntity({})", atlasRule); + + AtlasEntity ret = getDefaultAtlasEntity(atlasRule); + + ret.setAttribute(PROPERTY_RULE_NAME, atlasRule.getRuleName()); + ret.setAttribute(PROPERTY_DESC, atlasRule.getDesc()); + ret.setAttribute(PROPERTY_ACTION, atlasRule.getAction()); + ret.setAttribute(PROPERTY_RULE_EXPR, AtlasType.toJson(atlasRule.getRuleExpr())); + + LOG.debug("<== AtlasRuleDTO.toEntity() : {}", ret); + + return ret; + } + + @Override + public AtlasEntity.AtlasEntityWithExtInfo toEntityWithExtInfo(AtlasRule obj) throws AtlasBaseException { + LOG.debug("==> AtlasRuleDTO.toEntityWithExtInfo({})", obj); + + AtlasEntity.AtlasEntityWithExtInfo ret = new AtlasEntity.AtlasEntityWithExtInfo(toEntity(obj)); + + LOG.debug("<== AtlasRuleDTO.toEntityWithExtInfo() : {}", ret); + + return ret; + } + + @Override + public Map getUniqueAttributes(AtlasRule obj) { + Map ret = new HashMap<>(); + ret.put(PROPERTY_RULE_NAME, obj.getRuleName()); + ret.put(PROPERTY_RULE_EXPR, obj.getRuleExpr()); + return ret; + } +} diff --git a/repository/src/main/java/org/apache/atlas/repository/ogm/DataAccess.java b/repository/src/main/java/org/apache/atlas/repository/ogm/DataAccess.java index a3be9c0e146..d063f13bc61 100644 --- a/repository/src/main/java/org/apache/atlas/repository/ogm/DataAccess.java +++ b/repository/src/main/java/org/apache/atlas/repository/ogm/DataAccess.java @@ -30,6 +30,7 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import javax.inject.Inject; @@ -40,6 +41,7 @@ import static java.util.Objects.requireNonNull; @Component +@Lazy public class DataAccess { private static final Logger LOG = LoggerFactory.getLogger(DataAccess.class); private static final Logger PERF_LOG = AtlasPerfTracer.getPerfLogger("repository.DataAccess"); @@ -209,7 +211,7 @@ public void deleteUsingGuid(String guid) throws AtlasBaseException { entityStore.deleteById(guid); } - public void delete(String guid) throws AtlasBaseException { + public EntityMutationResponse delete(String guid) throws AtlasBaseException { requireNonNull(guid, "guid"); AtlasPerfTracer perf = null; @@ -218,13 +220,13 @@ public void delete(String guid) throws AtlasBaseException { perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "DataAccess.delete()"); } - entityStore.deleteById(guid); + return entityStore.deleteById(guid); } finally { AtlasPerfTracer.log(perf); } } - public void delete(List guids) throws AtlasBaseException { + public EntityMutationResponse delete(List guids) throws AtlasBaseException { requireNonNull(guids, "guids"); AtlasPerfTracer perf = null; @@ -234,7 +236,7 @@ public void delete(List guids) throws AtlasBaseException { perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "DataAccess.multiDelete()"); } - entityStore.deleteByIds(guids); + return entityStore.deleteByIds(guids); } finally { AtlasPerfTracer.log(perf); } diff --git a/repository/src/main/java/org/apache/atlas/rulesengine/AtlasEntityAuditFilterService.java b/repository/src/main/java/org/apache/atlas/rulesengine/AtlasEntityAuditFilterService.java new file mode 100644 index 00000000000..853a924354b --- /dev/null +++ b/repository/src/main/java/org/apache/atlas/rulesengine/AtlasEntityAuditFilterService.java @@ -0,0 +1,726 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.atlas.rulesengine; + +import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.apache.atlas.AtlasConfiguration; +import org.apache.atlas.AtlasErrorCode; +import org.apache.atlas.SortOrder; +import org.apache.atlas.annotation.AtlasService; +import org.apache.atlas.annotation.GraphTransaction; +import org.apache.atlas.exception.AtlasBaseException; +import org.apache.atlas.model.audit.EntityAuditEventV2; +import org.apache.atlas.model.instance.AtlasEntity; +import org.apache.atlas.model.instance.AtlasRule; +import org.apache.atlas.model.instance.EntityMutationResponse; +import org.apache.atlas.repository.audit.AtlasAuditService; +import org.apache.atlas.repository.graphdb.AtlasVertex; +import org.apache.atlas.repository.ogm.AtlasRuleDTO; +import org.apache.atlas.repository.ogm.DataAccess; +import org.apache.atlas.repository.store.graph.v2.AtlasGraphUtilsV2; +import org.apache.atlas.type.AtlasEntityType; +import org.apache.atlas.type.AtlasType; +import org.apache.atlas.type.AtlasTypeRegistry; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; + +import javax.inject.Inject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.apache.atlas.model.instance.AtlasEntity.KEY_STATUS; +import static org.apache.atlas.repository.Constants.ATTRIBUTE_NAME_STATE; +import static org.apache.atlas.repository.Constants.INTERNAL_PROPERTY_KEY_PREFIX; +import static org.apache.atlas.repository.ogm.AtlasRuleDTO.ENTITY_TYPE_NAME; +import static org.apache.atlas.repository.ogm.AtlasRuleDTO.PROPERTY_RULE_NAME; +import static org.apache.atlas.rulesengine.RuleAction.Result.ACCEPT; +import static org.apache.atlas.rulesengine.RuleAction.Result.DISCARD; +import static org.apache.atlas.type.AtlasStructType.UNIQUE_ATTRIBUTE_SHADE_PROPERTY_PREFIX; + +@AtlasService +public class AtlasEntityAuditFilterService { + public static final String ATLAS_RULE_ENTITY_NAME = "AtlasRule"; + public static final String ATTR_OPERATION_TYPE = "operationType"; + public static final String ALL_ENTITY_TYPES = "_ALL_ENTITY_TYPES"; + public static final AtlasEntityType MATCH_ALL_ENTITY_TYPES = AtlasEntityType.getEntityRoot(); + public static final String TYPENAME_DELIMITER = ","; + public static final List externalAttributes = new ArrayList<>(Collections.singletonList(ATTR_OPERATION_TYPE)); + static final String ATLAS_RULE_TYPENAME = "__AtlasRule"; + private static final Logger LOG = LoggerFactory.getLogger(AtlasAuditService.class); + final AtlasTypeRegistry typeRegistry; + private final DataAccess dataAccess; + private final AtlasRuleDTO ruleDTO; + private final boolean isEntityAuditCustomFilterEnabled; + private final String defaultEntityAuditFilterAction; + private JsonLogicConverter jsonLogicConverter; + private boolean isDiscardByDefault; + + @Inject + public AtlasEntityAuditFilterService(@Lazy DataAccess dataAccess, AtlasTypeRegistry atlasTypeRegistry, AtlasRuleDTO ruleDTO) { + this.dataAccess = dataAccess; + this.typeRegistry = atlasTypeRegistry; + this.ruleDTO = ruleDTO; + this.isEntityAuditCustomFilterEnabled = AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getBoolean(); + this.defaultEntityAuditFilterAction = AtlasConfiguration.DEFAULT_ENTITY_AUDIT_FILTER_ACTION.getString(); + if (isEntityAuditCustomFilterEnabled()) { + isDiscardByDefault = setDiscardByDefault(); + jsonLogicConverter = new JsonLogicConverter(); + LOG.info("AtlasEntityAuditFilterService is initialized"); + } else { + LOG.info("AtlasEntityAuditFilterService is disabled"); + } + } + + public static boolean isDuplicateRuleExpr(List criterion) { + Object[] criterionList = criterion.toArray(); + return Arrays.stream(criterionList).distinct().count() != criterionList.length; + } + + public boolean isDiscardByDefault() { + return isDiscardByDefault; + } + + public boolean isEntityAuditCustomFilterEnabled() { + return isEntityAuditCustomFilterEnabled; + } + + public String getDefaultEntityAuditFilterAction() { + return defaultEntityAuditFilterAction; + } + + public JsonLogicConverter getJsonLogicConverter() { + return jsonLogicConverter; + } + + public RuleAction getDefaultAction() { + return AtlasRuleUtils.getRuleActionFromString(defaultEntityAuditFilterAction); + } + + @GraphTransaction + public List fetchRules() throws AtlasBaseException { + LOG.debug("==> AtlasEntityAuditFilterService.fetchRules()"); + + List rules; + List ruleGuids = AtlasGraphUtilsV2.findEntityGUIDsByType(ATLAS_RULE_TYPENAME, SortOrder.ASCENDING); + AtlasEntity.AtlasEntitiesWithExtInfo ruleEntities; + + if (CollectionUtils.isNotEmpty(ruleGuids)) { + ruleEntities = dataAccess.getAtlasEntityStore().getByIds(ruleGuids, true, false); + rules = new ArrayList<>(); + for (AtlasEntity ruleEntity : ruleEntities.getEntities()) { + AtlasRule rule = ruleDTO.from(ruleEntity); + rules.add(rule); + } + } else { + rules = Collections.emptyList(); + } + + LOG.debug("<== AtlasEntityAuditFilterService.fetchRules() : {}", rules); + + return AtlasRuleUtils.getSortedRules(rules); + } + + @GraphTransaction + public AtlasRule createRule(AtlasRule atlasRule) throws AtlasBaseException { + LOG.debug("==> AtlasEntityAuditFilterService.createRule({})", atlasRule); + + if (Objects.isNull(atlasRule)) { + throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "Rule definition missing"); + } + + if (StringUtils.isEmpty(atlasRule.getRuleName())) { + throw new AtlasBaseException(AtlasErrorCode.MISSING_MANDATORY_ATTRIBUTE, ATLAS_RULE_ENTITY_NAME, "ruleName"); + } + if (StringUtils.isEmpty(atlasRule.getAction())) { + throw new AtlasBaseException(AtlasErrorCode.MISSING_MANDATORY_ATTRIBUTE, ATLAS_RULE_ENTITY_NAME, "action"); + } + if (Objects.isNull(atlasRule.getRuleExpr())) { + throw new AtlasBaseException(AtlasErrorCode.MISSING_MANDATORY_ATTRIBUTE, ATLAS_RULE_ENTITY_NAME, "ruleExpr"); + } + + validateRuleAction(atlasRule.getAction()); + validateRuleName(atlasRule.getRuleName()); + validateRuleExprExists(atlasRule.getRuleExpr()); + validateRuleExprFormat(atlasRule.getRuleExpr()); + + AtlasRule storeObject = dataAccess.save(atlasRule); + + LOG.debug("<== AtlasEntityAuditFilterService.createRule() : {}", storeObject); + + return storeObject; + } + + public Set getTypesAndSubtypes(Set entityTypes) { + Set typeAndSubTypes = new HashSet<>(); + if (CollectionUtils.isNotEmpty(entityTypes)) { + for (AtlasEntityType entityType : entityTypes) { + if (entityType.equals(MATCH_ALL_ENTITY_TYPES)) { + typeAndSubTypes = Collections.emptySet(); + break; + } else { + Set allTypes = entityType.getTypeAndAllSubTypes(); + typeAndSubTypes.addAll(allTypes); + } + } + } else { + typeAndSubTypes = Collections.emptySet(); + } + + return typeAndSubTypes; + } + + @GraphTransaction + public AtlasRule updateRule(AtlasRule atlasRule) throws AtlasBaseException { + LOG.debug("==> AtlasEntityAuditFilterService.updateRule({})", atlasRule); + + if (Objects.isNull(atlasRule)) { + throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "Rule is null/empty"); + } + + AtlasRule storeObject = dataAccess.load(atlasRule); + + if (StringUtils.isEmpty(atlasRule.getDesc())) { + atlasRule.setDesc(storeObject.getDesc()); + } + + if (StringUtils.isNotEmpty(atlasRule.getRuleName()) && !storeObject.getRuleName().equals(atlasRule.getRuleName())) { + validateRuleName(atlasRule.getRuleName()); + } else { + atlasRule.setRuleName(storeObject.getRuleName()); + } + + if (StringUtils.isNotEmpty(atlasRule.getAction())) { + validateRuleAction(atlasRule.getAction()); + } else { + atlasRule.setAction(storeObject.getAction()); + } + + if (!Objects.isNull(atlasRule.getRuleExpr())) { + validateRuleExprFormat(atlasRule.getRuleExpr()); + } else { + atlasRule.setRuleExpr(storeObject.getRuleExpr()); + } + + storeObject = dataAccess.save(atlasRule); + + LOG.debug("<== AtlasEntityAuditFilterService.updateRule() : {}", storeObject); + + return storeObject; + } + + @GraphTransaction + public EntityMutationResponse deleteRule(String ruleGuid) throws AtlasBaseException { + LOG.debug("==> AtlasEntityAuditFilterService.deleteRule({})", ruleGuid); + + if (Objects.isNull(ruleGuid)) { + throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "RuleGuid is null/empty"); + } + + AtlasRule storeObject = new AtlasRule(); + storeObject.setGuid(ruleGuid); + dataAccess.load(storeObject); //this will check and revert with exception if the rule is already deleted + EntityMutationResponse ret = dataAccess.delete(ruleGuid); + + LOG.debug("<== AtlasEntityAuditFilterService.deleteRule()"); + + return ret; + } + + public EntityMutationResponse deleteRules(List ruleGuids) throws AtlasBaseException { + LOG.debug("==> AtlasEntityAuditFilterService.deleteRules({})", Arrays.toString(ruleGuids.toArray())); + + if (CollectionUtils.isEmpty(ruleGuids)) { + throw new AtlasBaseException(AtlasErrorCode.INVALID_PARAMETERS, "Guid(s) not specified"); + } + List guidList = new ArrayList<>(); + for (String ruleGuid : ruleGuids) { + AtlasRule storeObject = new AtlasRule(); + storeObject.setGuid(ruleGuid); + dataAccess.load(storeObject); + guidList.add(ruleGuid); + } + EntityMutationResponse ret = dataAccess.delete(guidList); + + LOG.debug("<== AtlasEntityAuditFilterService.deleteRules()"); + + return ret; + } + + public EntityMutationResponse deleteAllRules() throws AtlasBaseException { + LOG.debug("==> AtlasEntityAuditFilterService.deleteAllRules()"); + + List ruleGuids = AtlasGraphUtilsV2.findEntityGUIDsByType(ATLAS_RULE_TYPENAME, SortOrder.ASCENDING); + if (CollectionUtils.isEmpty(ruleGuids)) { + throw new AtlasBaseException(AtlasErrorCode.EMPTY_RESULTS, "Rules"); + } + + for (String ruleGuid : ruleGuids) { + AtlasRule storeObject = new AtlasRule(); + storeObject.setGuid(ruleGuid); + dataAccess.load(storeObject); + } + EntityMutationResponse ret = dataAccess.delete(ruleGuids); + + LOG.debug("<== AtlasEntityAuditFilterService.deleteAllRules()"); + + return ret; + } + + public Set getAllMatchingEntityNames(String singleTypeName, boolean includeSubTypes) { + Set matchingTypeNames = null; + if (!includeSubTypes) { + if (!singleTypeName.contains("*")) { + return Collections.singleton(singleTypeName); + } + matchingTypeNames = getAllMatchingEntityTypesAsStringSet(singleTypeName); + } else { + Set matchingEntityTypes = getAllMatchingEntityTypesAsTypeSet(singleTypeName); + if (matchingEntityTypes != null) { + matchingTypeNames = getTypesAndSubtypes(matchingEntityTypes); + } + } + + return matchingTypeNames; + } + + Set getEntityTypes(String typeName) throws AtlasBaseException { + Set entityTypes = null; + if (org.apache.commons.lang3.StringUtils.isNotEmpty(typeName)) { + Set typeNames = Stream.of(typeName.trim().split(TYPENAME_DELIMITER)).collect(Collectors.toSet()); + if (typeNames.size() > 1 && typeNames.contains(ALL_ENTITY_TYPES)) { + throw new AtlasBaseException(ALL_ENTITY_TYPES + " can not be included with any other entity type"); + } + + entityTypes = new HashSet<>(); + Set invalidEntityTypes = new HashSet<>(); + + for (String name : typeNames) { + Set matchingEntityTypes = getAllMatchingEntityTypesAsTypeSet(name); + if (matchingEntityTypes != null) { + entityTypes.addAll(matchingEntityTypes); + } else { + invalidEntityTypes.add(name); + } + } + + if (invalidEntityTypes.size() > 0) { + throw new AtlasBaseException(AtlasErrorCode.UNKNOWN_TYPENAME, String.join(TYPENAME_DELIMITER, invalidEntityTypes)); + } + } + return entityTypes; + } + + Set getEntityTypeNames(String typeName, boolean includeSubTypes) { + Set entityTypeNames = null; + if (StringUtils.isNotEmpty(typeName)) { + //all mix formed multiple typenames received - with or without wildcard; + Set typeNames = Stream.of(typeName.trim().split(TYPENAME_DELIMITER)).collect(Collectors.toSet()); + + entityTypeNames = new HashSet<>(); + for (String name : typeNames) { + Set matchingEntityTypes = getAllMatchingEntityNames(name, includeSubTypes); + entityTypeNames.addAll(matchingEntityTypes); + } + } + + return entityTypeNames; + } + + private boolean setDiscardByDefault() { + RuleAction.Result result = ACCEPT; + if (getDefaultAction() != null) { + result = getDefaultAction().getResult(); + } + return (result == DISCARD); + } + + private void validateRuleName(String ruleName) throws AtlasBaseException { + AtlasVertex vertex = AtlasGraphUtilsV2.findByUniqueAttributes(typeRegistry.getEntityTypeByName(ATLAS_RULE_TYPENAME), new HashMap() { + { + put(PROPERTY_RULE_NAME, ruleName); + } + }); + if (Objects.nonNull(vertex)) { + throw new AtlasBaseException(AtlasErrorCode.RULE_NAME_ALREADY_EXISTS, ruleName); + } + } + + private void validateRuleAction(String action) throws AtlasBaseException { + if (StringUtils.isNotEmpty(action)) { + for (RuleAction.Result res : RuleAction.Result.values()) { + if (res.name().equals(action)) { + return; + } + } + throw new AtlasBaseException(AtlasErrorCode.INVALID_RULE_ACTION); + } + } + + private void validateRuleExprFormat(AtlasRule.RuleExpr ruleExpr) throws AtlasBaseException { + if (ruleExpr == null) { + return; + } + + List allExpressions = ruleExpr.getRuleExprObjList(); + List> recordedTypeNamesList = new ArrayList<>(); + + for (AtlasRule.RuleExprObject ruleExprObj : allExpressions) { + validateTypeName(ruleExprObj.getTypeName(), recordedTypeNamesList); + + AtlasRule.Condition condition = ruleExprObj.getCondition(); + List criterion = ruleExprObj.getCriterion(); + validateConditionAndCriteria(condition, criterion); + + validateAttributes(ruleExprObj, condition, criterion); + } + } + + private void validateTypeName(String typeName, List> recordedTypeNamesList) throws AtlasBaseException { + if (Strings.isNullOrEmpty(typeName) || "null".equals(typeName)) { + throw new AtlasBaseException(AtlasErrorCode.MISSING_MANDATORY_TYPENAME_IN_RULE_EXPR); + } + + if (isDuplicateTypeNameValue(recordedTypeNamesList, typeName)) { + throw new AtlasBaseException(AtlasErrorCode.DUPLICATE_TYPENAME_IN_RULE_EXPR, typeName); + } + + recordedTypeNamesList.add(Arrays.asList(typeName.split(","))); + + getEntityTypes(typeName); + } + + private void validateConditionAndCriteria(AtlasRule.Condition condition, List criterion) throws AtlasBaseException { + if (condition != null && CollectionUtils.isEmpty(criterion)) { + throw new AtlasBaseException(AtlasErrorCode.MISSING_CRITERIA_CONDITION, "criteria"); + } + if (CollectionUtils.isNotEmpty(criterion) && condition == null) { + throw new AtlasBaseException(AtlasErrorCode.MISSING_CRITERIA_CONDITION, "condition"); + } + } + + private void validateAttributes(AtlasRule.RuleExprObject ruleExprObj, AtlasRule.Condition condition, List criterion) throws AtlasBaseException { + Set entityTypes = getEntityTypes(ruleExprObj.getTypeName()); + + for (AtlasEntityType entityType : entityTypes) { + if (condition != null && CollectionUtils.isNotEmpty(criterion)) { + validateCriteriaList(entityType, criterion); + } else { + validateExpression(entityType, ruleExprObj.getOperator(), ruleExprObj.getAttributeName(), ruleExprObj.getAttributeValue()); + } + } + } + + private boolean isDuplicateTypeNameValue(List> recordedTypeNamesList, String typeName) { + return AtlasRuleUtils.isDuplicateList(recordedTypeNamesList, Arrays.asList(typeName.split(","))); + } + + private void validateExternalAttribute(String attrName, String attrValue, AtlasRule.RuleExprObject.Operator operator) throws AtlasBaseException { + if (ATTR_OPERATION_TYPE.equals(attrName)) { + EntityAuditEventV2.EntityAuditActionV2[] enumConstants = EntityAuditEventV2.EntityAuditActionV2.class.getEnumConstants(); + if (isValidOperator(enumConstants, operator, attrValue)) { + return; + } + } + + throw new AtlasBaseException(AtlasErrorCode.INVALID_OPERATOR_ON_ATTRIBUTE, operator.getSymbol(), ATTR_OPERATION_TYPE); + } + + private boolean isValidOperator(EntityAuditEventV2.EntityAuditActionV2[] enumConstants, AtlasRule.RuleExprObject.Operator operator, String attrValue) { + switch (operator) { + case EQ: + return Arrays.stream(enumConstants).anyMatch(e -> e.name().equals(attrValue)); + case STARTS_WITH: + return Arrays.stream(enumConstants).anyMatch(e -> (boolean) AtlasRuleUtils.startsWithFunc.apply(new String[] {e.name(), attrValue})); + case ENDS_WITH: + return Arrays.stream(enumConstants).anyMatch(e -> (boolean) AtlasRuleUtils.endsWithFunc.apply(new String[] {e.name(), attrValue})); + case CONTAINS: + return Arrays.stream(enumConstants).anyMatch(e -> (boolean) AtlasRuleUtils.containsFunc.apply(new String[] {e.name(), attrValue})); + case CONTAINS_IGNORECASE: + return Arrays.stream(enumConstants).anyMatch(e -> (boolean) AtlasRuleUtils.containsIgnoreCaseFunc.apply(new String[] {e.name(), attrValue})); + case NOT_CONTAINS: + return Arrays.stream(enumConstants).anyMatch(e -> (boolean) AtlasRuleUtils.notContainsFunc.apply(new String[] {e.name(), attrValue})); + case NOT_CONTAINS_IGNORECASE: + return Arrays.stream(enumConstants).anyMatch(e -> (boolean) AtlasRuleUtils.notContainsIgnoreCaseFunc.apply(new String[] {e.name(), attrValue})); + default: + return false; + } + } + + private AtlasEntityType getEntityType(String entityName) { + return StringUtils.equals(entityName, ALL_ENTITY_TYPES) ? MATCH_ALL_ENTITY_TYPES : + typeRegistry.getEntityTypeByName(entityName); + } + + private Set getAllMatchingEntityTypesAsStringSet(String entityName) { + Collection allTypeNamesSet = typeRegistry.getAllEntityDefNames(); + + return allTypeNamesSet.stream() + .filter(strTypeName -> AtlasRuleUtils.match(entityName, strTypeName)) + .filter(Objects::nonNull).collect(Collectors.toSet()); + } + + private Set getAllMatchingEntityTypesAsTypeSet(String entityName) { + if (!entityName.contains("*")) { + AtlasEntityType entityType = getEntityType(entityName); + return entityType == null ? null : Collections.singleton(entityType); + } + + Set matchingTypeNames = getAllMatchingEntityTypesAsStringSet(entityName); + Set entityTypes = null; + if (matchingTypeNames.size() > 0) { + entityTypes = matchingTypeNames.stream() + .map(n -> typeRegistry.getEntityTypeByName(n)) + .filter(Objects::nonNull).collect(Collectors.toSet()); + } + + return entityTypes; + } + + private void validateOperator(AtlasRule.RuleExprObject.Operator operator) throws AtlasBaseException { + if (operator == null) { + throw new AtlasBaseException(AtlasErrorCode.MISSING_MANDATORY_OPERATOR_IN_RULE_EXPR_CRITERIA); + } + } + + private void validateRuleExprExists(AtlasRule.RuleExpr ruleExpr) throws AtlasBaseException { + AtlasVertex vertex = AtlasGraphUtilsV2.findByUniqueAttributes(typeRegistry.getEntityTypeByName(ATLAS_RULE_TYPENAME), new HashMap() { + { + put(AtlasRuleDTO.PROPERTY_RULE_EXPR, AtlasType.toJson(ruleExpr)); + } + }); + if (Objects.nonNull(vertex)) { + String propName = ENTITY_TYPE_NAME + "." + UNIQUE_ATTRIBUTE_SHADE_PROPERTY_PREFIX + PROPERTY_RULE_NAME; + String ruleName = vertex.getProperty(propName, String.class); + throw new AtlasBaseException(AtlasErrorCode.RULE_EXPRESSION_ALREADY_EXISTS, ruleName); + } + } + + private void validateCriteriaList(AtlasEntityType entityType, List criteriaList) throws AtlasBaseException { + if (isDuplicateRuleExpr(criteriaList)) { + throw new AtlasBaseException(AtlasErrorCode.DUPLICATE_CONDITION_IN_SAME_RULE_EXPR); + } + for (AtlasRule.RuleExprObject.Criterion criterion : criteriaList) { + validateCriteria(entityType, criterion); + } + } + + private void validateCriteria(AtlasEntityType entityType, AtlasRule.RuleExprObject.Criterion criteria) throws AtlasBaseException { + AtlasRule.Condition condition = criteria.getCondition(); + if (condition != null && CollectionUtils.isNotEmpty(criteria.getCriterion())) { + validateCriteriaList(entityType, criteria.getCriterion()); + } else { + validateExpression(entityType, criteria.getOperator(), criteria.getAttributeName(), criteria.getAttributeValue()); + } + } + + private void validateExpression(AtlasEntityType entityType, AtlasRule.RuleExprObject.Operator op, String attrName, String attrVal) throws AtlasBaseException { + if (op != null || StringUtils.isNotEmpty(attrName) || StringUtils.isNotEmpty(attrVal)) { + validateOperator(op); + validateAttribute(entityType, attrName, attrVal, op); + } + } + + private void validateAttribute(AtlasEntityType entityType, String attributeName, String attributeValue, AtlasRule.RuleExprObject.Operator operator) throws AtlasBaseException { + if (Strings.isNullOrEmpty(attributeName) || "null".equals(attributeName)) { + throw new AtlasBaseException(AtlasErrorCode.MISSING_ATTRIBUTE_NAME_IN_RULE_EXPR); + } + if (operator.isBinary() && (Strings.isNullOrEmpty(attributeValue) || "null".equals(attributeValue))) { + throw new AtlasBaseException(AtlasErrorCode.MISSING_ATTRIBUTE_VALUE_IN_RULE_EXPR, attributeName); + } + if (externalAttributes.contains(attributeName)) { + validateExternalAttribute(attributeName, attributeValue, operator); + } else { + validateAttribute(entityType, attributeName); + } + } + + private void validateAttribute(final AtlasEntityType entityType, String attributeName) throws AtlasBaseException { + if (StringUtils.isNotEmpty(attributeName) && (entityType == null || entityType.getAttributeType(attributeName) == null)) { + if (entityType == null) { + throw new AtlasBaseException(AtlasErrorCode.UNKNOWN_TYPENAME, "NULL"); + } + String name = entityType.getTypeName(); + if (name.equals(MATCH_ALL_ENTITY_TYPES.getTypeName())) { + name = ALL_ENTITY_TYPES; + } + throw new AtlasBaseException(AtlasErrorCode.UNKNOWN_ATTRIBUTE, attributeName, name); + } + } + + class JsonLogicConverter { + private static final String PROPERTY_KEY_TYPENAME = "typeName"; + private static final String PROPERTY_KEY_INCLUDESUBTYPES = "includeSubTypes"; + private static final String PROPERTY_KEY_CONDITION = "condition"; + private static final String PROPERTY_KEY_CRITERION = "criterion"; + private static final String PROPERTY_KEY_ATTRIBUTENAME = "attributeName"; + private static final String PROPERTY_KEY_ATTRIBUTEVALUE = "attributeValue"; + private static final String PROPERTY_KEY_OPERATOR = "operator"; + private final Gson gson = new Gson(); + + private JsonLogicConverter() { + } + + public String convertToJsonLogic(String ruleExprJson) { + JsonArray ruleExprArray = gson.fromJson(ruleExprJson, JsonArray.class); + StringBuilder finalResult = new StringBuilder(); + String beginExpr = "{\"" + AtlasRule.Condition.OR.name().toLowerCase() + "\": ["; + String endExpr = "]}"; + + if (ruleExprArray.size() > 1) { + finalResult.append(beginExpr); + } + + for (JsonElement ruleExprObj : ruleExprArray) { + String ruleExprObjJLStr = convertRuleExprObj((JsonObject) ruleExprObj); + finalResult.append(ruleExprObjJLStr).append(","); + } + finalResult.deleteCharAt(finalResult.length() - 1); + if (ruleExprArray.size() > 1) { + finalResult.append(endExpr); + } + return finalResult.toString(); + } + + private String convertRuleExprObj(JsonObject ruleExprObj) { + String op = extractStringProperty(ruleExprObj, PROPERTY_KEY_CONDITION, PROPERTY_KEY_OPERATOR); + String typeNameValue = extractStringProperty(ruleExprObj, PROPERTY_KEY_TYPENAME); + boolean includeSubTypes = extractBooleanProperty(ruleExprObj, PROPERTY_KEY_INCLUDESUBTYPES); + + if (op == null && !ALL_ENTITY_TYPES.equals(typeNameValue)) { + return getMatchingTypeNameCondition(typeNameValue, includeSubTypes); + } + + String typeNameConditionStr = null; + String conditionRes = null; + String criteriaRes = null; + + if (typeNameValue != null && !ALL_ENTITY_TYPES.equals(typeNameValue)) { + typeNameConditionStr = getMatchingTypeNameCondition(typeNameValue, includeSubTypes); + } + + if ("and".equalsIgnoreCase(op) || "or".equalsIgnoreCase(op)) { + JsonArray criterion = ruleExprObj.getAsJsonArray(PROPERTY_KEY_CRITERION); + String result = convertCriterionArray(criterion); + conditionRes = "{\"" + op.toLowerCase() + "\": [" + result + "]}"; + } else { + criteriaRes = formatSingleCriterion(ruleExprObj); + } + + if (typeNameConditionStr != null) { + if (conditionRes != null) { + return "{\"and\": [" + typeNameConditionStr + ", " + conditionRes + "]}"; + } else { + return "{\"and\": [" + typeNameConditionStr + ", " + criteriaRes + "]}"; + } + } else if (conditionRes != null) { + return conditionRes; + } else { + return criteriaRes; + } + } + + private String extractStringProperty(JsonObject jsonObject, String... propertyKeys) { + for (String propertyKey : propertyKeys) { + if (jsonObject.has(propertyKey)) { + return jsonObject.get(propertyKey).getAsString(); + } + } + return null; + } + + private boolean extractBooleanProperty(JsonObject jsonObject, String propertyKey) { + if (jsonObject.has(propertyKey)) { + return jsonObject.get(propertyKey).getAsBoolean(); + } + return true; + } + + private String convertCriterionArray(JsonArray criterion) { + StringBuilder result = new StringBuilder(); + for (JsonElement criteria : criterion) { + JsonObject subCondition = criteria.getAsJsonObject(); + String subResult = convertRuleExprObj(subCondition); + result.append(subResult).append(","); + } + if (result.length() > 0) { + result.deleteCharAt(result.length() - 1); + } + return result.toString(); + } + + private String formatSingleCriterion(JsonObject ruleExprObj) { + String op = ruleExprObj.get(PROPERTY_KEY_OPERATOR).getAsString(); + String attrName = formatAttrName(ruleExprObj.get(PROPERTY_KEY_ATTRIBUTENAME).getAsString()); + String criteriaRes = "{\"" + op + "\": [{\"var\":\"" + attrName + "\"}"; + if (ruleExprObj.has(PROPERTY_KEY_ATTRIBUTEVALUE)) { + String attrVal = ruleExprObj.get(PROPERTY_KEY_ATTRIBUTEVALUE).getAsString(); + criteriaRes += "," + attrVal; + } + criteriaRes += "]}"; + return criteriaRes; + } + + private String getMatchingTypeNameCondition(String typeNameValue, boolean includeSubTypes) { + Set entityTypeNames = getEntityTypeNames(typeNameValue, includeSubTypes); + + if (entityTypeNames.size() == 1) { + return "{\"contains\" : [{\"var\":\"" + PROPERTY_KEY_TYPENAME + "\"}, \"" + entityTypeNames.stream().findAny().get() + "\"]}"; + } + + StringBuilder typeNameConditionStr = new StringBuilder(" {\"in\":[{\"var\":\"" + PROPERTY_KEY_TYPENAME + "\"}, ["); + + for (String typeName : entityTypeNames) { + typeNameConditionStr.append("\"").append(typeName).append("\"").append(","); + } + typeNameConditionStr.deleteCharAt(typeNameConditionStr.length() - 1); + typeNameConditionStr.append("] ]}"); + + return typeNameConditionStr.toString(); + } + + private String formatAttrName(String attributeName) { + if (attributeName.startsWith(INTERNAL_PROPERTY_KEY_PREFIX)) { + attributeName = attributeName.substring(2); + if (ATTRIBUTE_NAME_STATE.equals(attributeName)) { + attributeName = KEY_STATUS; + } + } else if (!externalAttributes.contains(attributeName)) { + attributeName = "attributes." + attributeName; + } + return attributeName; + } + } +} diff --git a/repository/src/main/java/org/apache/atlas/rulesengine/AtlasRuleUtils.java b/repository/src/main/java/org/apache/atlas/rulesengine/AtlasRuleUtils.java new file mode 100644 index 00000000000..809992cc68a --- /dev/null +++ b/repository/src/main/java/org/apache/atlas/rulesengine/AtlasRuleUtils.java @@ -0,0 +1,134 @@ + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.atlas.rulesengine; + +import org.apache.atlas.model.instance.AtlasRule; +import org.apache.commons.collections.CollectionUtils; +import org.json.simple.JSONArray; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class AtlasRuleUtils { + public static final Comparator ruleSorter = (rule1, rule2) -> rule1.getCreatedTime().compareTo(rule2.getCreatedTime()); + static final Function isNullFunc = (objarr) -> (objarr[0] == null); + static final Function notNullFunc = (objarr) -> (objarr[0] != null); + static final Function startsWithFunc = (objarr) -> ((String) objarr[0]).startsWith((String) objarr[1]); + static final Function endsWithFunc = (objarr) -> ((String) objarr[0]).endsWith((String) objarr[1]); + static final BiPredicate containsBiPredicate = AtlasRuleUtils::match; + static final BiPredicate notContainsBiPredicate = containsBiPredicate.negate(); + // Negating the containsBiPredicate to check if both strings do not match + static final Function notContainsFunc = (arr) -> (notContainsBiPredicate.test((String) arr[1], (String) arr[0])); + static final Function notContainsIgnoreCaseFunc = (arr) -> (notContainsBiPredicate.test(((String) arr[1]).toLowerCase(), ((String) arr[0]).toLowerCase())); + //checks if two given strings match. The chkStr(first parameter) may contain wildcard characters + static final Function containsFunc = (arr) -> (containsBiPredicate.test((String) arr[1], (String) arr[0])); + static final Function containsIgnoreCaseFunc = (arr) -> (containsBiPredicate.test(((String) arr[1]).toLowerCase(), ((String) arr[0]).toLowerCase())); + static final RuleAction ACCEPT = new RuleAction() { + public RuleAction.Result performAction(Object dataObj) { + return getResult(); + } + + public Result getResult() { + return Result.ACCEPT; + } + }; + static final RuleAction DISCARD = new RuleAction() { + public RuleAction.Result performAction(Object dataObj) { + return getResult(); + } + + public Result getResult() { + return Result.DISCARD; + } + }; + + private AtlasRuleUtils() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static String getRuleExprJsonString(AtlasRule.RuleExpr ruleExpr) { + List exprList = ruleExpr.getRuleExprObjList(); + return JSONArray.toJSONString(exprList); + } + + public static RuleAction getRuleActionFromString(String action) { + boolean isAccept = RuleAction.Result.valueOf(action) == RuleAction.Result.ACCEPT; + return (isAccept ? ACCEPT : DISCARD); + } + + public static List getSortedRules(Collection ruleList) { + List sortedRules = ruleList.stream() + .sorted(AtlasRuleUtils.ruleSorter) + .collect(Collectors.toList()); + + return Collections.unmodifiableList(sortedRules); + } + + public static boolean isDuplicateList(List> allLists, List listToCheck) { + for (List list : allLists) { + if (CollectionUtils.isEqualCollection(list, listToCheck)) { + return true; + } + } + return false; + } + + static boolean match(String strWithWildcard, String strTarget) { + // If we reach at the end of both strings, we are done + if (strWithWildcard.length() == 0 && strTarget.length() == 0) { + return true; + } + + //if no wildcard character, simply check contains substring + if (!strWithWildcard.contains("*")) { + return strTarget.contains(strWithWildcard); + } + + // Make sure to eliminate consecutive '*' + if (strWithWildcard.length() > 1 && strWithWildcard.charAt(0) == '*') { + int i = 0; + while (i + 1 < strWithWildcard.length() && strWithWildcard.charAt(i + 1) == '*') { + i++; + } + strWithWildcard = strWithWildcard.substring(i); + } + + // Make sure that the characters after '*' are present in second string. This function assumes that the strWithWildcard will not contain two consecutive '*' + if (strWithWildcard.length() > 1 && strWithWildcard.charAt(0) == '*' && strTarget.length() == 0) { + return false; + } + + // If the first string contains '?', or current characters of both strings match + if ((strWithWildcard.length() > 1 && strWithWildcard.charAt(0) == '?') || (strWithWildcard.length() != 0 && strTarget.length() != 0 && strWithWildcard.charAt(0) == strTarget.charAt(0))) { + return match(strWithWildcard.substring(1), strTarget.substring(1)); + } + // If there is *, then there are two possibilities + // a) We consider current character of second string + // b) We ignore current character of second string. + if (strWithWildcard.length() > 0 && strWithWildcard.charAt(0) == '*') { + return match(strWithWildcard.substring(1), strTarget) || + match(strWithWildcard, strTarget.substring(1)); + } + return false; + } +} diff --git a/repository/src/main/java/org/apache/atlas/rulesengine/AtlasRulesEngine.java b/repository/src/main/java/org/apache/atlas/rulesengine/AtlasRulesEngine.java new file mode 100644 index 00000000000..670a51fef8a --- /dev/null +++ b/repository/src/main/java/org/apache/atlas/rulesengine/AtlasRulesEngine.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.atlas.rulesengine; + +import io.github.jamsesso.jsonlogic.JsonLogic; +import io.github.jamsesso.jsonlogic.JsonLogicException; +import org.apache.atlas.exception.AtlasBaseException; +import org.apache.atlas.model.instance.AtlasRule; +import org.apache.commons.collections.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; + +import static org.apache.atlas.rulesengine.RuleAction.Result.ACCEPT; +import static org.apache.atlas.rulesengine.RuleAction.Result.DISCARD; + +public class AtlasRulesEngine { + private static final Logger LOG = LoggerFactory.getLogger(AtlasRulesEngine.class); + final JsonLogic jsonLogic = new JsonLogic(); + private final RuleAction defaultAction; + private final AtlasEntityAuditFilterService auditFilterService; + + public AtlasRulesEngine(AtlasEntityAuditFilterService auditFilterService) { + this.auditFilterService = auditFilterService; + this.defaultAction = auditFilterService.getDefaultAction(); + registerOperations(); + } + + public boolean accept(Map entityObj) throws AtlasBaseException { + RuleAction.Result result = ACCEPT; + if (defaultAction != null) { + result = defaultAction.getResult(); + } + LOG.debug("DEFAULT rule action : {}", result); + final List rules = auditFilterService.fetchRules(); + if (CollectionUtils.isEmpty(rules)) { + LOG.debug("No rules defined, DEFAULT action to {} will be taken", result); + } + + for (AtlasRule rule : rules) { + try { + String jsonLogicExpr = auditFilterService.getJsonLogicConverter().convertToJsonLogic(AtlasRuleUtils.getRuleExprJsonString(rule.getRuleExpr())); + boolean isMatched = evaluate(jsonLogicExpr, entityObj); + + LOG.debug("entityObj : {}", entityObj); + LOG.debug("JLexpr : {}", jsonLogicExpr); + LOG.debug("isMatched : {}", isMatched); + + if (isMatched && rule.getAction() != null) { + result = AtlasRuleUtils.getRuleActionFromString(rule.getAction()).performAction(entityObj); + LOG.debug("Matching rule found {} for entityObj{} with action {}", rule.getRuleExpr(), entityObj, rule.getAction()); + + if (result.equals(ACCEPT)) { + continue; + } + if (result.equals(DISCARD)) { + break; + } + } + } catch (Exception e) { + LOG.error("Error applying rule {} to event. Proceeding to evaluate other rules", rule, e); + } + } + + return result == ACCEPT; + } + + private void registerOperations() { + // Register custom operations + jsonLogic.addOperation("isNull", AtlasRuleUtils.isNullFunc); + jsonLogic.addOperation("notNull", AtlasRuleUtils.notNullFunc); + jsonLogic.addOperation("startsWith", AtlasRuleUtils.startsWithFunc); + jsonLogic.addOperation("endsWith", AtlasRuleUtils.endsWithFunc); + + //checks if two given strings match. The chkStr(first parameter) may contain wildcard characters + jsonLogic.addOperation("contains", AtlasRuleUtils.containsFunc); + + // Negating the containsBiPredicate to check if both strings do not match + jsonLogic.addOperation("notContains", AtlasRuleUtils.notContainsFunc); + + jsonLogic.addOperation("containsIgnoreCase", AtlasRuleUtils.containsIgnoreCaseFunc); + jsonLogic.addOperation("notContainsIgnoreCase", AtlasRuleUtils.notContainsIgnoreCaseFunc); + } + + private boolean evaluate(String jsonLogicRuleExpr, Object dataObj) throws JsonLogicException { + return (boolean) jsonLogic.apply(jsonLogicRuleExpr, dataObj); + } +} diff --git a/repository/src/main/java/org/apache/atlas/rulesengine/RuleAction.java b/repository/src/main/java/org/apache/atlas/rulesengine/RuleAction.java new file mode 100644 index 00000000000..2f675fb7249 --- /dev/null +++ b/repository/src/main/java/org/apache/atlas/rulesengine/RuleAction.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.atlas.rulesengine; + +public interface RuleAction { + Result performAction(Object var1); + + Result getResult(); + + enum Result { + ACCEPT, + DISCARD, + DEFAULT; + + Result() { + } + } +} diff --git a/server-api/pom.xml b/server-api/pom.xml index 67db728011f..b6b292c2292 100644 --- a/server-api/pom.xml +++ b/server-api/pom.xml @@ -46,6 +46,10 @@ org.apache.hadoop hadoop-common + + com.google.code.gson + gson + javax.servlet servlet-api diff --git a/test-tools/src/main/resources/solr/core-template/solrconfig.xml b/test-tools/src/main/resources/solr/core-template/solrconfig.xml index c7ee388c7b8..9d89a105616 100644 --- a/test-tools/src/main/resources/solr/core-template/solrconfig.xml +++ b/test-tools/src/main/resources/solr/core-template/solrconfig.xml @@ -445,7 +445,7 @@ --> edismax - 35x_t 5j9_t 7wl_t a9x_t but_t dfp_l f0l_t i6d_l iyt_l jr9_t kjp_s lc5_t m4l_s mx1_t ohx_t xz9_i 1151_t 12px_t 14at_l 15vp_t 1891_t 19tx_t 1bet_t 1czp_t 1ekl_t 1gxx_t 1iit_l 1k3p_t 1lol_t 1o1x_t 1qf9_t 1ssl_t 1v5x_t 1wqt_t 1z45_t 20p1_t 4ttx_t 56h1_s 54w5_s 52it_s 50xx_s 543p_t 5c05_t 581x_t 59mt_l 5af9_t 5gqt_t 5f5x_t 5ibp_t 5pfp_t 5uyt_t 5zph_t 5xc5_t 5y4l_t 5wjp_t 6611_t 658l_t 6d51_l 6epx_l 66th_t 6ccl_t 6net_l 6ozp_l 6ltx_t 6k91_t 6qkl_t 6gat_t 6h39_t 6io5_t 70ud_t 71mt_l 6xol_t 6uit_l 6w3p_l 6rd1_t 6z9h_t 7e9x_t 7f2d_t 7dhh_t 7bwl_t 737p_t 7abp_t 7b45_t 7cp1_t 78qt_t a3gl_t 9vk5_t a51h_t a139_t a491_t a2o5_t aeit_t a6md_t agw5_t ac5h_t adqd_t ag3p_t aqdh_t aih1_t ao05_t apl1_t f3lx_t ewhx_t f18l_t f2th_t eznp_t eux1_l ey2t_t f0g5_t evph_l f56t_l f9xh_t fapx_t f4ed_l f5z9_t f6rp_i f951_t fdvp_t fd39_t feo5_t fg91_i fk79_t fls5_t fpqd_t fqit_i fo5h_t fs3p_t frb9_t fwud_t fz7p_t fuh1_t fxmt_t fw1x_t fsw5_l fyf9_t ftol_l ho1x_l hkw5_t hmh1_l hs05_l hssl_t hvyd_i hybp_t hudh_t j091_t jc3p_l jdol_i j6kl_t jeh1_i jf9h_i jg1x_f jmdh_d jll1_l kirp_t koat_l krgl_t kumd_l kw79_t kt1h_t kxs5_l kzd1_t + 35x_t 5j9_t 7wl_t a9x_t but_t dfp_l f0l_t i6d_l iyt_l jr9_t kjp_s lc5_t m4l_s mx1_t ohx_t xz9_i 1151_t 12px_t 14at_l 15vp_t 1891_t 19tx_t 1bet_t 1czp_t 1ekl_t 1gxx_t 1iit_l 1k3p_t 1lol_t 1o1x_t 1qf9_t 1ssl_t 1v5x_t 1wqt_t 1z45_t 20p1_t 4zd1_t 5c05_s 5af9_s 581x_s 56h1_s 59mt_t 5hj9_t 5dl1_t 5f5x_l 5fyd_t 5m9x_t 5kp1_t 5nut_t 5uyt_t 60hx_t 658l_t 62v9_t 63np_t 622t_t 6bk5_t 6arp_t 6io5_l 6k91_l 6ccl_t 6hvp_t 6sxx_l 6uit_l 6rd1_t 6ps5_t 6w3p_t 6ltx_t 6mmd_t 6o79_t 76dh_t 775x_l 737p_t 701x_l 71mt_l 6ww5_t 74sl_t 7gn9_t 7hfp_t 7j0l_t 78qt_t 7klh_t 7fut_t 7i85_t 7jt1_t 7e9x_t 7yth_l 7ldx_t 7rph_t 7y11_l 7tad_t 7qx1_t an7p_t afb9_t aosl_t akud_t ao05_t amf9_t ay9x_t aqdh_t b0n9_t avwl_t axhh_t azut_t ba4l_t b285_t b7r9_t b9c5_t fnd1_t fg91_t fkzp_t fmkl_t fjet_t feo5_l fhtx_t fk79_t ffgl_l foxx_l ftol_t fuh1_t fo5h_l fpqd_t fqit_i fsw5_t fxmt_t fwud_t fyf9_t g005_i g3yd_t g5j9_t g9hh_t ga9x_i g7wl_t gbut_t gb2d_t gglh_t giyt_t ge85_t ghdx_t gft1_t gcn9_l gi6d_t gdfp_l i7t1_l i4n9_t i685_l ibr9_l icjp_t ifph_i ii2t_t ie4l_t jk05_t jvut_l jxfp_i jqbp_t jy85_i jz0l_i jzt1_f k64l_d k5c5_l * true true diff --git a/webapp/src/main/java/org/apache/atlas/web/filters/ActiveServerFilter.java b/webapp/src/main/java/org/apache/atlas/web/filters/ActiveServerFilter.java index 04acbfc8f99..fd8aac1bf72 100644 --- a/webapp/src/main/java/org/apache/atlas/web/filters/ActiveServerFilter.java +++ b/webapp/src/main/java/org/apache/atlas/web/filters/ActiveServerFilter.java @@ -55,7 +55,7 @@ public class ActiveServerFilter implements Filter { private final String[] adminUriNotFiltered = {"/admin/export", "/admin/import", "/admin/importfile", "/admin/audits", "/admin/purge", "/admin/expimp/audit", "/admin/metrics", "/admin/server", "/admin/audit/", "admin/tasks", - "/admin/debug/metrics", "/admin/audits/ageout", "admin/async/import", "admin/async/import/status"}; + "/admin/debug/metrics", "/admin/audits/ageout", "admin/async/import", "admin/async/import/status", "/admin/audits/rules"}; private final ActiveInstanceState activeInstanceState; private final ServiceState serviceState; diff --git a/webapp/src/main/java/org/apache/atlas/web/resources/AdminResource.java b/webapp/src/main/java/org/apache/atlas/web/resources/AdminResource.java index f3128b5ab1a..2f003ad4563 100755 --- a/webapp/src/main/java/org/apache/atlas/web/resources/AdminResource.java +++ b/webapp/src/main/java/org/apache/atlas/web/resources/AdminResource.java @@ -50,6 +50,7 @@ import org.apache.atlas.model.instance.AtlasCheckStateResult; import org.apache.atlas.model.instance.AtlasEntityHeader; import org.apache.atlas.model.instance.AtlasObjectId; +import org.apache.atlas.model.instance.AtlasRule; import org.apache.atlas.model.instance.EntityMutationResponse; import org.apache.atlas.model.metrics.AtlasMetrics; import org.apache.atlas.model.metrics.AtlasMetricsMapToChart; @@ -67,6 +68,7 @@ import org.apache.atlas.repository.impexp.ZipSink; import org.apache.atlas.repository.patches.AtlasPatchManager; import org.apache.atlas.repository.store.graph.AtlasEntityStore; +import org.apache.atlas.rulesengine.AtlasEntityAuditFilterService; import org.apache.atlas.services.MetricsService; import org.apache.atlas.tasks.TaskManagement; import org.apache.atlas.type.AtlasType; @@ -89,6 +91,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.security.core.Authentication; @@ -192,6 +195,7 @@ public class AdminResource { private final boolean isUiTasksTabEnabled; private final AtlasAuditReductionService auditReductionService; private Response version; + private final AtlasEntityAuditFilterService entityAuditFilterService; @Context private HttpServletRequest httpServletRequest; @@ -205,7 +209,7 @@ public AdminResource(ServiceState serviceState, MetricsService metricsService, A MigrationProgressService migrationProgressService, AtlasServerService serverService, ExportImportAuditService exportImportAuditService, AtlasEntityStore entityStore, AtlasPatchManager patchManager, AtlasAuditService auditService, EntityAuditRepository auditRepository, - TaskManagement taskManagement, AtlasDebugMetricsSink debugMetricsRESTSink, AtlasAuditReductionService atlasAuditReductionService, AtlasMetricsUtil atlasMetricsUtil) { + TaskManagement taskManagement, AtlasDebugMetricsSink debugMetricsRESTSink, AtlasAuditReductionService atlasAuditReductionService, AtlasMetricsUtil atlasMetricsUtil, @Lazy AtlasEntityAuditFilterService entityAuditFilterService) { this.serviceState = serviceState; this.metricsService = metricsService; this.exportService = exportService; @@ -224,6 +228,7 @@ public AdminResource(ServiceState serviceState, MetricsService metricsService, A this.debugMetricsRESTSink = debugMetricsRESTSink; this.auditReductionService = atlasAuditReductionService; this.atlasMetricsUtil = atlasMetricsUtil; + this.entityAuditFilterService = entityAuditFilterService; if (atlasProperties != null) { this.defaultUIVersion = atlasProperties.getString(DEFAULT_UI_VERSION, UI_VERSION_V2); @@ -1098,6 +1103,180 @@ public Response serviceReadiness() throws AtlasBaseException { } } + /** + * Create a rule + * + * @param atlasRule rule definition, + * @return + * @throws AtlasBaseException + * @HTTP 200 If rule creation was successful + * @HTTP 400 If rule definition has invalid or missing information + * @HTTP 409 If rule definition already exists (duplicate qualifiedName) + */ + @POST + @Path("/audits/rules") + @Consumes(Servlets.JSON_MEDIA_TYPE) + @Produces(Servlets.JSON_MEDIA_TYPE) + public AtlasRule createRule(AtlasRule atlasRule) throws AtlasBaseException { + AtlasAuthorizationUtils.verifyAccess(new AtlasAdminAccessRequest(AtlasPrivilege.ADMIN_AUDITS), "Admin Entity Audit Custom Filters"); + + if (!AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getBoolean()) { + LOG.warn("AdminResource.createRule() : " + AtlasErrorCode.CUSTOM_AUDIT_FILTERS_NOT_ENABLED.getFormattedErrorMessage(AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getPropertyName())); + throw new AtlasBaseException(AtlasErrorCode.CUSTOM_AUDIT_FILTERS_NOT_ENABLED, AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getPropertyName()); + } + AtlasPerfTracer perf = null; + try { + if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { + perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "AdminResource.createRule()"); + } + return entityAuditFilterService.createRule(atlasRule); + } finally { + AtlasPerfTracer.log(perf); + } + } + + @GET + @Path("/audits/rules") + @Produces(Servlets.JSON_MEDIA_TYPE) + public List getRules() throws AtlasBaseException { + AtlasAuthorizationUtils.verifyAccess(new AtlasAdminAccessRequest(AtlasPrivilege.ADMIN_AUDITS), "Admin Entity Audit Custom Filters"); + + if (!AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getBoolean()) { + LOG.warn("AdminResource.getRules() : " + AtlasErrorCode.CUSTOM_AUDIT_FILTERS_NOT_ENABLED.getFormattedErrorMessage(AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getPropertyName())); + throw new AtlasBaseException(AtlasErrorCode.CUSTOM_AUDIT_FILTERS_NOT_ENABLED, AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getPropertyName()); + } + AtlasPerfTracer perf = null; + try { + if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { + perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "AdminResource.getRules()"); + } + + return entityAuditFilterService.fetchRules(); + } finally { + AtlasPerfTracer.log(perf); + } + } + + /** + * Update the given rule + * + * @param ruleGuid unique identifier for rule + * @param updatedRule Updated rule definition + * @return Rule + * @throws AtlasBaseException + * @HTTP 200 If rule update was successful + * @HTTP 404 If rule guid in invalid + * @HTTP 400 If rule definition has invalid or missing information + */ + @PUT + @Path("/audits/rules/{ruleGuid}") + @Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON}) + @Produces(Servlets.JSON_MEDIA_TYPE) + public AtlasRule updateRule(@PathParam("ruleGuid") String ruleGuid, AtlasRule updatedRule) throws AtlasBaseException { + AtlasAuthorizationUtils.verifyAccess(new AtlasAdminAccessRequest(AtlasPrivilege.ADMIN_AUDITS), "Admin Entity Audit Custom Filters"); + + if (!AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getBoolean()) { + LOG.warn("AdminResource.updateRule() : " + AtlasErrorCode.CUSTOM_AUDIT_FILTERS_NOT_ENABLED.getFormattedErrorMessage(AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getPropertyName())); + throw new AtlasBaseException(AtlasErrorCode.CUSTOM_AUDIT_FILTERS_NOT_ENABLED, AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getPropertyName()); + } + Servlets.validateQueryParamLength("ruleGuid", ruleGuid); + AtlasPerfTracer perf = null; + try { + if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { + perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "AdminResource.updateRule(" + ruleGuid + ")"); + } + updatedRule.setGuid(ruleGuid); + return entityAuditFilterService.updateRule(updatedRule); + } finally { + AtlasPerfTracer.log(perf); + } + } + + /** + * Delete a rule + * + * @param ruleGuid unique identifier for rule + * @throws AtlasBaseException + * @HTTP 204 If rule delete was successful + * @HTTP 404 If rule guid in invalid + */ + @DELETE + @Path("/audits/rules/guid/{ruleGuid}") + @Consumes(Servlets.JSON_MEDIA_TYPE) + @Produces(Servlets.JSON_MEDIA_TYPE) + public EntityMutationResponse deleteRule(@PathParam("ruleGuid") String ruleGuid) throws AtlasBaseException { + AtlasAuthorizationUtils.verifyAccess(new AtlasAdminAccessRequest(AtlasPrivilege.ADMIN_AUDITS), "Admin Entity Audit Custom Filters"); + + if (!AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getBoolean()) { + LOG.warn("AdminResource.deleteRule() : " + AtlasErrorCode.CUSTOM_AUDIT_FILTERS_NOT_ENABLED.getFormattedErrorMessage(AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getPropertyName())); + throw new AtlasBaseException(AtlasErrorCode.CUSTOM_AUDIT_FILTERS_NOT_ENABLED, AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getPropertyName()); + } + Servlets.validateQueryParamLength("ruleGuid", ruleGuid); + AtlasPerfTracer perf = null; + try { + if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { + perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "AdminResource.deleteRule(" + ruleGuid + ")"); + } + + return entityAuditFilterService.deleteRule(ruleGuid); + } finally { + AtlasPerfTracer.log(perf); + } + } + + @DELETE + @Path("/audits/rules") + @Consumes(Servlets.JSON_MEDIA_TYPE) + @Produces(Servlets.JSON_MEDIA_TYPE) + public EntityMutationResponse deleteRules(List ruleGuids) throws AtlasBaseException { + AtlasAuthorizationUtils.verifyAccess(new AtlasAdminAccessRequest(AtlasPrivilege.ADMIN_AUDITS), "Admin Entity Audit Custom Filters"); + + if (!AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getBoolean()) { + LOG.warn("AdminResource.deleteRules() : " + AtlasErrorCode.CUSTOM_AUDIT_FILTERS_NOT_ENABLED.getFormattedErrorMessage(AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getPropertyName())); + throw new AtlasBaseException(AtlasErrorCode.CUSTOM_AUDIT_FILTERS_NOT_ENABLED, AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getPropertyName()); + } + + if (CollectionUtils.isNotEmpty(ruleGuids)) { + for (String ruleGuid : ruleGuids) { + Servlets.validateQueryParamLength("ruleGuids", ruleGuid); + } + } + + AtlasPerfTracer perf = null; + try { + if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { + perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "AdminResource.deleteRules(" + ruleGuids + ")"); + } + + return entityAuditFilterService.deleteRules(ruleGuids); + } finally { + AtlasPerfTracer.log(perf); + } + } + + @DELETE + @Path("/audits/rules/all") + @Produces(Servlets.JSON_MEDIA_TYPE) + public EntityMutationResponse deleteAllRules() throws AtlasBaseException { + AtlasAuthorizationUtils.verifyAccess(new AtlasAdminAccessRequest(AtlasPrivilege.ADMIN_AUDITS), "Admin Entity Audit Custom Filters"); + + if (!AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getBoolean()) { + LOG.warn("AdminResource.deleteAllRules() : " + AtlasErrorCode.CUSTOM_AUDIT_FILTERS_NOT_ENABLED.getFormattedErrorMessage(AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getPropertyName())); + throw new AtlasBaseException(AtlasErrorCode.CUSTOM_AUDIT_FILTERS_NOT_ENABLED, AtlasConfiguration.ENTITY_AUDIT_FILTER_ENABLED.getPropertyName()); + } + + AtlasPerfTracer perf = null; + try { + if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { + perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "AdminResource.deleteAllRules()"); + } + + return entityAuditFilterService.deleteAllRules(); + } finally { + AtlasPerfTracer.log(perf); + } + } + private void updateCriteriaWithDefaultValues(AuditReductionCriteria auditReductionCriteria) { if (auditReductionCriteria.getDefaultAgeoutTTLInDays() <= 0) { auditReductionCriteria.setDefaultAgeoutTTLInDays(AtlasConfiguration.ATLAS_AUDIT_DEFAULT_AGEOUT_TTL.getInt()); diff --git a/webapp/src/test/java/org/apache/atlas/web/filters/CAFTests.java b/webapp/src/test/java/org/apache/atlas/web/filters/CAFTests.java new file mode 100644 index 00000000000..2716e927eb7 --- /dev/null +++ b/webapp/src/test/java/org/apache/atlas/web/filters/CAFTests.java @@ -0,0 +1,386 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.atlas.web.filters; + +import org.apache.atlas.ApplicationProperties; +import org.apache.atlas.AtlasClientV2; +import org.apache.atlas.AtlasServiceException; +import org.apache.atlas.TestModules; +import org.apache.atlas.model.audit.EntityAuditEventV2; +import org.apache.atlas.model.instance.AtlasClassification; +import org.apache.atlas.model.instance.AtlasEntity; +import org.apache.atlas.model.instance.AtlasRule; +import org.apache.atlas.model.instance.EntityMutationResponse; +import org.apache.atlas.model.instance.EntityMutations; +import org.apache.atlas.model.typedef.AtlasClassificationDef; +import org.apache.atlas.model.typedef.AtlasTypesDef; +import org.apache.atlas.repository.graph.AtlasGraphProvider; +import org.apache.atlas.type.AtlasTypeUtil; +import org.apache.atlas.utils.AuthenticationUtil; +import org.apache.commons.lang.RandomStringUtils; +import org.javatuples.Triplet; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.CLASSIFICATION_ADD; +import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.CLASSIFICATION_DELETE; +import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.ENTITY_CREATE; +import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.ENTITY_DELETE; +import static org.apache.atlas.model.audit.EntityAuditEventV2.EntityAuditActionV2.ENTITY_UPDATE; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; +import static org.testng.AssertJUnit.assertEquals; + +@Guice(modules = TestModules.TestOnlyModule.class) +public class CAFTests { + public static final String ATLAS_REST_ADDRESS = "atlas.rest.address"; + static final Triplet CRITERION_NAME_CONTAINS_TEMP = Triplet.with("name", AtlasRule.RuleExprObject.Operator.CONTAINS_IGNORECASE, "temp"); + static final Triplet CRITERION_NAME_STARTS_WITH_TEST = Triplet.with("name", AtlasRule.RuleExprObject.Operator.STARTS_WITH, "test"); + static final Triplet CRITERION_NAME_STARTS_WITH_DEMO = Triplet.with("name", AtlasRule.RuleExprObject.Operator.CONTAINS, "demo"); + static final Triplet CRITERION_NAME_ENDS_WITH_TEST = Triplet.with("name", AtlasRule.RuleExprObject.Operator.ENDS_WITH, "test"); + static final Triplet CRITERION_DELETE_CLASSIFICATION = Triplet.with("operationType", AtlasRule.RuleExprObject.Operator.EQ, "CLASSIFICATION_DELETE"); + static final Triplet CRITERION_ENTITY_DELETED = Triplet.with("operationType", AtlasRule.RuleExprObject.Operator.EQ, "ENTITY_DELETE"); + + protected AtlasClientV2 atlasClientV2; + final List allRuleGuids = new ArrayList<>(); + final List allEntityGuids = new ArrayList<>(); + + @BeforeClass + public void setUp() throws Exception { + String[] atlasUrls = ApplicationProperties.get().getStringArray(ATLAS_REST_ADDRESS); + if (atlasUrls == null || atlasUrls.length == 0) { + atlasUrls = new String[] {"http://localhost:21000/"}; + } + + atlasClientV2 = AuthenticationUtil.isKerberosAuthenticationEnabled() + ? new AtlasClientV2(atlasUrls) + : new AtlasClientV2(atlasUrls, new String[] {"admin", "admin"}); + + setupRules(); + } + + private void setupRules() { + try { + // Rule 1: Discard all audits for hive_table,hive_db if name contains "temp"(Note support for CSV of type-names) + createRule("rule_1", "hive_table,hive_db", false, CRITERION_NAME_CONTAINS_TEMP); + + // Rule 2: Discard audits for spark_table if name starts or ends with "test" + createRule("rule_2", "spark_table", false, AtlasRule.Condition.OR, CRITERION_NAME_STARTS_WITH_TEST, CRITERION_NAME_ENDS_WITH_TEST); + + // Rule 3: Discard audits for hive* (wildcard support) on classification delete or entity delete + createRule("rule_3", "hive*", false, AtlasRule.Condition.OR, CRITERION_ENTITY_DELETED, CRITERION_DELETE_CLASSIFICATION); + + // Rule 4: Discard audits for Asset and subtypes where name starts with "demo" + createRule("rule_4", "Asset", true, CRITERION_NAME_STARTS_WITH_DEMO); + + // Rule 5: Discard all audits by default for spark_process - all operations/events + createRule("rule_5", "spark_process", false); + } catch (AtlasServiceException e) { + fail("Failed to set up rules: " + e.getMessage(), e); + } + } + + @SafeVarargs + private final void createRule(String ruleName, + String typeName, + boolean includeSubtypes, + Triplet... criteria) throws AtlasServiceException { + createRule(ruleName, typeName, includeSubtypes, null, criteria); + } + + @SafeVarargs + private final void createRule(String ruleName, + String typeName, + boolean includeSubtypes, + AtlasRule.Condition condition, + Triplet... criteria) throws AtlasServiceException { + if (condition != null && (criteria == null || criteria.length == 0)) { + throw new IllegalArgumentException("Criteria must not be null or empty"); + } + + List ruleExprObjects = new ArrayList<>(); + + if (condition != null && criteria.length > 1) { + ruleExprObjects.add(getNestedRuleExprObject(typeName, includeSubtypes, condition, Arrays.asList(criteria))); + } else { + for (Triplet criterion : criteria) { + ruleExprObjects.add(getSimpleRuleExprObject(typeName, includeSubtypes, criterion)); + } + } + + AtlasRule.RuleExpr ruleExpr = new AtlasRule.RuleExpr(ruleExprObjects); + + AtlasRule atlasRule = new AtlasRule(); + atlasRule.setAction("DISCARD"); + atlasRule.setRuleName(ruleName); + atlasRule.setRuleExpr(ruleExpr); + + AtlasRule createdRule = atlasClientV2.createRule(atlasRule); + saveRuleGuid(createdRule.getRuleName()); + } + + private void createRule(String ruleName, String typeName, boolean includeSubtypes) throws AtlasServiceException { + // Creates a basic rule with no filtering logic (no conditions and no criteria) + AtlasRule.RuleExprObject ruleExprObject = getSimpleRuleExprObject(typeName, includeSubtypes); + AtlasRule.RuleExpr ruleExpr = new AtlasRule.RuleExpr(Collections.singletonList(ruleExprObject)); + + AtlasRule atlasRule = new AtlasRule(); + atlasRule.setAction("DISCARD"); + atlasRule.setRuleName(ruleName); + atlasRule.setRuleExpr(ruleExpr); + + AtlasRule rule = atlasClientV2.createRule(atlasRule); + saveRuleGuid(rule.getRuleName()); + } + + private AtlasRule.RuleExprObject getSimpleRuleExprObject(String typeName, boolean includeSubTypes) { + return new AtlasRule.RuleExprObject(typeName, includeSubTypes); + } + + private AtlasRule.RuleExprObject getSimpleRuleExprObject(String typeName, boolean includeSubTypes, String attributeName, AtlasRule.RuleExprObject.Operator operator, String attributeValue) { + return new AtlasRule.RuleExprObject(typeName, attributeName, operator, attributeValue, includeSubTypes); + } + + private AtlasRule.RuleExprObject getSimpleRuleExprObject(String typeName, boolean includeSubTypes, Triplet criterionTriplet) { + return getSimpleRuleExprObject(typeName, includeSubTypes, criterionTriplet.getValue0(), criterionTriplet.getValue1(), criterionTriplet.getValue2()); + } + + private AtlasRule.RuleExprObject getNestedRuleExprObject(String typeName, boolean includeSubTypes, AtlasRule.Condition condition, List> criterion) { + return new AtlasRule.RuleExprObject(typeName, condition, transform(criterion), includeSubTypes); + } + + private List transform(List> criterion) { + return criterion.stream().map(criteriatriplet -> new AtlasRule.RuleExprObject.Criterion(criteriatriplet.getValue1(), criteriatriplet.getValue0(), criteriatriplet.getValue2())).collect(Collectors.toList()); + } + + private void saveRuleGuid(String ruleName) throws AtlasServiceException { + Map attributes = Collections.singletonMap("ruleName", ruleName); + AtlasEntity ruleEntity = atlasClientV2.getEntityByAttribute("__AtlasRule", attributes).getEntity(); + allRuleGuids.add(ruleEntity.getGuid()); + } + + private AtlasEntity createEntity(String typeName, String entityName) throws AtlasServiceException { + Map attrMap = new HashMap<>(); + if (typeName.equals("hdfs_path")) { + attrMap.put("path", entityName + "_path"); + } else if (typeName.equals("hive_db")) { + attrMap.put("clusterName", "cl1"); + } + return createEntity(typeName, entityName, attrMap); + } + + private AtlasEntity createEntity(String typeName, String name, Map additionalAttrs) throws AtlasServiceException { + AtlasEntity entity = new AtlasEntity(typeName); + entity.setAttribute("name", name); + entity.setAttribute("qualifiedName", "q" + name + randomString()); + additionalAttrs.forEach(entity::setAttribute); + + EntityMutationResponse response = atlasClientV2.createEntity(new AtlasEntity.AtlasEntityWithExtInfo(entity)); + String guid = response.getEntitiesByOperation(EntityMutations.EntityOperation.CREATE).get(0).getGuid(); + entity.setGuid(guid); + allEntityGuids.add(guid); + return entity; + } + + protected String randomString() { + return RandomStringUtils.randomAlphabetic(1) + RandomStringUtils.randomAlphanumeric(9); + } + + private void updateEntityName(AtlasEntity entity, String newName) throws AtlasServiceException { + entity.setAttribute("name", newName); + atlasClientV2.updateEntity(new AtlasEntity.AtlasEntityWithExtInfo(entity)); + } + + private String createClassification(String guid, String classificationName) throws AtlasServiceException { + String clName = classificationName + randomString(); + AtlasClassificationDef clDef = AtlasTypeUtil.createTraitTypeDef(clName, Collections.emptySet()); + atlasClientV2.createAtlasTypeDefs(new AtlasTypesDef(Collections.emptyList(), Collections.emptyList(), Collections.singletonList(clDef), Collections.emptyList())); + atlasClientV2.addClassifications(guid, Collections.singletonList(new AtlasClassification(clDef.getName()))); + return clName; + } + + public void removeEntityClassification(AtlasEntity entity, String classificationName) throws AtlasServiceException { + Map attrMap = new HashMap() { + { + put("qualifiedName", (String) entity.getAttribute("qualifiedName")); + } + }; + atlasClientV2.removeClassification(entity.getTypeName(), attrMap, classificationName); + } + + private void assertEntityAudits(String guid, int expectedCount) throws AtlasServiceException { + assertEntityAudits(guid, null, expectedCount); + } + + private void assertEntityAudits(String guid, EntityAuditEventV2.EntityAuditActionV2 action, int expectedCount) throws AtlasServiceException { + List events = atlasClientV2.getAuditEvents(guid, "", action, (short) 100); + assertEquals("Unexpected audit count for: " + guid, expectedCount, events.size()); + } + + @DataProvider(name = "nameContainsTempEntitiesProvider") + public Object[][] nameContainsTempEntitiesProvider() { + return new Object[][] { + // {typeName, name, attributes, expectedAuditCount} + {"hive_table", "temp_table", 0}, + {"hive_db", "db01_temp", 0}, + {"hdfs_path", "temp_path", 1} + }; + } + + @Test(dataProvider = "nameContainsTempEntitiesProvider") + public void test_DiscardAuditsIfNameContainsTemp(String typeName, String name, int expectedAuditCount) throws AtlasServiceException { + AtlasEntity entity = createEntity(typeName, name); + assertEntityAudits(entity.getGuid(), expectedAuditCount); + + // Only check update audit for types that had ENTITY_CREATE discarded (to ensure rule still applies) + if (expectedAuditCount == 0) { + updateEntityName(entity, entity.getAttribute("name") + randomString()); + assertEntityAudits(entity.getGuid(), ENTITY_UPDATE, 0); + } + } + + @DataProvider(name = "nameUpdateScenariosForAuditFiltering") + public Object[][] nameUpdateScenariosForAuditFiltering() { + return new Object[][] { + {"test_", "", 0}, // starts with test; Should be discarded + {"prefix_notest_", "_notest_suffix", 1}, // neither starts nor ends with test; Should be accepted + {"", "_midname_test", 0} // ends with test; Should be discarded + }; + } + + @Test(dataProvider = "nameUpdateScenariosForAuditFiltering") + public void test_DiscardUpdateIfNameStartsOrEndsWithTest(String newNamePrefix, String newNameSuffix, int expectedUpdateAudits) throws Exception { + AtlasEntity entity = createEntity("spark_table", "sptable"); + assertEntityAudits(entity.getGuid(), 1); // One CREATE audit + + updateEntityName(entity, newNamePrefix + entity.getAttribute("name") + newNameSuffix); + assertEntityAudits(entity.getGuid(), ENTITY_UPDATE, expectedUpdateAudits); + + // Check total audit events (CREATE + expected updates) + assertEntityAudits(entity.getGuid(), 1 + expectedUpdateAudits); + } + + @Test + public void test_DiscardSpecificAuditsForAllTypesUnderHiveHook() throws AtlasServiceException { + //Discard audits when entity is updated + AtlasEntity dbEntity = createEntity("hive_db", "db01"); + assertEntityAudits(dbEntity.getGuid(), 1); + + EntityMutationResponse deleteResponse = atlasClientV2.deleteEntitiesByGuids(Collections.singletonList(dbEntity.getGuid())); + allEntityGuids.remove(dbEntity.getGuid()); + assertNotNull(deleteResponse.getEntitiesByOperation(EntityMutations.EntityOperation.DELETE)); + assertTrue(deleteResponse.getEntitiesByOperation(EntityMutations.EntityOperation.DELETE).size() > 0); + assertEntityAudits(dbEntity.getGuid(), ENTITY_DELETE, 0); + + //Discard audits when classification is removed + AtlasEntity tblEntity = createEntity("hive_table", "tbl01"); + assertEntityAudits(tblEntity.getGuid(), 1); + + String clName = createClassification(tblEntity.getGuid(), "class01"); + assertEntityAudits(tblEntity.getGuid(), CLASSIFICATION_ADD, 1); + + try { + Thread.sleep(1000); //hits error "ATLAS-406-00-001" otherwise + } catch (InterruptedException ignored) { + } + + removeEntityClassification(tblEntity, clName); + assertEntityAudits(tblEntity.getGuid(), CLASSIFICATION_DELETE, 0); + } + + @Test + public void test_DiscardAuditsForTypeAndSubtypes() throws AtlasServiceException { + //Discard all entity audits for type Asset and its subtypes + AtlasEntity tblEntity = createEntity("hive_table", "DEMO_table"); + assertEntityAudits(tblEntity.getGuid(), ENTITY_CREATE, 1); + + updateEntityName(tblEntity, "demo_table"); + assertEntityAudits(tblEntity.getGuid(), ENTITY_UPDATE, 0); + + AtlasEntity processEntity = createEntity("Process", "demo_process"); + assertEntityAudits(processEntity.getGuid(), ENTITY_CREATE, 0); + } + + @Test + public void test_DefaultDiscardRuleApplied() throws AtlasServiceException { + //Discards all audits by default for spark_process + AtlasEntity entity = createEntity("spark_process", "sp_name"); + assertEntityAudits(entity.getGuid(), ENTITY_CREATE, 0); + + updateEntityName(entity, "sp_name_new"); + assertEntityAudits(entity.getGuid(), ENTITY_UPDATE, 0); + + createClassification(entity.getGuid(), "class02"); + assertEntityAudits(entity.getGuid(), CLASSIFICATION_ADD, 0); + } + + @DataProvider(name = "unmatchedRuleEntitiesProvider") + public Object[][] unmatchedRuleEntitiesProvider() { + return new Object[][] { + {"hive_table", "unmatched_table"}, + {"hive_db", "unmatched_db"}, + {"hdfs_path", "unmatched_hdfs"}, + {"spark_table", "uniqueSparkTable"} + }; + } + + @Test(dataProvider = "unmatchedRuleEntitiesProvider") + public void test_UnmatchedRuleAuditNotDiscarded(String typeName, String namePrefix) throws AtlasServiceException { + //verifies the default behavior of the audit filter logic. + String entityName = namePrefix + "_" + randomString(); + Map attrMap = new HashMap<>(); + if (typeName.equals("hdfs_path")) { + attrMap.put("path", entityName + "_path"); + } else if (typeName.equals("hive_db")) { + attrMap.put("clusterName", "cl1"); + } + + AtlasEntity entity = createEntity(typeName, entityName, attrMap); + assertNotNull(entity); + + // ENTITY_CREATE audit is expected to be generated + assertEntityAudits(entity.getGuid(), ENTITY_CREATE, 1); + } + + @AfterClass + public void teardown() throws Exception { + for (String guid : allRuleGuids) { + EntityMutationResponse resp = atlasClientV2.deleteRuleByGuid(guid); + assertEquals(1, resp.getDeletedEntities().size()); + } + + if (!allEntityGuids.isEmpty()) { + EntityMutationResponse deleteResponse = atlasClientV2.deleteEntitiesByGuids(allEntityGuids); + assertEquals(deleteResponse.getEntitiesByOperation(EntityMutations.EntityOperation.DELETE).size(), allEntityGuids.size()); + } + + AtlasGraphProvider.cleanup(); + } +} diff --git a/webapp/src/test/java/org/apache/atlas/web/resources/AdminResourceTest.java b/webapp/src/test/java/org/apache/atlas/web/resources/AdminResourceTest.java index f7e2c7ef73c..aac34993e8b 100644 --- a/webapp/src/test/java/org/apache/atlas/web/resources/AdminResourceTest.java +++ b/webapp/src/test/java/org/apache/atlas/web/resources/AdminResourceTest.java @@ -48,7 +48,7 @@ public void setup() { public void testStatusOfActiveServerIsReturned() throws IOException { when(serviceState.getState()).thenReturn(ServiceState.ServiceStateValue.ACTIVE); - AdminResource adminResource = new AdminResource(serviceState, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + AdminResource adminResource = new AdminResource(serviceState, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); Response response = adminResource.getStatus(); assertEquals(response.getStatus(), HttpServletResponse.SC_OK); @@ -62,7 +62,7 @@ public void testStatusOfActiveServerIsReturned() throws IOException { public void testResourceGetsValueFromServiceState() throws IOException { when(serviceState.getState()).thenReturn(ServiceState.ServiceStateValue.PASSIVE); - AdminResource adminResource = new AdminResource(serviceState, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + AdminResource adminResource = new AdminResource(serviceState, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); Response response = adminResource.getStatus(); verify(serviceState).getState(); diff --git a/webapp/src/test/resources/atlas-application.properties b/webapp/src/test/resources/atlas-application.properties index b5c707ed10f..39b905ac5b6 100644 --- a/webapp/src/test/resources/atlas-application.properties +++ b/webapp/src/test/resources/atlas-application.properties @@ -135,3 +135,5 @@ atlas.debug.metrics.enabled=true ######### Configure on-demand lineage ######### atlas.lineage.on.demand.enabled=true +atlas.entity.audit.filter.enabled=true +atlas.entity.audit.filter.default.action=ACCEPT