RANGER-459 Additional validations for service-def Signed-off-by: Madhan Neethiraj <[email protected]>
Project: http://git-wip-us.apache.org/repos/asf/incubator-ranger/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ranger/commit/7ac04f9d Tree: http://git-wip-us.apache.org/repos/asf/incubator-ranger/tree/7ac04f9d Diff: http://git-wip-us.apache.org/repos/asf/incubator-ranger/diff/7ac04f9d Branch: refs/heads/master Commit: 7ac04f9d3389aa71b6b0ce6804abe6e8d527ca5e Parents: cdcb1c0 Author: Alok Lal <[email protected]> Authored: Thu May 7 11:23:47 2015 -0700 Committer: Madhan Neethiraj <[email protected]> Committed: Fri May 8 12:36:28 2015 -0700 ---------------------------------------------------------------------- .../model/validation/RangerPolicyValidator.java | 9 +- .../validation/RangerServiceDefHelper.java | 43 +- .../validation/RangerServiceDefValidator.java | 444 +++++++++++++------ .../validation/RangerServiceValidator.java | 9 +- .../model/validation/RangerValidator.java | 150 +++++++ .../ranger/plugin/util/RangerObjectFactory.java | 10 + .../service-defs/ranger-servicedef-kafka.json | 12 + .../service-defs/ranger-servicedef-kms.json | 12 + .../validation/TestRangerPolicyValidator.java | 15 +- .../validation/TestRangerServiceDefHelper.java | 109 ++++- .../TestRangerServiceDefValidator.java | 358 ++++++++++----- .../validation/TestRangerServiceValidator.java | 10 +- .../model/validation/TestRangerValidator.java | 71 +++ .../model/validation/ValidationTestUtils.java | 118 ++++- .../src/test/resources/log4j.properties | 2 +- 15 files changed, 1075 insertions(+), 297 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerPolicyValidator.java ---------------------------------------------------------------------- diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerPolicyValidator.java b/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerPolicyValidator.java index 7f0318f..1acf81f 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerPolicyValidator.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerPolicyValidator.java @@ -90,12 +90,9 @@ public class RangerPolicyValidator extends RangerValidator { .build()); valid = false; } else if (getPolicy(id) == null) { - failures.add(new ValidationFailureDetailsBuilder() - .field("id") - .isSemanticallyIncorrect() - .becauseOf("no policy found for id[" + id + "]") - .build()); - valid = false; + if (LOG.isDebugEnabled()) { + LOG.debug("No policy found for id[" + id + "]! ok!"); + } } if(LOG.isDebugEnabled()) { http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefHelper.java ---------------------------------------------------------------------- diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefHelper.java b/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefHelper.java index 6381dfe..91ff16a 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefHelper.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefHelper.java @@ -44,6 +44,15 @@ public class RangerServiceDefHelper { final Delegate _delegate; public RangerServiceDefHelper(RangerServiceDef serviceDef) { + this(serviceDef, true); + } + + /** + * Intended for use when serviceDef object is not-trusted, e.g. when service-def is being created or updated. + * @param serviceDef + * @param useCache + */ + public RangerServiceDefHelper(RangerServiceDef serviceDef, boolean useCache) { // NOTE: we assume serviceDef, its name and update time are can never by null. if(LOG.isDebugEnabled()) { @@ -54,7 +63,7 @@ public class RangerServiceDefHelper { Date serviceDefFreshnessDate = serviceDef.getUpdateTime(); Delegate delegate = null; - if (_Cache.containsKey(serviceName)) { + if (useCache && _Cache.containsKey(serviceName)) { LOG.debug("RangerServiceDefHelper(): found delegate in cache with matching serviceName. Need to check date"); Delegate that = _Cache.get(serviceName); if (Objects.equals(that.getServiceFreshnessDate(), serviceDefFreshnessDate)) { @@ -66,8 +75,10 @@ public class RangerServiceDefHelper { } if (delegate == null) { // either not found in cache or date didn't match delegate = new Delegate(serviceDef); - _Cache.put(serviceName, delegate); - LOG.debug("RangerServiceDefHelper(): Created new delegate and put in delegate cache!"); + if (useCache) { + LOG.debug("RangerServiceDefHelper(): Created new delegate and put in delegate cache!"); + _Cache.put(serviceName, delegate); + } } _delegate = delegate; } @@ -106,13 +117,17 @@ public class RangerServiceDefHelper { return result; } - public Set<String> getAllResourceNames(List<RangerResourceDef> hierarchy) { - Set<String> result = new HashSet<String>(hierarchy.size()); + public List<String> getAllResourceNames(List<RangerResourceDef> hierarchy) { + List<String> result = new ArrayList<String>(hierarchy.size()); for (RangerResourceDef resourceDef : hierarchy) { result.add(resourceDef.getName()); } return result; } + + public boolean isResourceGraphValid() { + return _delegate.isResourceGraphValid(); + } /** * Not designed for public access. Package level only for testability. @@ -123,6 +138,7 @@ public class RangerServiceDefHelper { final Date _serviceDefFreshnessDate; final String _serviceName; final Map<String, RangerResourceDef> _resourceMap; + final boolean _valid; public Delegate(RangerServiceDef serviceDef) { @@ -135,7 +151,8 @@ public class RangerServiceDefHelper { _resourceMap = Collections.unmodifiableMap(getResourcesAsMap(resourceDefs)); DirectedGraph graph = createGraph(resourceDefs); - if (isValid(graph)) { + _valid = isValid(graph); + if (_valid) { Set<List<String>> hierarchies = getHierarchies(graph); _hierarchies = Collections.unmodifiableSet(convertHierarchies(hierarchies, _resourceMap)); } else { @@ -163,6 +180,10 @@ public class RangerServiceDefHelper { public Date getServiceFreshnessDate() { return _serviceDefFreshnessDate; } + + public boolean isResourceGraphValid() { + return _valid; + } /** * Builds a directed graph where each resource is node and arc goes from parent level to child level * @@ -186,12 +207,12 @@ public class RangerServiceDefHelper { } /** - * A minimally valid resource graph has - at least one sink AND - and least one source. - * - * A more rigorous definition would require all of the following: - exactly one source (assuming this is required) - At least one sink - no cycles - all non-source nodes have an in-degree of - * exactly 1 - all non-sink nodes have an out-degree of 1 or more (if more than one source is allowed then this will changed) + * A valid resource graph is a forest, i.e. a disjoint union of trees. In our case, given that node can have only one "parent" node, we can detect this validity simply by ensuring that + * the resource graph has: + * - at least one sink AND + * - and least one source. * - * Anyhow, we don't need such a rigorous definition at this time, hence a more complete validity function is deferred till we need one! + * A more direct method would have been ensure that the resulting graph does not have any cycles. * * @param graph * http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefValidator.java ---------------------------------------------------------------------- diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefValidator.java b/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefValidator.java index 0ef7ff9..c57e5fc 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefValidator.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefValidator.java @@ -23,6 +23,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Set; import org.apache.commons.collections.CollectionUtils; @@ -33,8 +35,12 @@ import org.apache.ranger.plugin.model.RangerServiceDef; import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef; import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumDef; import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumElementDef; +import org.apache.ranger.plugin.model.RangerServiceDef.RangerPolicyConditionDef; +import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef; +import org.apache.ranger.plugin.model.RangerServiceDef.RangerServiceConfigDef; import org.apache.ranger.plugin.store.ServiceStore; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; public class RangerServiceDefValidator extends RangerValidator { @@ -84,12 +90,9 @@ public class RangerServiceDefValidator extends RangerValidator { .build()); valid = false; } else if (getServiceDef(id) == null) { - failures.add(new ValidationFailureDetailsBuilder() - .field("id") - .isSemanticallyIncorrect() - .becauseOf("no service def found for id[" + id + "]") - .build()); - valid = false; + if (LOG.isDebugEnabled()) { + LOG.debug("No service found for id[" + id + "]! ok!"); + } } if(LOG.isDebugEnabled()) { @@ -118,78 +121,103 @@ public class RangerServiceDefValidator extends RangerValidator { valid = false; } else { Long id = serviceDef.getId(); - if (action == Action.UPDATE) { // id is ignored for CREATE - if (id == null) { - String message = "service def id was null/empty/blank"; - LOG.debug(message); - failures.add(new ValidationFailureDetailsBuilder() - .field("id") - .isMissing() - .becauseOf(message) - .build()); - valid = false; - } else if (getServiceDef(id) == null) { - failures.add(new ValidationFailureDetailsBuilder() - .field("id") - .isSemanticallyIncorrect() - .becauseOf("no service def exists with id[" + id +"]") - .build()); - valid = false; - } + valid = isValidServiceDefId(id, action, failures) && valid; + valid = isValidServiceDefName(serviceDef.getName(), id, action, failures) && valid; + valid = isValidAccessTypes(serviceDef.getAccessTypes(), failures) && valid; + if (isValidResources(serviceDef, failures)) { + // Semantic check of resource graph can only be done if resources are "syntactically" valid + valid = isValidResourceGraph(serviceDef, failures) && valid; + } else { + valid = false; } - // validate the service def name - String name = serviceDef.getName(); - boolean nameSpecified = StringUtils.isNotBlank(name); - if (!nameSpecified) { - String message = "service def name[" + name + "] was null/empty/blank"; + List<RangerEnumDef> enumDefs = serviceDef.getEnums(); + if (isValidEnums(enumDefs, failures)) { + // config def validation requires valid enums + valid = isValidConfigs(serviceDef.getConfigs(), enumDefs, failures) && valid; + } else { + valid = false; + } + valid = isValidPolicyConditions(serviceDef.getPolicyConditions(), failures) && valid; + } + + if(LOG.isDebugEnabled()) { + LOG.debug("<== RangerServiceDefValidator.isValid(" + serviceDef + "): " + valid); + } + return valid; + } + + boolean isValidServiceDefId(Long id, final Action action, final List<ValidationFailureDetails> failures) { + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValidServiceDefId(%s, %s, %s)", id, action, failures)); + } + boolean valid = true; + + if (action == Action.UPDATE) { // id is ignored for CREATE + if (id == null) { + String message = "service def id was null/empty/blank"; LOG.debug(message); failures.add(new ValidationFailureDetailsBuilder() - .field("name") + .field("id") .isMissing() .becauseOf(message) .build()); valid = false; - } else { - RangerServiceDef otherServiceDef = getServiceDef(name); - if (otherServiceDef != null && action == Action.CREATE) { - failures.add(new ValidationFailureDetailsBuilder() - .field("name") - .isSemanticallyIncorrect() - .becauseOf("service def with the name[" + name + "] already exists") - .build()); - valid = false; - } else if (otherServiceDef != null && otherServiceDef.getId() !=null && otherServiceDef.getId() != id) { - failures.add(new ValidationFailureDetailsBuilder() - .field("id/name") - .isSemanticallyIncorrect() - .becauseOf("id/name conflict: another service def already exists with name[" + name + "], its id is [" + otherServiceDef.getId() + "]") - .build()); - valid = false; - } - } - if (CollectionUtils.isEmpty(serviceDef.getAccessTypes())) { + } else if (getServiceDef(id) == null) { failures.add(new ValidationFailureDetailsBuilder() - .field("access types") - .isMissing() - .becauseOf("access types collection was null/empty") + .field("id") + .isSemanticallyIncorrect() + .becauseOf("no service def exists with id[" + id +"]") .build()); valid = false; - } else { - valid = isValidAccessTypes(serviceDef.getAccessTypes(), failures) && valid; - } - if (CollectionUtils.isEmpty(serviceDef.getEnums())) { - LOG.debug("No enums specified on the service def. Ok!"); - } else { - valid = isValidEnums(serviceDef.getEnums(), failures) && valid; } } - + if(LOG.isDebugEnabled()) { - LOG.debug("<== RangerServiceDefValidator.isValid(" + serviceDef + "): " + valid); + LOG.debug(String.format("<== RangerServiceDefValidator.isValidServiceDefId(%s, %s, %s): %s", id, action, failures, valid)); } return valid; } + + boolean isValidServiceDefName(String name, Long id, final Action action, final List<ValidationFailureDetails> failures) { + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValidServiceDefName(%s, %s, %s, %s)", name, id, action, failures)); + } + boolean valid = true; + + if (StringUtils.isBlank(name)) { + String message = "service def name[" + name + "] was null/empty/blank"; + LOG.debug(message); + failures.add(new ValidationFailureDetailsBuilder() + .field("name") + .isMissing() + .becauseOf(message) + .build()); + valid = false; + } else { + RangerServiceDef otherServiceDef = getServiceDef(name); + if (otherServiceDef != null && action == Action.CREATE) { + failures.add(new ValidationFailureDetailsBuilder() + .field("name") + .isSemanticallyIncorrect() + .becauseOf("service def with the name[" + name + "] already exists") + .build()); + valid = false; + } else if (otherServiceDef != null && !Objects.equals(id, otherServiceDef.getId())) { + failures.add(new ValidationFailureDetailsBuilder() + .field("id/name") + .isSemanticallyIncorrect() + .becauseOf("id/name conflict: another service def already exists with name[" + name + "], its id is [" + otherServiceDef.getId() + "]") + .build()); + valid = false; + } + } + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("<== RangerServiceDefValidator.isValidServiceDefName(%s, %s, %s, %s): %s", name, id, action, failures, valid)); + } + return valid; + } + boolean isValidAccessTypes(final List<RangerAccessTypeDef> accessTypeDefs, final List<ValidationFailureDetails> failures) { if(LOG.isDebugEnabled()) { LOG.debug(String.format("==> RangerServiceDefValidator.isValidAccessTypes(%s, %s)", accessTypeDefs, failures)); @@ -197,31 +225,20 @@ public class RangerServiceDefValidator extends RangerValidator { boolean valid = true; if (CollectionUtils.isEmpty(accessTypeDefs)) { - LOG.debug("access type def collection is empty/null"); + failures.add(new ValidationFailureDetailsBuilder() + .field("access types") + .isMissing() + .becauseOf("access types collection was null/empty") + .build()); + valid = false; } else { List<RangerAccessTypeDef> defsWithImpliedGrants = new ArrayList<RangerAccessTypeDef>(); Set<String> accessNames = new HashSet<String>(); + Set<Long> ids = new HashSet<Long>(); for (RangerAccessTypeDef def : accessTypeDefs) { String name = def.getName(); - // name can't be null/empty/blank - if (StringUtils.isBlank(name)) { - failures.add(new ValidationFailureDetailsBuilder() - .field("access type name") - .isMissing() - .becauseOf("access type name[" + name + "] is null/empty") - .build()); - valid = false; - } else if (accessNames.contains(name.toLowerCase())) { - failures.add(new ValidationFailureDetailsBuilder() - .field("access type name") - .subField(name) - .isSemanticallyIncorrect() - .becauseOf("duplicate access type names in access types collection: [" + name + "]") - .build()); - valid = false; - } else { - accessNames.add(name.toLowerCase()); // we have a new unique access type - } + valid = isUnique(name, accessNames, "access type name", "access types", failures) && valid; + valid = isUnique(def.getId(), ids, "access type id", "access types", failures) && valid; if (CollectionUtils.isNotEmpty(def.getImpliedGrants())) { defsWithImpliedGrants.add(def); } @@ -259,6 +276,215 @@ public class RangerServiceDefValidator extends RangerValidator { return valid; } + boolean isValidPolicyConditions(List<RangerPolicyConditionDef> policyConditions, List<ValidationFailureDetails> failures) { + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValidPolicyConditions(%s, %s)", policyConditions, failures)); + } + boolean valid = true; + + if (CollectionUtils.isEmpty(policyConditions)) { + LOG.debug("Configs collection was null/empty! ok"); + } else { + Set<Long> ids = new HashSet<Long>(); + Set<String> names = new HashSet<String>(); + for (RangerPolicyConditionDef conditionDef : policyConditions) { + valid = isUnique(conditionDef.getId(), ids, "policy condition def id", "policy condition defs", failures) && valid; + String name = conditionDef.getName(); + valid = isUnique(name, names, "policy condition def name", "policy condition defs", failures) && valid; + if (StringUtils.isBlank(conditionDef.getEvaluator())) { + String reason = String.format("evaluator on policy condition definition[%s] was null/empty!", name); + LOG.debug(reason); + failures.add(new ValidationFailureDetailsBuilder() + .field("policy condition def evaluator") + .subField(name) + .isMissing() + .becauseOf(reason) + .build()); + valid = false; + } + } + } + + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("<== RangerServiceDefValidator.isValidPolicyConditions(%s, %s): %s", policyConditions, failures, valid)); + } + return valid; + } + + boolean isValidConfigs(List<RangerServiceConfigDef> configs, List<RangerEnumDef> enumDefs, List<ValidationFailureDetails> failures) { + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValidConfigs(%s, %s, %s)", configs, enumDefs, failures)); + } + boolean valid = true; + + if (CollectionUtils.isEmpty(configs)) { + LOG.debug("Configs collection was null/empty! ok"); + } else { + Set<Long> ids = new HashSet<Long>(configs.size()); + Set<String> names = new HashSet<String>(configs.size()); + for (RangerServiceConfigDef aConfig : configs) { + valid = isUnique(aConfig.getId(), ids, "config def id", "config defs", failures) && valid; + String configName = aConfig.getName(); + valid = isUnique(configName, names, "config def name", "config defs", failures) && valid; + String type = aConfig.getType(); + valid = isValidConfigType(type, configName, failures) && valid; + if ("enum".equals(type)) { + valid = isValidConfigOfEnumType(aConfig, enumDefs, failures) && valid; + } + } + } + + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("<== RangerServiceDefValidator.isValidConfigs(%s, %s, %s): %s", configs, enumDefs, failures, valid)); + } + return valid; + } + + boolean isValidConfigOfEnumType(RangerServiceConfigDef configDef, List<RangerEnumDef> enumDefs, List<ValidationFailureDetails> failures) { + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValidConfigOfEnumType(%s, %s, %s)", configDef, enumDefs, failures)); + } + boolean valid = true; + + if (!"enum".equals(configDef.getType())) { + LOG.debug("ConfigDef wasn't of enum type!"); + } else { + Map<String, RangerEnumDef> enumDefsMap = getEnumDefMap(enumDefs); + Set<String> enumTypes = enumDefsMap.keySet(); + String subType = configDef.getSubType(); + String configName = configDef.getName(); + + if (!enumTypes.contains(subType)) { + String reason = String.format("subtype[%s] of service def config[%s] was not among defined enums[%s]", subType, configName, enumTypes); + failures.add(new ValidationFailureDetailsBuilder() + .field("config def subtype") + .subField(configName) + .isSemanticallyIncorrect() + .becauseOf(reason) + .build()); + valid = false; + } else { + // default value check is possible only if sub-type is correctly configured + String defaultValue = configDef.getDefaultValue(); + if (StringUtils.isNotBlank(defaultValue)) { + RangerEnumDef enumDef = enumDefsMap.get(subType); + Set<String> enumValues = getEnumValues(enumDef); + if (!enumValues.contains(defaultValue)) { + String reason = String.format("default value[%s] of service def config[%s] was not among the valid values[%s] of enums[%s]", defaultValue, configName, enumValues, subType); + failures.add(new ValidationFailureDetailsBuilder() + .field("config def default value") + .subField(configName) + .isSemanticallyIncorrect() + .becauseOf(reason) + .build()); + valid = false; + } + } + } + } + + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("<== RangerServiceDefValidator.isValidConfigOfEnumType(%s, %s, %s): %s", configDef, enumDefs, failures, valid)); + } + return valid; + } + + boolean isValidConfigType(String type, String configName, List<ValidationFailureDetails> failures) { + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValidConfigType(%s, %s, %s)", type, configName, failures)); + } + boolean valid = true; + + Set<String> validTypes = ImmutableSet.of("bool", "enum", "int", "string", "password", "path"); + if (StringUtils.isBlank(type)) { + String reason = String.format("type of service def config[%s] was null/empty", configName); + failures.add(new ValidationFailureDetailsBuilder() + .field("config def type") + .subField(configName) + .isMissing() + .becauseOf(reason) + .build()); + valid = false; + } else if (!validTypes.contains(type)) { + String reason = String.format("type[%s] of service def config[%s] is not among valid types: %s", type, configName, validTypes); + failures.add(new ValidationFailureDetailsBuilder() + .field("config def type") + .subField(configName) + .isSemanticallyIncorrect() + .becauseOf(reason) + .build()); + valid = false; + } + + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("<== RangerServiceDefValidator.isValidConfigType(%s, %s, %s): %s", type, configName, failures, valid)); + } + return valid; + } + + boolean isValidResources(RangerServiceDef serviceDef, List<ValidationFailureDetails> failures) { + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValidResources(%s, %s)", serviceDef, failures)); + } + boolean valid = true; + + List<RangerResourceDef> resources = serviceDef.getResources(); + if (CollectionUtils.isEmpty(resources)) { + String reason = "service def resources collection was null/empty"; + failures.add(new ValidationFailureDetailsBuilder() + .field("resources") + .isMissing() + .becauseOf(reason) + .build()); + valid = false; + } else { + Set<String> names = new HashSet<String>(resources.size()); + Set<Long> ids = new HashSet<Long>(resources.size()); + for (RangerResourceDef resource : resources) { + /* + * While id is the natural key, name is a surrogate key. At several places code expects resource name to be unique within a service. + */ + valid = isUnique(resource.getName(), names, "resource name", "resources", failures) && valid; + valid = isUnique(resource.getId(), ids, "resource id", "resources", failures) && valid; + } + } + + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("<== RangerServiceDefValidator.isValidResources(%s, %s): %s", serviceDef, failures, valid)); + } + return valid; + } + + boolean isValidResourceGraph(RangerServiceDef serviceDef, List<ValidationFailureDetails> failures) { + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValidResourceGraph(%s, %s)", serviceDef, failures)); + } + boolean valid = true; + // We don't want this helper to get into the cache or to use what is in the cache!! + RangerServiceDefHelper defHelper = _factory.createServiceDefHelper(serviceDef, false); + if (!defHelper.isResourceGraphValid()) { + failures.add(new ValidationFailureDetailsBuilder() + .field("resource graph") + .isSemanticallyIncorrect() + .becauseOf("Resource graph implied by various resources, e.g. parent value is invalid. Valid graph must forest (union of disjoint trees).") + .build()); + valid = false; + } + // resource level should be unique within a hierarchy + Set<List<RangerResourceDef>> hierarchies = defHelper.getResourceHierarchies(); + for (List<RangerResourceDef> aHierarchy : hierarchies) { + Set<Integer> levels = new HashSet<Integer>(aHierarchy.size()); + for (RangerResourceDef resourceDef : aHierarchy) { + valid = isUnique(resourceDef.getLevel(), levels, "resource level", "resources", failures) && valid; + } + } + + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("<== RangerServiceDefValidator.isValidResourceGraph(%s, %s): %s", serviceDef, failures, valid)); + } + return valid; + } + boolean isValidEnums(List<RangerEnumDef> enumDefs, List<ValidationFailureDetails> failures) { if(LOG.isDebugEnabled()) { LOG.debug(String.format("==> RangerServiceDefValidator.isValidEnums(%s, %s)", enumDefs, failures)); @@ -266,9 +492,10 @@ public class RangerServiceDefValidator extends RangerValidator { boolean valid = true; if (CollectionUtils.isEmpty(enumDefs)) { - LOG.debug("enum def collection passed in was null/empty"); + LOG.debug("enum def collection passed in was null/empty. Ok."); } else { - Set<String> enumNames = new HashSet<String>(); + Set<String> names = new HashSet<String>(); + Set<Long> ids = new HashSet<Long>(); for (RangerEnumDef enumDef : enumDefs) { if (enumDef == null) { failures.add(new ValidationFailureDetailsBuilder() @@ -278,26 +505,10 @@ public class RangerServiceDefValidator extends RangerValidator { .build()); valid = false; } else { - // enum-names must non-blank and be unique to a service definition - String enumName = enumDef.getName(); - if (StringUtils.isBlank(enumName)) { - failures.add(new ValidationFailureDetailsBuilder() - .field("enum def name") - .isMissing() - .becauseOf("enum name [" + enumName + "] is null/empty") - .build()); - valid = false; - } else if (enumNames.contains(enumName.toLowerCase())) { - failures.add(new ValidationFailureDetailsBuilder() - .field("enum def name") - .subField(enumName) - .isSemanticallyIncorrect() - .becauseOf("dumplicate enum name [" + enumName + "] found") - .build()); - valid = false; - } else { - enumNames.add(enumName.toLowerCase()); - } + // enum-names and ids must non-blank and be unique to a service definition + String enumName = enumDef.getName(); + valid = isUnique(enumName, names, "enum def name", "enum defs", failures) && valid; + valid = isUnique(enumDef.getId(), ids, "enum def id", "enum defs", failures) && valid; // enum must contain at least one valid value and those values should be non-blank and distinct if (CollectionUtils.isEmpty(enumDef.getElements())) { failures.add(new ValidationFailureDetailsBuilder() @@ -333,7 +544,7 @@ public class RangerServiceDefValidator extends RangerValidator { boolean isValidEnumElements(List<RangerEnumElementDef> enumElementsDefs, List<ValidationFailureDetails> failures, String enumName) { if(LOG.isDebugEnabled()) { - LOG.debug(String.format("==> RangerServiceDefValidator.isValidEnums(%s, %s)", enumElementsDefs, failures)); + LOG.debug(String.format("==> RangerServiceDefValidator.isValidEnumElements(%s, %s)", enumElementsDefs, failures)); } boolean valid = true; @@ -342,6 +553,7 @@ public class RangerServiceDefValidator extends RangerValidator { } else { // enum element names should be valid and distinct Set<String> elementNames = new HashSet<String>(); + Set<Long> ids = new HashSet<Long>(); for (RangerEnumElementDef elementDef : enumElementsDefs) { if (elementDef == null) { failures.add(new ValidationFailureDetailsBuilder() @@ -352,32 +564,14 @@ public class RangerServiceDefValidator extends RangerValidator { .build()); valid = false; } else { - String elementName = elementDef.getName(); - if (StringUtils.isBlank(elementName)) { - failures.add(new ValidationFailureDetailsBuilder() - .field("enum element name") - .subField(enumName) - .isMissing() - .becauseOf("Name of an element of enum [" + enumName + "] is null/empty[" + elementName + "]") - .build()); - valid = false; - } else if (elementNames.contains(elementName.toLowerCase())) { - failures.add(new ValidationFailureDetailsBuilder() - .field("enum element name") - .subField(enumName) - .isSemanticallyIncorrect() - .becauseOf("dumplicate enum element name [" + elementName + "] found for enum[" + enumName + "]") - .build()); - valid = false; - } else { - elementNames.add(elementName.toLowerCase()); - } + valid = isUnique(elementDef.getName(), enumName, elementNames, "enum element name", "enum elements", failures) && valid; + valid = isUnique(elementDef.getId(), enumName, ids, "enum element id", "enum elements", failures) && valid; } } } if(LOG.isDebugEnabled()) { - LOG.debug(String.format("<== RangerServiceDefValidator.isValidEnums(%s, %s): %s", enumElementsDefs, failures, valid)); + LOG.debug(String.format("<== RangerServiceDefValidator.isValidEnumElements(%s, %s): %s", enumElementsDefs, failures, valid)); } return valid; } http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceValidator.java ---------------------------------------------------------------------- diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceValidator.java b/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceValidator.java index 2019284..659249e 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceValidator.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceValidator.java @@ -79,12 +79,9 @@ public class RangerServiceValidator extends RangerValidator { .build()); valid = false; } else if (getService(id) == null) { - failures.add(new ValidationFailureDetailsBuilder() - .field("id") - .isSemanticallyIncorrect() - .becauseOf("no service found for id[" + id + "]") - .build()); - valid = false; + if (LOG.isDebugEnabled()) { + LOG.debug("No service found for id[" + id + "]! ok!"); + } } if(LOG.isDebugEnabled()) { http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerValidator.java ---------------------------------------------------------------------- diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerValidator.java b/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerValidator.java index bc4c7f1..ec16eee 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerValidator.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerValidator.java @@ -39,6 +39,7 @@ import org.apache.ranger.plugin.model.RangerService; import org.apache.ranger.plugin.model.RangerServiceDef; import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef; import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumDef; +import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumElementDef; import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef; import org.apache.ranger.plugin.model.RangerServiceDef.RangerServiceConfigDef; import org.apache.ranger.plugin.store.ServiceStore; @@ -501,4 +502,153 @@ public abstract class RangerValidator { return output; } + boolean isUnique(Long value, Set<Long> alreadySeen, String valueName, String collectionName, List<ValidationFailureDetails> failures) { + return isUnique(value, null, alreadySeen, valueName, collectionName, failures); + } + + /** + * NOTE: <code>alreadySeen</code> collection passed in gets updated. + * @param value + * @param alreadySeen + * @param valueName - use-friendly name of the <code>value</code> that would be used when generating failure message + * @param collectionName - use-friendly name of the <code>value</code> collection that would be used when generating failure message + * @param failures + * @return + */ + boolean isUnique(Long value, String valueContext, Set<Long> alreadySeen, String valueName, String collectionName, List<ValidationFailureDetails> failures) { + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValueUnique(%s, %s, %s, %s, %s)", value, alreadySeen, valueName, collectionName, failures)); + } + boolean valid = true; + + if (value == null) { // null/empty/blank value is an error + failures.add(new ValidationFailureDetailsBuilder() + .field(valueName) + .subField(valueContext) + .isMissing() + .becauseOf(String.format("%s[%s] is null/empty", valueName, value)) + .build()); + valid = false; + } else if (alreadySeen.contains(value)) { // it shouldn't have been seen already + failures.add(new ValidationFailureDetailsBuilder() + .field(valueName) + .subField(value.toString()) + .isSemanticallyIncorrect() + .becauseOf(String.format("duplicate %s[%s] in %s", valueName, value, collectionName)) + .build()); + valid = false; + } else { + alreadySeen.add(value); // we have a new unique access type + } + + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValueUnique(%s, %s, %s, %s, %s): %s", value, alreadySeen, valueName, collectionName, failures, valid)); + } + return valid; + } + + /** + * NOTE: <code>alreadySeen</code> collection passed in gets updated. + * @param value + * @param alreadySeen + * @param valueName - use-friendly name of the <code>value</code> that would be used when generating failure message + * @param collectionName - use-friendly name of the <code>value</code> collection that would be used when generating failure message + * @param failures + * @return + */ + boolean isUnique(Integer value, Set<Integer> alreadySeen, String valueName, String collectionName, List<ValidationFailureDetails> failures) { + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValueUnique(%s, %s, %s, %s, %s)", value, alreadySeen, valueName, collectionName, failures)); + } + boolean valid = true; + + if (value == null) { // null/empty/blank value is an error + failures.add(new ValidationFailureDetailsBuilder() + .field(valueName) + .isMissing() + .becauseOf(String.format("%s[%s] is null/empty", valueName, value)) + .build()); + valid = false; + } else if (alreadySeen.contains(value)) { // it shouldn't have been seen already + failures.add(new ValidationFailureDetailsBuilder() + .field(valueName) + .subField(value.toString()) + .isSemanticallyIncorrect() + .becauseOf(String.format("duplicate %s[%s] in %s", valueName, value, collectionName)) + .build()); + valid = false; + } else { + alreadySeen.add(value); // we have a new unique access type + } + + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValueUnique(%s, %s, %s, %s, %s): %s", value, alreadySeen, valueName, collectionName, failures, valid)); + } + return valid; + } + + boolean isUnique(final String value, final Set<String> alreadySeen, final String valueName, final String collectionName, final List<ValidationFailureDetails> failures) { + return isUnique(value, null, alreadySeen, valueName, collectionName, failures); + } + /** + * NOTE: <code>alreadySeen</code> collection passed in gets updated. + * @param value + * @param alreadySeen + * @param valueName - use-friendly name of the <code>value</code> that would be used when generating failure message + * @param collectionName - use-friendly name of the <code>value</code> collection that would be used when generating failure message + * @param failures + * @return + */ + boolean isUnique(final String value, final String valueContext, final Set<String> alreadySeen, final String valueName, final String collectionName, final List<ValidationFailureDetails> failures) { + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValueUnique(%s, %s, %s, %s, %s)", value, alreadySeen, valueName, collectionName, failures)); + } + boolean valid = true; + + if (StringUtils.isBlank(value)) { // null/empty/blank value is an error + failures.add(new ValidationFailureDetailsBuilder() + .field(valueName) + .subField(valueContext) + .isMissing() + .becauseOf(String.format("%s[%s] is null/empty", valueName, value)) + .build()); + valid = false; + } else if (alreadySeen.contains(value.toLowerCase())) { // it shouldn't have been seen already + failures.add(new ValidationFailureDetailsBuilder() + .field(valueName) + .subField(value) + .isSemanticallyIncorrect() + .becauseOf(String.format("duplicate %s[%s] in %s", valueName, value, collectionName)) + .build()); + valid = false; + } else { + alreadySeen.add(value.toLowerCase()); // we have a new unique access type + } + + if(LOG.isDebugEnabled()) { + LOG.debug(String.format("==> RangerServiceDefValidator.isValueUnique(%s, %s, %s, %s, %s): %s", value, alreadySeen, valueName, collectionName, failures, valid)); + } + return valid; + } + + Map<String, RangerEnumDef> getEnumDefMap(List<RangerEnumDef> enumDefs) { + Map<String, RangerEnumDef> result = new HashMap<String, RangerServiceDef.RangerEnumDef>(); + if (enumDefs != null) { + for (RangerEnumDef enumDef : enumDefs) { + result.put(enumDef.getName(), enumDef); + } + } + return result; + } + + Set<String> getEnumValues(RangerEnumDef enumDef) { + Set<String> result = new HashSet<String>(); + if (enumDef != null) { + for (RangerEnumElementDef element : enumDef.getElements()) { + result.add(element.getName()); + } + } + return result; + } + } http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerObjectFactory.java ---------------------------------------------------------------------- diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerObjectFactory.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerObjectFactory.java index 72f0fd5..4a570b6 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerObjectFactory.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerObjectFactory.java @@ -21,9 +21,19 @@ package org.apache.ranger.plugin.util; import org.apache.ranger.plugin.model.RangerPolicy; import org.apache.ranger.plugin.model.RangerPolicyResourceSignature; +import org.apache.ranger.plugin.model.RangerServiceDef; +import org.apache.ranger.plugin.model.validation.RangerServiceDefHelper; public class RangerObjectFactory { public RangerPolicyResourceSignature createPolicyResourceSignature(RangerPolicy policy) { return new RangerPolicyResourceSignature(policy); } + + public RangerServiceDefHelper createServiceDefHelper(RangerServiceDef serviceDef) { + return new RangerServiceDefHelper(serviceDef); + } + + public RangerServiceDefHelper createServiceDefHelper(RangerServiceDef serviceDef, boolean useCache) { + return new RangerServiceDefHelper(serviceDef, useCache); + } } http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/main/resources/service-defs/ranger-servicedef-kafka.json ---------------------------------------------------------------------- diff --git a/agents-common/src/main/resources/service-defs/ranger-servicedef-kafka.json b/agents-common/src/main/resources/service-defs/ranger-servicedef-kafka.json index 8dc29fe..98501a2 100644 --- a/agents-common/src/main/resources/service-defs/ranger-servicedef-kafka.json +++ b/agents-common/src/main/resources/service-defs/ranger-servicedef-kafka.json @@ -28,34 +28,42 @@ ], "accessTypes":[ { + "id": 1, "name":"publish", "label":"Publish" }, { + "id": 2, "name":"consume", "label":"Consume" }, { + "id": 3, "name":"create", "label":"Create" }, { + "id": 4, "name":"delete", "label":"Delete" }, { + "id": 5, "name":"configure", "label":"Configure" }, { + "id": 6, "name":"describe", "label":"Describe" }, { + "id": 7, "name":"replicate", "label":"Replicate" }, { + "id": 8, "name":"connect", "label":"Connect" } @@ -63,18 +71,21 @@ ], "configs":[ { + "id": 1, "name":"username", "type":"string", "mandatory":true, "label":"Username" }, { + "id": 2, "name":"password", "type":"password", "mandatory":true, "label":"Password" }, { + "id": 3, "name":"zookeeper.connect", "type":"string", "mandatory":true, @@ -82,6 +93,7 @@ "label":"Zookeeper Connect String" }, { + "id": 4, "name":"commonNameForCertificate", "type":"string", "mandatory":false, http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/main/resources/service-defs/ranger-servicedef-kms.json ---------------------------------------------------------------------- diff --git a/agents-common/src/main/resources/service-defs/ranger-servicedef-kms.json b/agents-common/src/main/resources/service-defs/ranger-servicedef-kms.json index b1f6d45..260d351 100755 --- a/agents-common/src/main/resources/service-defs/ranger-servicedef-kms.json +++ b/agents-common/src/main/resources/service-defs/ranger-servicedef-kms.json @@ -28,46 +28,55 @@ "accessTypes": [ { + "id": 1, "name": "create", "label": "Create" }, { + "id": 2, "name": "delete", "label": "Delete" }, { + "id": 3, "name": "rollover", "label": "Rollover" }, { + "id": 4, "name": "setkeymaterial", "label": "Set Key Material" }, { + "id": 5, "name": "get", "label": "Get" }, { + "id": 6, "name": "getkeys", "label": "Get Keys" }, { + "id": 7, "name": "getmetadata", "label": "Get Metadata" }, { + "id": 8, "name": "generateeek", "label": "Generate EEK" }, { + "id": 9, "name": "decrypteek", "label": "Decrypt EEK" } @@ -76,6 +85,7 @@ "configs": [ { + "id": 1, "name": "provider", "type": "string", "mandatory": true, @@ -83,6 +93,7 @@ }, { + "id": 2, "name": "username", "type": "string", "mandatory": true, @@ -90,6 +101,7 @@ }, { + "id": 3, "name": "password", "type": "password", "mandatory": true, http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerPolicyValidator.java ---------------------------------------------------------------------- diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerPolicyValidator.java b/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerPolicyValidator.java index d55cdc5..5828f6f 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerPolicyValidator.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerPolicyValidator.java @@ -152,18 +152,19 @@ public class TestRangerPolicyValidator { _failures.clear(); _failures.clear(); assertFalse(_validator.isValid((Long)null, Action.DELETE, _failures)); _utils.checkFailureForMissingValue(_failures, "id"); - // should fail with appropriate error message if policy can't be found for the specified id + // should not fail if policy can't be found for the specified id when(_store.getPolicy(1L)).thenReturn(null); when(_store.getPolicy(2L)).thenThrow(new Exception()); RangerPolicy existingPolicy = mock(RangerPolicy.class); when(_store.getPolicy(3L)).thenReturn(existingPolicy); - _failures.clear(); assertFalse(_validator.isValid(1L, Action.DELETE, _failures)); - _utils.checkFailureForSemanticError(_failures, "id"); - _failures.clear(); assertFalse(_validator.isValid(2L, Action.DELETE, _failures)); - _utils.checkFailureForSemanticError(_failures, "id"); + _failures.clear(); assertTrue(_validator.isValid(1L, Action.DELETE, _failures)); + assertTrue(_failures.isEmpty()); + _failures.clear(); assertTrue(_validator.isValid(2L, Action.DELETE, _failures)); + assertTrue(_failures.isEmpty()); - // if policy exists then delete validation should pass - assertTrue(_validator.isValid(3L, Action.DELETE, _failures)); + // if policy exists then delete validation should pass, too! + _failures.clear(); assertTrue(_validator.isValid(3L, Action.DELETE, _failures)); + assertTrue(_failures.isEmpty()); } @Test http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefHelper.java ---------------------------------------------------------------------- diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefHelper.java b/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefHelper.java index a004f84..2703384 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefHelper.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefHelper.java @@ -19,6 +19,7 @@ package org.apache.ranger.plugin.model.validation; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -26,19 +27,28 @@ import static org.mockito.Mockito.when; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; +import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.ranger.plugin.model.RangerServiceDef; import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef; -import org.apache.ranger.plugin.model.validation.RangerServiceDefHelper; import org.apache.ranger.plugin.model.validation.RangerServiceDefHelper.Delegate; +import org.junit.Before; import org.junit.Test; import com.google.common.collect.Lists; public class TestRangerServiceDefHelper { + @Before + public void before() { + _serviceDef = mock(RangerServiceDef.class); + when(_serviceDef.getName()).thenReturn("a-service-def"); + // wipe the cache clean + RangerServiceDefHelper._Cache.clear(); + } + @Test public void test_getResourceHierarchies() { /* @@ -65,12 +75,11 @@ public class TestRangerServiceDefHelper { // order of resources in list sould not matter List<RangerResourceDef> resourceDefs = Lists.newArrayList(Column, Database, Table, Table_Atrribute, UDF); // stuff this into a service-def - RangerServiceDef serviceDef = mock(RangerServiceDef.class); - when(serviceDef.getResources()).thenReturn(resourceDefs); - when(serviceDef.getName()).thenReturn("a-service-def"); + when(_serviceDef.getResources()).thenReturn(resourceDefs); // now assert the behavior - RangerServiceDefHelper serviceDefHelper = new RangerServiceDefHelper(serviceDef); - Set<List<RangerResourceDef>> hierarchies = serviceDefHelper.getResourceHierarchies(); + _helper = new RangerServiceDefHelper(_serviceDef); + assertTrue(_helper.isResourceGraphValid()); + Set<List<RangerResourceDef>> hierarchies = _helper.getResourceHierarchies(); // there should be List<RangerResourceDef> hierarchy = Lists.newArrayList(Database, UDF); assertTrue(hierarchies.contains(hierarchy)); @@ -81,6 +90,69 @@ public class TestRangerServiceDefHelper { } @Test + public final void test_isResourceGraphValid_detectCycle() { + /* + * Create a service-def with cycles in resource graph + * A --> B --> C + * ^ | + * | | + * |---- D <--- + */ + RangerResourceDef A = createResourceDef("A", "D"); // A's parent is D, etc. + RangerResourceDef B = createResourceDef("B", "C"); + RangerResourceDef C = createResourceDef("C", "D"); + RangerResourceDef D = createResourceDef("D", "A"); + // order of resources in list sould not matter + List<RangerResourceDef> resourceDefs = Lists.newArrayList(A, B, C, D); + when(_serviceDef.getResources()).thenReturn(resourceDefs); + _helper = new RangerServiceDefHelper(_serviceDef); + assertFalse("Graph was valid!", _helper.isResourceGraphValid()); + } + + @Test + public final void test_isResourceGraphValid_forest() { + /* + * Create a service-def which is a forest + * Database -> Table-space + * | + * v + * Table -> Column + * + * Namespace -> package + * | + * v + * function + * + * Check that helper corrects reports back all of the hierarchies: levels in it and their order. + */ + RangerResourceDef database = createResourceDef("database", ""); + RangerResourceDef tableSpace = createResourceDef("table-space", "database"); + RangerResourceDef table = createResourceDef("table", "database"); + RangerResourceDef column = createResourceDef("column", "table"); + RangerResourceDef namespace = createResourceDef("namespace", ""); + RangerResourceDef function = createResourceDef("function", "namespace"); + RangerResourceDef Package = createResourceDef("package", "namespace"); + List<RangerResourceDef> resourceDefs = Lists.newArrayList(database, tableSpace, table, column, namespace, function, Package); + when(_serviceDef.getResources()).thenReturn(resourceDefs); + _helper = new RangerServiceDefHelper(_serviceDef); + assertTrue(_helper.isResourceGraphValid()); + Set<List<RangerResourceDef>> hierarchies = _helper.getResourceHierarchies(); + + Set<List<String>> expectedHierarchies = new HashSet<List<String>>(); + expectedHierarchies.add(Lists.newArrayList("database", "table-space")); + expectedHierarchies.add(Lists.newArrayList("database", "table", "column")); + expectedHierarchies.add(Lists.newArrayList("namespace", "package")); + expectedHierarchies.add(Lists.newArrayList("namespace", "function")); + + for (List<RangerResourceDef> aHierarchy : hierarchies) { + List<String> resourceNames = _helper.getAllResourceNames(aHierarchy); + assertTrue(expectedHierarchies.contains(resourceNames)); + expectedHierarchies.remove(resourceNames); + } + assertTrue(expectedHierarchies.isEmpty()); // make sure we saw got back hierarchies + } + + @Test public final void test_cacheBehavior() { // wipe the cache clean RangerServiceDefHelper._Cache.clear(); @@ -93,26 +165,26 @@ public class TestRangerServiceDefHelper { RangerServiceDefHelper._Cache.put(serviceName, delegate); // create a service def with matching date value - RangerServiceDef serviceDef = mock(RangerServiceDef.class); - when(serviceDef.getName()).thenReturn(serviceName); - when(serviceDef.getUpdateTime()).thenReturn(aDate); + _serviceDef = mock(RangerServiceDef.class); + when(_serviceDef.getName()).thenReturn(serviceName); + when(_serviceDef.getUpdateTime()).thenReturn(aDate); // since cache has it, we should get back the one that we have added - RangerServiceDefHelper serviceDefHelper = new RangerServiceDefHelper(serviceDef); - assertTrue("Didn't get back the same object that was put in cache", delegate == serviceDefHelper._delegate); + _helper = new RangerServiceDefHelper(_serviceDef); + assertTrue("Didn't get back the same object that was put in cache", delegate == _helper._delegate); // if we change the date then that should force helper to create a new delegate instance /* * NOTE:: current logic would replace the cache instance even if the one in the cache is newer. This is not likely to happen but it is important to call this out * as in rare cases one may end up creating re creating delegate if threads have stale copies of service def. */ - when(serviceDef.getUpdateTime()).thenReturn(getLastMonth()); - serviceDefHelper = new RangerServiceDefHelper(serviceDef); - assertTrue("Didn't get a delegate different than what was put in the cache", delegate != serviceDefHelper._delegate); + when(_serviceDef.getUpdateTime()).thenReturn(getLastMonth()); + _helper = new RangerServiceDefHelper(_serviceDef); + assertTrue("Didn't get a delegate different than what was put in the cache", delegate != _helper._delegate); // now that a new instance was added to the cache let's ensure that it got added to the cache - Delegate newDelegate = serviceDefHelper._delegate; - serviceDefHelper = new RangerServiceDefHelper(serviceDef); - assertTrue("Didn't get a delegate different than what was put in the cache", newDelegate == serviceDefHelper._delegate); + Delegate newDelegate = _helper._delegate; + _helper = new RangerServiceDefHelper(_serviceDef); + assertTrue("Didn't get a delegate different than what was put in the cache", newDelegate == _helper._delegate); } RangerResourceDef createResourceDef(String name, String parent) { @@ -134,4 +206,7 @@ public class TestRangerServiceDefHelper { Date now = cal.getTime(); return now; } + + private RangerServiceDef _serviceDef; + private RangerServiceDefHelper _helper; } http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefValidator.java ---------------------------------------------------------------------- diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefValidator.java b/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefValidator.java index 1409d2c..54bbdf1 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefValidator.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefValidator.java @@ -26,22 +26,21 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.ArrayList; +import java.util.Date; import java.util.List; -import java.util.Map; import org.apache.ranger.plugin.model.RangerServiceDef; import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef; import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumDef; import org.apache.ranger.plugin.model.RangerServiceDef.RangerEnumElementDef; -import org.apache.ranger.plugin.model.validation.RangerServiceDefValidator; -import org.apache.ranger.plugin.model.validation.ValidationFailureDetails; +import org.apache.ranger.plugin.model.RangerServiceDef.RangerPolicyConditionDef; +import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef; +import org.apache.ranger.plugin.model.RangerServiceDef.RangerServiceConfigDef; import org.apache.ranger.plugin.model.validation.RangerValidator.Action; import org.apache.ranger.plugin.store.ServiceStore; import org.junit.Before; import org.junit.Test; -import com.google.common.collect.ImmutableMap; - public class TestRangerServiceDefValidator { @Before @@ -55,15 +54,15 @@ public class TestRangerServiceDefValidator { final Action[] cu = new Action[] { Action.CREATE, Action.UPDATE }; final Object[][] accessTypes_good = new Object[][] { - { "read", null }, // ok, null implied grants - { "write", new String[] { } }, // ok, empty implied grants - { "admin", new String[] { "READ", "write" } } // ok, admin access implies read/write, access types are case-insensitive + { 1L, "read", null }, // ok, null implied grants + { 2L, "write", new String[] { } }, // ok, empty implied grants + { 3L, "admin", new String[] { "READ", "write" } } // ok, admin access implies read/write, access types are case-insensitive }; - final Map<String, String[]> enums_good = ImmutableMap.of( - "authentication-type", new String[] { "simple", "kerberos" }, - "time-unit", new String[] { "day", "hour", "minute" } - ); + final Object[][] enums_good = new Object[][] { + { 1L, "authentication-type", new String[] { "simple", "kerberos" } }, + { 2L, "time-unit", new String[] { "day", "hour", "minute" } }, + }; @Test public final void test_isValid_happyPath_create() throws Exception { @@ -74,26 +73,6 @@ public class TestRangerServiceDefValidator { List<RangerEnumDef> enumDefs = _utils.createEnumDefs(enums_good); when(_serviceDef.getEnums()).thenReturn(enumDefs); - // create: id is not relevant, name should not conflict - when(_serviceDef.getId()).thenReturn(null); // id is not relevant for create - when(_serviceDef.getName()).thenReturn("aServiceDef"); // service has a name - when(_store.getServiceDefByName("aServiceDef")).thenReturn(null); // no name collision - assertTrue(_validator.isValid(_serviceDef, Action.CREATE, _failures)); - assertTrue(_failures.isEmpty()); - - // update: id should match existing service, name should not point to different service def - when(_serviceDef.getId()).thenReturn(5L); - RangerServiceDef existingServiceDef = mock(RangerServiceDef.class); - when(_store.getServiceDef(5L)).thenReturn(existingServiceDef); - assertTrue(_validator.isValid(_serviceDef, Action.UPDATE, _failures)); - assertTrue(_failures.isEmpty()); - - // update: if name points to a service that it's id should be the same - RangerServiceDef anotherExistingServiceDef = mock(RangerServiceDef.class); - when(anotherExistingServiceDef.getId()).thenReturn(5L); - when(_store.getServiceDefByName("aServiceDef")).thenReturn(anotherExistingServiceDef); - assertTrue(_validator.isValid(_serviceDef, Action.UPDATE, _failures)); - assertTrue(_failures.isEmpty()); } @Test @@ -106,11 +85,11 @@ public class TestRangerServiceDefValidator { // passing in null id is an error _failures.clear(); assertFalse(_validator.isValid((Long)null, Action.DELETE, _failures)); _utils.checkFailureForMissingValue(_failures, "id"); - // a service def with that id should exist, else it is an error + // It is ok for a service def with that id to not exist! id = 3L; when(_store.getServiceDef(id)).thenReturn(null); - _failures.clear(); assertFalse(_validator.isValid(id, Action.DELETE, _failures)); - _utils.checkFailureForSemanticError(_failures, "id"); + _failures.clear(); assertTrue(_validator.isValid(id, Action.DELETE, _failures)); + assertTrue(_failures.isEmpty()); // happypath when(_store.getServiceDef(id)).thenReturn(_serviceDef); _failures.clear(); assertTrue(_validator.isValid(id, Action.DELETE, _failures)); @@ -118,81 +97,116 @@ public class TestRangerServiceDefValidator { } @Test - public final void testIsValid_failures_name() throws Exception { + public final void testIsValid_failures() throws Exception { // null service def and bad service def name for (Action action : cu) { // passing in null service def is an error assertFalse(_validator.isValid((RangerServiceDef)null, action, _failures)); _utils.checkFailureForMissingValue(_failures, "service def"); - // name should be valid - for (String name : new String[] { null, "", " " }) { - when(_serviceDef.getName()).thenReturn(name); - _failures.clear(); assertFalse(_validator.isValid(_serviceDef, action, _failures)); - _utils.checkFailureForMissingValue(_failures, "name"); - } } } @Test - public final void testIsValid_failures_id() throws Exception { + public final void test_isValidServiceDefId_failures() throws Exception { + // id is required for update - when(_serviceDef.getId()).thenReturn(null); - assertFalse(_validator.isValid(_serviceDef, Action.UPDATE, _failures)); + assertFalse(_validator.isValidServiceDefId(null, Action.UPDATE, _failures)); _utils.checkFailureForMissingValue(_failures, "id"); // update: service should exist for the passed in id - Long id = 7L; + long id = 7; when(_serviceDef.getId()).thenReturn(id); when(_store.getServiceDef(id)).thenReturn(null); - assertFalse(_validator.isValid(_serviceDef, Action.UPDATE, _failures)); + assertFalse(_validator.isValidServiceDefId(id, Action.UPDATE, _failures)); _utils.checkFailureForSemanticError(_failures, "id"); when(_store.getServiceDef(id)).thenThrow(new Exception()); - assertFalse(_validator.isValid(_serviceDef, Action.UPDATE, _failures)); + assertFalse(_validator.isValidServiceDefId(id, Action.UPDATE, _failures)); _utils.checkFailureForSemanticError(_failures, "id"); } @Test - public final void testIsValid_failures_nameId_create() throws Exception { - // service shouldn't exist with the name + public final void test_isValidServiceDefId_happyPath() throws Exception { + + // create: null id is ok + assertTrue(_validator.isValidServiceDefId(null, Action.CREATE, _failures)); + assertTrue(_failures.isEmpty()); + + // update: a service with same id exist + long id = 7; + when(_serviceDef.getId()).thenReturn(id); + RangerServiceDef serviceDefFromDb = mock(RangerServiceDef.class); + when(serviceDefFromDb.getId()).thenReturn(id); + when(_store.getServiceDef(id)).thenReturn(serviceDefFromDb); + assertTrue(_validator.isValidServiceDefId(id, Action.UPDATE, _failures)); + assertTrue(_failures.isEmpty()); + } + + @Test + public final void test_isValidName() throws Exception { + Long id = 7L; // some arbitrary value + // name can't be null/empty + for (Action action: cu) { + for (String name : new String[] { null, "", " " }) { + when(_serviceDef.getName()).thenReturn(name); + _failures.clear(); assertFalse(_validator.isValidServiceDefName(name, id, action, _failures)); + _utils.checkFailureForMissingValue(_failures, "name"); + } + } + } + + @Test + public final void test_isValidName_create() throws Exception { + Long id = null; // id should be irrelevant for name check for create. + + // for create a service shouldn't exist with the name + String name = "existing-service"; + when(_serviceDef.getName()).thenReturn(name); + when(_store.getServiceDefByName(name)).thenReturn(null); + assertTrue(_validator.isValidServiceDefName(name, id, Action.CREATE, _failures)); + assertTrue(_failures.isEmpty()); + RangerServiceDef existingServiceDef = mock(RangerServiceDef.class); - when(_store.getServiceDefByName("existing-service")).thenReturn(existingServiceDef); - when(_serviceDef.getName()).thenReturn("existing-service"); - _failures.clear(); assertFalse(_validator.isValid(_serviceDef, Action.CREATE, _failures)); + when(_store.getServiceDefByName(name)).thenReturn(existingServiceDef); + _failures.clear(); assertFalse(_validator.isValidServiceDefName(name, id, Action.CREATE, _failures)); _utils.checkFailureForSemanticError(_failures, "name"); } @Test - public final void testIsValid_failures_nameId_update() throws Exception { + public final void test_isValidName_update() throws Exception { // update: if service exists with the same name then it can't point to a different service - Long id = 7L; + long id = 7; when(_serviceDef.getId()).thenReturn(id); - RangerServiceDef existingServiceDef = mock(RangerServiceDef.class); - when(existingServiceDef.getId()).thenReturn(id); - when(_store.getServiceDef(id)).thenReturn(existingServiceDef); - String name = "aServiceDef"; when(_serviceDef.getName()).thenReturn(name); - RangerServiceDef anotherExistingServiceDef = mock(RangerServiceDef.class); - Long anotherId = 49L; - when(anotherExistingServiceDef.getId()).thenReturn(anotherId); - when(_store.getServiceDefByName(name)).thenReturn(anotherExistingServiceDef); + when(_store.getServiceDefByName(name)).thenReturn(null); // no service with the new name (we are updating the name to a unique value) + assertTrue(_validator.isValidServiceDefName(name, id, Action.UPDATE, _failures)); + assertTrue(_failures.isEmpty()); + + RangerServiceDef existingServiceDef = mock(RangerServiceDef.class); + when(existingServiceDef.getId()).thenReturn(id); + when(existingServiceDef.getName()).thenReturn(name); + when(_store.getServiceDefByName(name)).thenReturn(existingServiceDef); + assertTrue(_validator.isValidServiceDefName(name, id, Action.UPDATE, _failures)); + assertTrue(_failures.isEmpty()); - assertFalse(_validator.isValid(_serviceDef, Action.UPDATE, _failures)); + long anotherId = 49; + when(existingServiceDef.getId()).thenReturn(anotherId); + assertFalse(_validator.isValidServiceDefName(name, id, Action.UPDATE, _failures)); _utils.checkFailureForSemanticError(_failures, "id/name"); } final Object[][] accessTypes_bad_unknownType = new Object[][] { - { "read", null }, // ok, null implied grants - { "write", new String[] { } }, // ok, empty implied grants - { "admin", new String[] { "ReaD", "execute" } } // non-existent access type (execute), read is good (case should not matter) + { 1L, "read", null }, // ok, null implied grants + { 1L, "write", new String[] { } }, // empty implied grants-ok, duplicate id + { 3L, "admin", new String[] { "ReaD", "execute" } } // non-existent access type (execute), read is good (case should not matter) }; final Object[][] accessTypes_bad_selfReference = new Object[][] { - { "read", null }, // ok, null implied grants - { "write", new String[] { } }, // ok, empty implied grants - { "admin", new String[] { "write", "admin" } } // non-existent access type (execute) + { 1L, "read", null }, // ok, null implied grants + { 2L, "write", new String[] { } }, // ok, empty implied grants + { 3L, "admin", new String[] { "write", "admin" } } // non-existent access type (execute) }; @Test @@ -204,16 +218,17 @@ public class TestRangerServiceDefValidator { @Test public final void test_isValidAccessTypes_failures() { - // sending in empty null access type defs is ok - assertTrue(_validator.isValidAccessTypes(null, _failures)); - assertTrue(_failures.isEmpty()); + // null or empty access type defs + List<RangerAccessTypeDef> accessTypeDefs = null; + _failures.clear(); assertFalse(_validator.isValidAccessTypes(accessTypeDefs, _failures)); + _utils.checkFailureForMissingValue(_failures, "access types"); - List<RangerAccessTypeDef> input = new ArrayList<RangerAccessTypeDef>(); - _failures.clear(); assertTrue(_validator.isValidAccessTypes(input, _failures)); - assertTrue(_failures.isEmpty()); + accessTypeDefs = new ArrayList<RangerAccessTypeDef>(); + _failures.clear(); assertFalse(_validator.isValidAccessTypes(accessTypeDefs, _failures)); + _utils.checkFailureForMissingValue(_failures, "access types"); // null/empty access types - List<RangerAccessTypeDef> accessTypeDefs = _utils.createAccessTypeDefs(new String[] { null, "", " " }); + accessTypeDefs = _utils.createAccessTypeDefs(new String[] { null, "", " " }); _failures.clear(); assertFalse(_validator.isValidAccessTypes(accessTypeDefs, _failures)); _utils.checkFailureForMissingValue(_failures, "access type name"); @@ -231,6 +246,7 @@ public class TestRangerServiceDefValidator { accessTypeDefs = _utils.createAccessTypeDefs(accessTypes_bad_unknownType); _failures.clear(); assertFalse(_validator.isValidAccessTypes(accessTypeDefs, _failures)); _utils.checkFailureForSemanticError(_failures, "implied grants", "execute"); + _utils.checkFailureForSemanticError(_failures, "access type id", "1"); // id 1 is duplicated // access type with implied grant referring to itself accessTypeDefs = _utils.createAccessTypeDefs(accessTypes_bad_selfReference); @@ -238,34 +254,33 @@ public class TestRangerServiceDefValidator { _utils.checkFailureForSemanticError(_failures, "implied grants", "admin"); } - final Map<String, String[]> enums_bad_enumName_null = ImmutableMap.of( - "authentication-type", new String[] { "simple", "kerberos" }, - "time-unit", new String[] { "day", "hour", "minute" }, - "null", new String[] { "foo", "bar", "tar" } // null enum-name -- "null" is a special value that leads to a null enum name - ); - - final Map<String, String[]> enums_bad_enumName_blank = ImmutableMap.of( - "authentication-type", new String[] { "simple", "kerberos" }, - "time-unit", new String[] { "day", "hour", "minute" }, - " ", new String[] { "foo", "bar", "tar" } // enum name is all spaces - ); + final Object[][] enums_bad_enumName_null = new Object[][] { + // { id, enum-name, enum-values } + { 1L, "authentication-type", new String[] { "simple", "kerberos" } }, + { 2L, "time-unit", new String[] { "day", "hour", "minute" } }, + { 3L, null, new String[] { "foo", "bar", "tar" } }, // null enum-name + }; - final Map<String, String[]> enums_bad_Elements_empty = ImmutableMap.of( - "authentication-type", new String[] { "simple", "kerberos" }, - "time-unit", new String[] { "day", "hour", "minute" }, - "anEnum", new String[] { } // enum elements collection is empty - ); + final Object[][] enums_bad_enumName_blank = new Object[][] { + // { id, enum-name, enum-values } + { 1L, "authentication-type", new String[] { "simple", "kerberos" } }, + { 1L, "time-unit", new String[] { "day", "hour", "minute" } }, + { 2L, " ", new String[] { "foo", "bar", "tar" } }, // enum name is all spaces + }; - final Map<String, String[]> enums_bad_enumName_duplicate_exact = ImmutableMap.of( - "authentication-type", new String[] { "simple", "kerberos" }, - "time-unit", new String[] { "day", "hour", "minute" } - ); + final Object[][] enums_bad_Elements_empty = new Object[][] { + // { id, enum-name, enum-values } + { null, "authentication-type", new String[] { "simple", "kerberos" } }, // null id + { 1L, "time-unit", new String[] { "day", "hour", "minute" } }, + { 2L, "anEnum", new String[] { } }, // enum elements collection is empty + }; - final Map<String, String[]> enums_bad_enumName_duplicate_differentCase = ImmutableMap.of( - "authentication-type", new String[] { "simple", "kerberos" }, - "time-unit", new String[] { "day", "hour", "minute" }, - "Authentication-Type", new String[] { } // duplicate enum-name different in case - ); + final Object[][] enums_bad_enumName_duplicate_differentCase = new Object[][] { + // { id, enum-name, enum-values } + { 1L, "authentication-type", new String[] { "simple", "kerberos" } }, + { 1L, "time-unit", new String[] { "day", "hour", "minute" } }, + { 1L, "Authentication-Type", new String[] { } },// duplicate enum-name different in case + }; @Test public final void test_isValidEnums_happyPath() { @@ -290,6 +305,7 @@ public class TestRangerServiceDefValidator { input = _utils.createEnumDefs(enums_bad_enumName_blank); _failures.clear(); assertFalse(_validator.isValidEnums(input, _failures)); _utils.checkFailureForMissingValue(_failures, "enum def name"); + _utils.checkFailureForSemanticError(_failures, "enum def id", "1"); // enum elements collection should not be null or empty input = _utils.createEnumDefs(enums_good); @@ -303,6 +319,7 @@ public class TestRangerServiceDefValidator { input = _utils.createEnumDefs(enums_bad_Elements_empty); _failures.clear(); assertFalse(_validator.isValidEnums(input, _failures)); _utils.checkFailureForMissingValue(_failures, "enum values", "anEnum"); + _utils.checkFailureForMissingValue(_failures, "enum def id"); // enum names should be distinct -- exact match input = _utils.createEnumDefs(enums_good); @@ -359,11 +376,144 @@ public class TestRangerServiceDefValidator { // element names should be distinct - case insensitive input = _utils.createEnumElementDefs(new String[] { "simple", "kerberos", "kerberos" }); // duplicate name - exact match _failures.clear(); assertFalse(_validator.isValidEnumElements(input, _failures, "anEnum")); - _utils.checkFailureForSemanticError(_failures, "enum element name", "anEnum"); + _utils.checkFailureForSemanticError(_failures, "enum element name", "kerberos"); input = _utils.createEnumElementDefs(new String[] { "simple", "kerberos", "kErbErOs" }); // duplicate name - different case _failures.clear(); assertFalse(_validator.isValidEnumElements(input, _failures, "anEnum")); - _utils.checkFailureForSemanticError(_failures, "enum element name", "anEnum"); + _utils.checkFailureForSemanticError(_failures, "enum element name", "kErbErOs"); + } + + Object[][] invalidResources = new Object[][] { + // { id, level, name } + { null, null, null }, // everything is null + { 1L, -10, "database" }, // -ve value for level is ok + { 1L, 10, "table" }, // id is duplicate + { 2L, -10, "DataBase" }, // (in different case) but name and level are duplicate + { 3L, 30, " " } // Name is all whitespace + }; + + @Test + public final void test_isValidResources() { + // null/empty resources are an error + when(_serviceDef.getResources()).thenReturn(null); + _failures.clear(); assertFalse(_validator.isValidResources(_serviceDef, _failures)); + _utils.checkFailureForMissingValue(_failures, "resources"); + + List<RangerResourceDef> resources = new ArrayList<RangerResourceDef>(); + when(_serviceDef.getResources()).thenReturn(resources); + _failures.clear(); assertFalse(_validator.isValidResources(_serviceDef, _failures)); + _utils.checkFailureForMissingValue(_failures, "resources"); + + resources.addAll(_utils.createResourceDefsWithIds(invalidResources)); + _failures.clear(); assertFalse(_validator.isValidResources(_serviceDef, _failures)); + _utils.checkFailureForMissingValue(_failures, "resource name"); + _utils.checkFailureForMissingValue(_failures, "resource id"); + _utils.checkFailureForSemanticError(_failures, "resource id", "1"); // id 1 is duplicate + _utils.checkFailureForSemanticError(_failures, "resource name", "DataBase"); + } + + @Test + public final void test_isValidResourceGraph() { + Object[][] data_bad = new Object[][] { + // { name, excludesSupported, recursiveSupported, mandatory, reg-exp, parent-level, level } + { "db", null, null, null, null, "" , 10 }, + { "table", null, null, null, null, "db", 20 }, // same as db's level + { "column-family", null, null, null, null, "table", null }, // level is null! + { "column", null, null, null, null, "column-family", 20 }, // level is duplicate for [db->table->column-family-> column] hierarchy + { "udf", null, null, null, null, "db", 10 }, // udf's id conflicts with that of db in the [db->udf] hierarchy + }; + + List<RangerResourceDef> resourceDefs = _utils.createResourceDefs(data_bad); + when(_serviceDef.getResources()).thenReturn(resourceDefs); + when(_serviceDef.getName()).thenReturn("service-name"); + when(_serviceDef.getUpdateTime()).thenReturn(new Date()); + + _failures.clear(); assertFalse(_validator.isValidResourceGraph(_serviceDef, _failures)); + _utils.checkFailureForMissingValue(_failures, "resource level"); + _utils.checkFailureForSemanticError(_failures, "resource level", "20"); // level 20 is duplicate for 1 hierarchy + _utils.checkFailureForSemanticError(_failures, "resource level", "10"); // level 10 is duplicate for another hierarchy + + Object[][] data_good = new Object[][] { + // { name, excludesSupported, recursiveSupported, mandatory, reg-exp, parent-level, level } + { "db", null, null, null, null, "" , -10 }, // -ve level is ok + { "table", null, null, null, null, "db", 0 }, // 0 level is ok + { "column", null, null, null, null, "table", 10 }, // level is null! + { "udf", null, null, null, null, "db", 0 }, // should not conflict as it belong to a different hierarchy + }; + resourceDefs = _utils.createResourceDefs(data_good); + when(_serviceDef.getResources()).thenReturn(resourceDefs); + _failures.clear(); assertTrue(_validator.isValidResourceGraph(_serviceDef, _failures)); + assertTrue(_failures.isEmpty()); + } + + @Test + public final void test_isValidResources_happyPath() { + Object[][] data = new Object[][] { + // { id, level, name } + { 1L, -10, "database" }, // -ve value for level + { 3L, 0, "table" }, // it is ok for id and level to skip values + { 5L, 10, "column" }, // it is ok for level to skip values + }; + List<RangerResourceDef> resources = _utils.createResourceDefsWithIds(data); + when(_serviceDef.getResources()).thenReturn(resources); + assertTrue(_validator.isValidResources(_serviceDef, _failures)); + assertTrue(_failures.isEmpty()); + } + + @Test + public final void test_isValidConfigs_failures() { + assertTrue(_validator.isValidConfigs(null, null, _failures)); + assertTrue(_failures.isEmpty()); + + Object[][] config_def_data_bad = new Object[][] { + // { id, name, type, subtype, default-value } + { null, null, "" }, // id and name both null, type is empty + { 1L, "security", "blah" }, // bad type for service def + { 1L, "port", "int" }, // duplicate id + { 2L, "security", "string" }, // duplicate name + { 3L, "timeout", "enum", "units", null }, // , sub-type (units) is not among known enum types + { 4L, "auth", "enum", "authentication-type", "dimple" }, // default value is not among known values for the enum (sub-type) + }; + + List<RangerServiceConfigDef> configs = _utils.createServiceDefConfigs(config_def_data_bad); + List<RangerEnumDef> enumDefs = _utils.createEnumDefs(enums_good); + assertFalse(_validator.isValidConfigs(configs, enumDefs, _failures)); + _utils.checkFailureForMissingValue(_failures, "config def name"); + _utils.checkFailureForMissingValue(_failures, "config def id"); + _utils.checkFailureForMissingValue(_failures, "config def type"); + _utils.checkFailureForSemanticError(_failures, "config def name", "security"); // there were two configs with same name as security + _utils.checkFailureForSemanticError(_failures, "config def id", "1"); // a config with duplicate had id of 1 + _utils.checkFailureForSemanticError(_failures, "config def type", "security"); // type for config security was invalid + _utils.checkFailureForSemanticError(_failures, "config def subtype", "timeout"); // type for config security was invalid + _utils.checkFailureForSemanticError(_failures, "config def default value", "auth"); // type for config security was invalid + } + + @Test + public final void test_isValidPolicyConditions() { + // null/empty policy conditions are ok + assertTrue(_validator.isValidPolicyConditions(null, _failures)); + assertTrue(_failures.isEmpty()); + List<RangerPolicyConditionDef> conditionDefs = new ArrayList<RangerServiceDef.RangerPolicyConditionDef>(); + assertTrue(_validator.isValidPolicyConditions(conditionDefs, _failures)); + assertTrue(_failures.isEmpty()); + + Object[][] policyCondition_data = { + // { id, name, evaluator } + { null, null, null}, // everything null! + { 1L, "condition-1", null }, // missing evaluator + { 1L, "condition-2", "" }, // duplicate id, missing evaluator + { 2L, "condition-1", "com.evaluator" }, // duplicate name + }; + + conditionDefs.addAll(_utils.createPolicyConditionDefs(policyCondition_data)); + _failures.clear(); assertFalse(_validator.isValidPolicyConditions(conditionDefs, _failures)); + _utils.checkFailureForMissingValue(_failures, "policy condition def id"); + _utils.checkFailureForMissingValue(_failures, "policy condition def name"); + _utils.checkFailureForMissingValue(_failures, "policy condition def evaluator"); + _utils.checkFailureForSemanticError(_failures, "policy condition def id", "1"); + _utils.checkFailureForSemanticError(_failures, "policy condition def name", "condition-1"); + _utils.checkFailureForMissingValue(_failures, "policy condition def evaluator", "condition-2"); + _utils.checkFailureForMissingValue(_failures, "policy condition def evaluator", "condition-1"); } private ValidationTestUtils _utils = new ValidationTestUtils(); http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceValidator.java ---------------------------------------------------------------------- diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceValidator.java b/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceValidator.java index dd8485e..6c20f0d 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceValidator.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceValidator.java @@ -214,14 +214,14 @@ public class TestRangerServiceValidator { _validator = new RangerServiceValidator(_store); _failures.clear(); assertFalse(_validator.isValid((Long)null, Action.DELETE, _failures)); _utils.checkFailureForMissingValue(_failures, "id"); - // if service with that id does not exist then that, too, is a failure + // if service with that id does not exist then that, is ok because delete is idempotent when(_store.getService(1L)).thenReturn(null); when(_store.getService(2L)).thenThrow(new Exception()); - _failures.clear(); assertFalse(_validator.isValid(1L, Action.DELETE, _failures)); - _utils.checkFailureForSemanticError(_failures, "id"); + _failures.clear(); assertTrue(_validator.isValid(1L, Action.DELETE, _failures)); + assertTrue(_failures.isEmpty()); - _failures.clear(); assertFalse(_validator.isValid(2L, Action.DELETE, _failures)); - _utils.checkFailureForSemanticError(_failures, "id"); + _failures.clear(); assertTrue(_validator.isValid(2L, Action.DELETE, _failures)); + assertTrue(_failures.isEmpty()); } @Test http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/7ac04f9d/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerValidator.java ---------------------------------------------------------------------- diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerValidator.java b/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerValidator.java index 46f488e..01c0e6d 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerValidator.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerValidator.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -77,6 +78,7 @@ public class TestRangerValidator { public void before() { _store = mock(ServiceStore.class); _validator = new RangerValidatorForTest(_store); + _failures = new ArrayList<ValidationFailureDetails>(); } @Test @@ -506,7 +508,76 @@ public class TestRangerValidator { assertTrue(result.contains(" d ")); } + @Test + public void test_isValid_string() { + String fieldName = "value-field-Name"; + String collectionName = "value-collection-Name"; + Set<String> alreadySeen = new HashSet<String>(); + // null/empty string value is invalid + for (String value : new String[] { null, "", " " }) { + assertFalse(_validator.isUnique(value, alreadySeen, fieldName, collectionName, _failures)); + _utils.checkFailureForMissingValue(_failures, fieldName); + } + // value should not have been seen so far. + String value = "blah"; + _failures.clear(); assertTrue(_validator.isUnique(value, alreadySeen, fieldName, collectionName, _failures)); + assertTrue(_failures.isEmpty()); + assertTrue(alreadySeen.contains(value)); + + // since "blah" has already been seen doing this test again should fail + _failures.clear(); assertFalse(_validator.isUnique(value, alreadySeen, fieldName, collectionName, _failures)); + _utils.checkFailureForSemanticError(_failures, fieldName, value); + + // not see check is done in a case-insenstive manner + value = "bLaH"; + _failures.clear(); assertFalse(_validator.isUnique(value, alreadySeen, fieldName, collectionName, _failures)); + _utils.checkFailureForSemanticError(_failures, fieldName, value); + } + + @Test + public void test_isValid_long() { + String fieldName = "field-Name"; + String collectionName = "field-collection-Name"; + Set<Long> alreadySeen = new HashSet<Long>(); + Long value = null; + // null value is invalid + assertFalse(_validator.isUnique(value, alreadySeen, fieldName, collectionName, _failures)); + _utils.checkFailureForMissingValue(_failures, fieldName); + + // value should not have been seen so far. + value = 7L; + _failures.clear(); assertTrue(_validator.isUnique(value, alreadySeen, fieldName, collectionName, _failures)); + assertTrue(_failures.isEmpty()); + assertTrue(alreadySeen.contains(value)); + + // since 7L has already been seen doing this test again should fail + _failures.clear(); assertFalse(_validator.isUnique(value, alreadySeen, fieldName, collectionName, _failures)); + _utils.checkFailureForSemanticError(_failures, fieldName, value.toString()); + } + + @Test + public void test_isValid_integer() { + String fieldName = "field-Name"; + String collectionName = "field-collection-Name"; + Set<Integer> alreadySeen = new HashSet<Integer>(); + Integer value = null; + // null value is invalid + assertFalse(_validator.isUnique(value, alreadySeen, fieldName, collectionName, _failures)); + _utils.checkFailureForMissingValue(_failures, fieldName); + + // value should not have been seen so far. + value = 49; + _failures.clear(); assertTrue(_validator.isUnique(value, alreadySeen, fieldName, collectionName, _failures)); + assertTrue(_failures.isEmpty()); + assertTrue(alreadySeen.contains(value)); + + // since 7L has already been seen doing this test again should fail + _failures.clear(); assertFalse(_validator.isUnique(value, alreadySeen, fieldName, collectionName, _failures)); + _utils.checkFailureForSemanticError(_failures, fieldName, value.toString()); + } + private RangerValidatorForTest _validator; private ServiceStore _store; private ValidationTestUtils _utils = new ValidationTestUtils(); + private List<ValidationFailureDetails> _failures; }
