/* * 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.ranger.plugin.model.validation; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.ranger.plugin.model.RangerPolicy; import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItem; import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItemAccess; import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource; import org.apache.ranger.plugin.model.RangerPolicyResourceSignature; import org.apache.ranger.plugin.model.RangerService; import org.apache.ranger.plugin.model.RangerServiceDef; import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef; import org.apache.ranger.plugin.model.validation.RangerValidator.Action; import org.apache.ranger.plugin.store.ServiceStore; import org.apache.ranger.plugin.util.RangerObjectFactory; import org.apache.ranger.plugin.util.SearchFilter; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatcher; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; public class TestRangerPolicyValidator { /** * Wrapper class only so we clear out the RangerServiceDefHelper before every test. * @author alal * */ static class RangerPolicyValidatorWrapper extends RangerPolicyValidator { public RangerPolicyValidatorWrapper(ServiceStore store) { super(store); } boolean isValid(Long id, Action action, List<ValidationFailureDetails> failures) { RangerServiceDefHelper._Cache.clear(); return super.isValid(id, action, failures); } boolean isValid(RangerPolicy policy, Action action, boolean isAdmin, List<ValidationFailureDetails> failures) { RangerServiceDefHelper._Cache.clear(); return super.isValid(policy, action, isAdmin, failures); } } @Before public void setUp() throws Exception { _store = mock(ServiceStore.class); _policy = mock(RangerPolicy.class); _validator = new RangerPolicyValidatorWrapper(_store); _serviceDef = mock(RangerServiceDef.class); _factory = mock(RangerObjectFactory.class); _validator._factory = _factory; } final Action[] cu = new Action[] { Action.CREATE, Action.UPDATE }; final Object[] policyItemsData = new Object[] { ImmutableMap.of( // all good "users", new String[] {"user1" ," user2"}, "groups", new String[] {"group1", "group2"}, "accesses", new String[] { "r", "w" }, "isAllowed", new Boolean[] { true, true }), ImmutableMap.of( // no users, access type different case "groups", new String[] {"group3", "group4"}, "accesses", new String[]{"W", "x"}, "isAllowed", new Boolean[] { true, true }), ImmutableMap.of( // no groups "users", new String[] {"user3" ," user4"}, "accesses", new String[] { "r", "x" }, "isAllowed", new Boolean[] { true, true }), ImmutableMap.of( // isallowed on access types is null, case is different from that in definition "users", new String[] {"user7" ," user6"}, "accesses", new String[] { "a" }, "isAllowed", new Boolean[] { null, null }) }; String[] accessTypes = new String[] { "r", "w", "x", "A" }; // mix of lower and upper case String[] accessTypes_bad = new String[] { "r", "w", "xx", }; // two missing (x, a), one new that isn't on bad (xx) private final Object[][] resourceDefData = new Object[][] { // { name, excludesSupported, recursiveSupported, mandatory, reg-exp, parent-level } { "db", null, null, true, "db\\d+", null }, // valid values: db1, db22, db983, etc.; invalid: db, db12x, ttx11, etc.; null => false for excludes and recursive { "tbl", true, true, true, null, "db" }, // regex == null => anything goes; excludes == true, recursive == true { "col", false, true, false, "col\\d{1,2}", "tbl" }, // valid: col1, col47, etc.; invalid: col, col238, col1, etc., excludes == false, recursive == true }; private final Object[][] resourceDefData_multipleHierarchies = new Object[][] { // { name, excludesSupported, recursiveSupported, mandatory, reg-exp, parent-level } { "db", null, null, true, "db\\d+", null }, // valid values: db1, db22, db983, etc.; invalid: db, db12x, ttx11, etc.; null => false for excludes and recursive { "tbl", true, true, true, null, "db" }, // regex == null => anything goes; excludes == true, recursive == true { "col", false, true, false, "col\\d{1,2}", "tbl" }, // valid: col1, col47, etc.; invalid: col, col238, col1, etc., excludes == false, recursive == true { "udf", true, true, true, null, "db" } // same parent as tbl (simulating hive's multiple resource hierarchies) }; private final Object[][] policyResourceMap_good = new Object[][] { // resource-name, values, excludes, recursive { "db", new String[] { "db1", "db2" }, null, null }, { "TBL", new String[] { "tbl1", "tbl2" }, true, false } // case should not matter }; private final Object[][] policyResourceMap_goodMultipleHierarchies = new Object[][] { // resource-name, values, excludes, recursive { "db", new String[] { "db1", "db2" }, null, null }, { "UDF", new String[] { "udf1", "udf2" }, true, false } // case should not matter }; private final Object[][] policyResourceMap_bad = new Object[][] { // resource-name, values, excludes, recursive { "db", new String[] { "db1", "db2" }, null, true }, // mandatory "tbl" missing; recursive==true specified when resource-def does not support it (null) {"col", new String[] { "col12", "col 1" }, true, true }, // wrong format of value for "col"; excludes==true specified when resource-def does not allow it (false) {"extra", new String[] { "extra1", "extra2" }, null, null } // spurious "extra" specified }; private final Object[][] policyResourceMap_bad_multiple_hierarchies = new Object[][] { // resource-name, values, excludes, recursive { "db", new String[] { "db1", "db2" }, null, true }, { "tbl", new String[] { "tbl11", "tbl2" }, null, true }, { "col", new String[] { "col1", "col2" }, true, true }, { "udf", new String[] { "extra1", "extra2" }, null, null } // either udf or tbl/db/col should be specified, not both }; private final Object[][] policyResourceMap_bad_multiple_hierarchies_missing_mandatory = new Object[][] { // resource-name, values, excludes, recursive { "db", new String[] { "db1", "db2" }, null, true } }; @Test public final void testIsValid_long() throws Exception { // this validation should be removed if we start supporting other than delete action Assert.assertFalse(_validator.isValid(3L, Action.CREATE, _failures)); _utils.checkFailureForInternalError(_failures); // should fail with appropriate error message if id is null _failures.clear(); _failures.clear(); Assert.assertFalse(_validator.isValid((Long)null, Action.DELETE, _failures)); _utils.checkFailureForMissingValue(_failures, "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(); Assert.assertTrue(_validator.isValid(1L, Action.DELETE, _failures)); Assert.assertTrue(_failures.isEmpty()); _failures.clear(); Assert.assertTrue(_validator.isValid(2L, Action.DELETE, _failures)); Assert.assertTrue(_failures.isEmpty()); // if policy exists then delete validation should pass, too! _failures.clear(); Assert.assertTrue(_validator.isValid(3L, Action.DELETE, _failures)); Assert.assertTrue(_failures.isEmpty()); } @Test public final void testIsValid_errorPaths() throws Exception { boolean isAdmin = true; // 1. create policy in a non-existing service Action action = Action.CREATE; when(_policy.getService()).thenReturn("non-existing-service-name"); when(_store.getServiceByName("non-existing-service-name")).thenReturn(null); Assert.assertFalse(action.toString(), _validator.isValid(_policy, action, isAdmin, _failures)); // 2. update a policy to change the service-name RangerPolicy existingPolicy = mock(RangerPolicy.class); when(existingPolicy.getId()).thenReturn(8L); when(existingPolicy.getService()).thenReturn("service-name"); RangerService service = mock(RangerService.class); when(service.getType()).thenReturn("service-type"); when(service.getName()).thenReturn("service-name"); when(_store.getServiceByName("service-name")).thenReturn(service); RangerService service2 = mock(RangerService.class); when(service2.getType()).thenReturn("service-type"); when(service2.getName()).thenReturn("service-name2"); when(_store.getServiceByName("service-name2")).thenReturn(service2); when(_policy.getService()).thenReturn("service-name2"); when(_store.getServiceByName("service-name2")).thenReturn(service2); action = Action.UPDATE; Assert.assertFalse(action.toString(), _validator.isValid(_policy, action, isAdmin, _failures)); // 3. update a policy to change the policy-type when(existingPolicy.getId()).thenReturn(8L); when(existingPolicy.getService()).thenReturn("service-name"); when(existingPolicy.getPolicyType()).thenReturn(Integer.valueOf(0)); when(_policy.getId()).thenReturn(8L); when(_policy.getService()).thenReturn("service-name"); when(_policy.getPolicyType()).thenReturn(Integer.valueOf(1)); Assert.assertFalse(action.toString(), _validator.isValid(_policy, action, isAdmin, _failures)); } @Test public final void testIsValid_happyPath() throws Exception { // valid policy has valid non-empty name and service name when(_policy.getService()).thenReturn("service-name"); // service name exists RangerService service = mock(RangerService.class); when(service.getType()).thenReturn("service-type"); when(_store.getServiceByName("service-name")).thenReturn(service); // service points to a valid service-def _serviceDef = _utils.createServiceDefWithAccessTypes(accessTypes); when(_serviceDef.getName()).thenReturn("service-type"); when(_store.getServiceDefByName("service-type")).thenReturn(_serviceDef); // a matching policy should exist for create when checked by id and not exist when checked by name. when(_store.getPolicy(7L)).thenReturn(null); RangerPolicy existingPolicy = mock(RangerPolicy.class); when(existingPolicy.getId()).thenReturn(8L); when(existingPolicy.getService()).thenReturn("service-name"); when(_store.getPolicy(8L)).thenReturn(existingPolicy); SearchFilter createFilter = new SearchFilter(); createFilter.setParam(SearchFilter.SERVICE_TYPE, "service-type"); createFilter.setParam(SearchFilter.POLICY_NAME, "policy-name-1"); // this name would be used for create when(_store.getPolicies(createFilter)).thenReturn(new ArrayList<RangerPolicy>()); // a matching policy should not exist for update. SearchFilter updateFilter = new SearchFilter(); updateFilter.setParam(SearchFilter.SERVICE_TYPE, "service-type"); updateFilter.setParam(SearchFilter.POLICY_NAME, "policy-name-2"); // this name would be used for update List<RangerPolicy> existingPolicies = new ArrayList<>(); existingPolicies.add(existingPolicy); when(_store.getPolicies(updateFilter)).thenReturn(existingPolicies); // valid policy can have empty set of policy items if audit is turned on // null value for audit is treated as audit on. // for now we want to turn any resource related checking off when(_policy.getResources()).thenReturn(null); for (Action action : cu) { for (Boolean auditEnabled : new Boolean[] { null, true } ) { for (boolean isAdmin : new boolean[] { true, false }) { when(_policy.getIsAuditEnabled()).thenReturn(auditEnabled); if (action == Action.CREATE) { when(_policy.getId()).thenReturn(7L); when(_policy.getName()).thenReturn("policy-name-1"); Assert.assertTrue("" + action + ", " + auditEnabled, _validator.isValid(_policy, action, isAdmin, _failures)); Assert.assertTrue(_failures.isEmpty()); } else { // update should work both when by-name is found or not, since nothing found by-name means name is being updated. when(_policy.getId()).thenReturn(8L); when(_policy.getName()).thenReturn("policy-name-1"); Assert.assertTrue("" + action + ", " + auditEnabled, _validator.isValid(_policy, action, isAdmin, _failures)); Assert.assertTrue(_failures.isEmpty()); when(_policy.getName()).thenReturn("policy-name-2"); Assert.assertTrue("" + action + ", " + auditEnabled, _validator.isValid(_policy, action, isAdmin, _failures)); Assert.assertTrue(_failures.isEmpty()); } } } } // if audit is disabled then policy should have policy items and all of them should be valid List<RangerPolicyItem> policyItems = _utils.createPolicyItems(policyItemsData); when(_policy.getPolicyItems()).thenReturn(policyItems); when(_policy.getIsAuditEnabled()).thenReturn(false); for (Action action : cu) { for (boolean isAdmin : new boolean[] { true, false}) { if (action == Action.CREATE) { when(_policy.getId()).thenReturn(7L); when(_policy.getName()).thenReturn("policy-name-1"); } else { when(_policy.getId()).thenReturn(8L); when(_policy.getName()).thenReturn("policy-name-2"); } Assert.assertTrue("" + action , _validator.isValid(_policy, action, isAdmin, _failures)); Assert.assertTrue(_failures.isEmpty()); } } // above succeeded as service def did not have any resources on it, mandatory or otherwise. // policy should have all mandatory resources specified, and they should conform to the validation pattern in resource definition List<RangerResourceDef> resourceDefs = _utils.createResourceDefs(resourceDefData); when(_serviceDef.getResources()).thenReturn(resourceDefs); Map<String, RangerPolicyResource> resourceMap = _utils.createPolicyResourceMap(policyResourceMap_good); when(_policy.getResources()).thenReturn(resourceMap); // let's add some other policies in the store for this service that have a different signature // setup the signatures on the policies RangerPolicyResourceSignature policySignature = mock(RangerPolicyResourceSignature.class); when(_factory.createPolicyResourceSignature(_policy)).thenReturn(policySignature); // setup the store to indicate that no other policy exists with matching signature when(policySignature.getSignature()).thenReturn("hash-1"); when(_store.getPoliciesByResourceSignature("service-name", "hash-1", true)).thenReturn(null); // we are reusing the same policies collection here -- which is fine for (Action action : cu) { if (action == Action.CREATE) { when(_policy.getId()).thenReturn(7L); when(_policy.getName()).thenReturn("policy-name-1"); } else { when(_policy.getId()).thenReturn(8L); when(_policy.getName()).thenReturn("policy-name-2"); } Assert.assertTrue("" + action , _validator.isValid(_policy, action, true, _failures)); // since policy resource has excludes admin privilages would be required Assert.assertTrue(_failures.isEmpty()); } } void checkFailure_isValid(Action action, String errorType, String field) { checkFailure_isValid(action, errorType, field, null); } void checkFailure_isValid(Action action, String errorType, String field, String subField) { for (boolean isAdmin : new boolean[] { true, false}) { _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); switch (errorType) { case "missing": _utils.checkFailureForMissingValue(_failures, field, subField); break; case "semantic": _utils.checkFailureForSemanticError(_failures, field, subField); break; case "internal error": _utils.checkFailureForInternalError(_failures); break; default: Assert.fail("Unsupported errorType[" + errorType + "]"); break; } } } @Test public final void testIsValid_failures() throws Exception { for (Action action : cu) { // passing in a null policy should fail with appropriate failure reason _policy = null; checkFailure_isValid(action, "missing", "policy"); // policy must have a name on it _policy = mock(RangerPolicy.class); for (String name : new String[] { null, " " }) { when(_policy.getName()).thenReturn(name); when(_policy.getResources()).thenReturn(null); checkFailure_isValid(action, "missing", "name"); } // for update id is required! if (action == Action.UPDATE) { when(_policy.getId()).thenReturn(null); checkFailure_isValid(action, "missing", "id"); } } /* * Id is ignored for Create but name should not belong to an existing policy. For update, policy should exist for its id and should match its name. */ when(_policy.getName()).thenReturn("policy-name"); when(_policy.getService()).thenReturn("service-name"); RangerPolicy existingPolicy = mock(RangerPolicy.class); when(existingPolicy.getId()).thenReturn(7L); List<RangerPolicy> existingPolicies = new ArrayList<>(); existingPolicies.add(existingPolicy); SearchFilter filter = new SearchFilter(); filter.setParam(SearchFilter.SERVICE_NAME, "service-name"); filter.setParam(SearchFilter.POLICY_NAME, "policy-name"); when(_store.getPolicies(filter)).thenReturn(existingPolicies); checkFailure_isValid(Action.CREATE, "semantic", "policy name"); // update : does not exist for id when(_policy.getId()).thenReturn(7L); when(_store.getPolicy(7L)).thenReturn(null); checkFailure_isValid(Action.UPDATE, "semantic", "id"); // Update: name should not point to an existing different policy, i.e. with a different id when(_store.getPolicy(7L)).thenReturn(existingPolicy); RangerPolicy anotherExistingPolicy = mock(RangerPolicy.class); when(anotherExistingPolicy.getId()).thenReturn(8L); existingPolicies.clear(); existingPolicies.add(anotherExistingPolicy); when(_store.getPolicies(filter)).thenReturn(existingPolicies); checkFailure_isValid(Action.UPDATE, "semantic", "id/name"); // more than one policies with same name is also an internal error when(_policy.getName()).thenReturn("policy-name"); when(_store.getPolicies(filter)).thenReturn(existingPolicies); existingPolicies.add(existingPolicy); existingPolicy = mock(RangerPolicy.class); existingPolicies.add(existingPolicy); for (boolean isAdmin : new boolean[] { true, false }) { _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, Action.UPDATE, isAdmin, _failures)); _utils.checkFailureForInternalError(_failures); } // policy must have service name on it and it should be valid when(_policy.getName()).thenReturn("policy-name"); for (Action action : cu) { for (boolean isAdmin : new boolean[] { true, false }) { when(_policy.getService()).thenReturn(null); _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); _utils.checkFailureForMissingValue(_failures, "service name"); when(_policy.getService()).thenReturn(""); _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); _utils.checkFailureForMissingValue(_failures, "service name"); } } // service name should be valid when(_store.getServiceByName("service-name")).thenReturn(null); when(_store.getServiceByName("another-service-name")).thenThrow(new Exception()); for (Action action : cu) { for (boolean isAdmin : new boolean[] { true, false }) { when(_policy.getService()).thenReturn(null); _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); _utils.checkFailureForMissingValue(_failures, "service name"); when(_policy.getService()).thenReturn(null); _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); _utils.checkFailureForMissingValue(_failures, "service name"); when(_policy.getService()).thenReturn("service-name"); _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); _utils.checkFailureForSemanticError(_failures, "service name"); when(_policy.getService()).thenReturn("another-service-name"); _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); _utils.checkFailureForSemanticError(_failures, "service name"); } } // policy must contain at least one policy item List<RangerPolicyItem> policyItems = new ArrayList<>(); when(_policy.getService()).thenReturn("service-name"); RangerService service = mock(RangerService.class); when(_store.getServiceByName("service-name")).thenReturn(service); for (Action action : cu) { for (boolean isAdmin : new boolean[] { true, false }) { // when it is null when(_policy.getPolicyItems()).thenReturn(null); _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); _utils.checkFailureForMissingValue(_failures, "policy items"); // or when it is not null but empty. when(_policy.getPolicyItems()).thenReturn(policyItems); _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); _utils.checkFailureForMissingValue(_failures, "policy items"); } } // these are known good policy items -- same as used above in happypath policyItems = _utils.createPolicyItems(policyItemsData); when(_policy.getPolicyItems()).thenReturn(policyItems); // policy item check requires that service def should exist when(service.getType()).thenReturn("service-type"); when(_store.getServiceDefByName("service-type")).thenReturn(null); for (Action action : cu) { for (boolean isAdmin : new boolean[] { true, false }) { _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); _utils.checkFailureForInternalError(_failures, "policy service def"); } } // service-def should contain the right access types on it. _serviceDef = _utils.createServiceDefWithAccessTypes(accessTypes_bad, "service-type"); when(_store.getServiceDefByName("service-type")).thenReturn(_serviceDef); for (Action action : cu) { for (boolean isAdmin : new boolean[] { true, false }) { _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); _utils.checkFailureForSemanticError(_failures, "policy item access type"); } } // create the right service def with right resource defs - this is the same as in the happypath test above. _serviceDef = _utils.createServiceDefWithAccessTypes(accessTypes, "service-type"); when(_store.getPolicies(filter)).thenReturn(null); List<RangerResourceDef> resourceDefs = _utils.createResourceDefs(resourceDefData); when(_serviceDef.getResources()).thenReturn(resourceDefs); when(_store.getServiceDefByName("service-type")).thenReturn(_serviceDef); // one mandatory is missing (tbl) and one unknown resource is specified (extra), and values of option resource don't conform to validation pattern (col) Map<String, RangerPolicyResource> policyResources = _utils.createPolicyResourceMap(policyResourceMap_bad); when(_policy.getResources()).thenReturn(policyResources); // ensure thta policy is kosher when it comes to resource signature RangerPolicyResourceSignature signature = mock(RangerPolicyResourceSignature.class); when(_factory.createPolicyResourceSignature(_policy)).thenReturn(signature); when(signature.getSignature()).thenReturn("hash-1"); when(_store.getPoliciesByResourceSignature("service-name", "hash-1", true)).thenReturn(null); // store does not have any policies for that signature hash for (Action action : cu) { for (boolean isAdmin : new boolean[] { true, false }) { _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); _utils.checkFailureForSemanticError(_failures, "resource-values", "col"); // for spurious resource: "extra" _utils.checkFailureForSemanticError(_failures, "isRecursive", "db"); // for specifying it as true when def did not allow it _utils.checkFailureForSemanticError(_failures, "isExcludes", "col"); // for specifying it as true when def did not allow it } } // Check if error around resource signature clash are reported. have Store return policies for same signature when(_store.getPoliciesByResourceSignature("service-name", "hash-1", true)).thenReturn(existingPolicies); for (Action action : cu) { for (boolean isAdmin : new boolean[] { true, false }) { _failures.clear(); Assert.assertFalse(_validator.isValid(_policy, action, isAdmin, _failures)); _utils.checkFailureForSemanticError(_failures, "policy resources"); } } } RangerPolicy anyPolicy() { return argThat(new ArgumentMatcher<RangerPolicy>() { @Override public boolean matches(Object argument) { return true; } }); } @Test public void test_isValidResourceValues() { List<RangerResourceDef> resourceDefs = _utils.createResourceDefs(resourceDefData); when(_serviceDef.getResources()).thenReturn(resourceDefs); Map<String, RangerPolicyResource> policyResources = _utils.createPolicyResourceMap(policyResourceMap_bad); Assert.assertFalse(_validator.isValidResourceValues(policyResources, _failures, _serviceDef)); _utils.checkFailureForSemanticError(_failures, "resource-values", "col"); policyResources = _utils.createPolicyResourceMap(policyResourceMap_good); Assert.assertTrue(_validator.isValidResourceValues(policyResources, _failures, _serviceDef)); } @Test public void test_isValidPolicyItems_failures() { // null/empty list is good because there is nothing Assert.assertTrue(_validator.isValidPolicyItems(null, _failures, _serviceDef)); _failures.isEmpty(); List<RangerPolicyItem> policyItems = new ArrayList<>(); Assert.assertTrue(_validator.isValidPolicyItems(policyItems, _failures, _serviceDef)); _failures.isEmpty(); // null elements in the list are flagged policyItems.add(null); Assert.assertFalse(_validator.isValidPolicyItems(policyItems, _failures, _serviceDef)); _utils.checkFailureForMissingValue(_failures, "policy item"); } @Test public void test_isValidPolicyItem_failures() { // empty access collections are invalid RangerPolicyItem policyItem = mock(RangerPolicyItem.class); when(policyItem.getAccesses()).thenReturn(null); _failures.clear(); Assert.assertFalse(_validator.isValidPolicyItem(policyItem, _failures, _serviceDef)); _utils.checkFailureForMissingValue(_failures, "policy item accesses"); List<RangerPolicyItemAccess> accesses = new ArrayList<>(); when(policyItem.getAccesses()).thenReturn(accesses); _failures.clear(); Assert.assertFalse(_validator.isValidPolicyItem(policyItem, _failures, _serviceDef)); _utils.checkFailureForMissingValue(_failures, "policy item accesses"); // both user and groups can't be null RangerPolicyItemAccess access = mock(RangerPolicyItemAccess.class); accesses.add(access); when(policyItem.getUsers()).thenReturn(null); when(policyItem.getGroups()).thenReturn(new ArrayList<String>()); _failures.clear(); Assert.assertFalse(_validator.isValidPolicyItem(policyItem, _failures, _serviceDef)); _utils.checkFailureForMissingValue(_failures, "policy item users/user-groups"); } @Test public void test_isValidPolicyItem_happPath() { // A policy item with no access is valid if it has delegated admin turned on and one user/group specified. RangerPolicyItem policyItem = mock(RangerPolicyItem.class); when(policyItem.getAccesses()).thenReturn(null); when(policyItem.getDelegateAdmin()).thenReturn(true); // create a non-empty user-list List<String> users = Arrays.asList("user1"); when(policyItem.getUsers()).thenReturn(users); _failures.clear(); Assert.assertTrue(_validator.isValidPolicyItem(policyItem, _failures, _serviceDef)); Assert.assertTrue(_failures.isEmpty()); } @Test public void test_isValidItemAccesses_happyPath() { // happy path Object[][] data = new Object[][] { { "a", null }, // valid { "b", true }, // valid { "c", true }, // valid }; List<RangerPolicyItemAccess> accesses = _utils.createItemAccess(data); _serviceDef = _utils.createServiceDefWithAccessTypes(new String[] { "a", "b", "c", "d" }); Assert.assertTrue(_validator.isValidItemAccesses(accesses, _failures, _serviceDef)); Assert.assertTrue(_failures.isEmpty()); } @Test public void test_isValidItemAccesses_failure() { // null policy item access values are an error List<RangerPolicyItemAccess> accesses = new ArrayList<>(); accesses.add(null); _failures.clear(); Assert.assertFalse(_validator.isValidItemAccesses(accesses, _failures, _serviceDef)); _utils.checkFailureForMissingValue(_failures, "policy item access"); // all items must be valid for this call to be valid Object[][] data = new Object[][] { { "a", null }, // valid { null, null }, // invalid - name can't be null { "c", true }, // valid }; accesses = _utils.createItemAccess(data); _serviceDef = _utils.createServiceDefWithAccessTypes(new String[] { "a", "b", "c", "d" }); _failures.clear(); Assert.assertFalse(_validator.isValidItemAccesses(accesses, _failures, _serviceDef)); } @Test public void test_isValidPolicyItemAccess_happyPath() { RangerPolicyItemAccess access = mock(RangerPolicyItemAccess.class); when(access.getType()).thenReturn("an-Access"); // valid Set<String> validAccesses = Sets.newHashSet(new String[] { "an-access", "another-access" }); // valid accesses should be lower-cased // both null or true access types are the same and valid for (Boolean allowed : new Boolean[] { null, true } ) { when(access.getIsAllowed()).thenReturn(allowed); Assert.assertTrue(_validator.isValidPolicyItemAccess(access, _failures, validAccesses)); Assert.assertTrue(_failures.isEmpty()); } } @Test public void test_isValidPolicyItemAccess_failures() { Set<String> validAccesses = Sets.newHashSet(new String[] { "anAccess", "anotherAccess" }); // null/empty names are invalid RangerPolicyItemAccess access = mock(RangerPolicyItemAccess.class); when(access.getIsAllowed()).thenReturn(null); // valid since null == true for (String type : new String[] { null, " "}) { when(access.getType()).thenReturn(type); // invalid // null/empty validAccess set skips all checks Assert.assertTrue(_validator.isValidPolicyItemAccess(access, _failures, null)); Assert.assertTrue(_validator.isValidPolicyItemAccess(access, _failures, new HashSet<String>())); _failures.clear(); Assert.assertFalse(_validator.isValidPolicyItemAccess(access, _failures, validAccesses)); _utils.checkFailureForMissingValue(_failures, "policy item access type"); } when(access.getType()).thenReturn("anAccess"); // valid when(access.getIsAllowed()).thenReturn(false); // invalid _failures.clear();Assert.assertFalse(_validator.isValidPolicyItemAccess(access, _failures, validAccesses)); _utils.checkFailureForSemanticError(_failures, "policy item access type allowed"); when(access.getType()).thenReturn("newAccessType"); // invalid _failures.clear(); Assert.assertFalse(_validator.isValidPolicyItemAccess(access, _failures, validAccesses)); _utils.checkFailureForSemanticError(_failures, "policy item access type"); } final Object[][] resourceDef_happyPath = new Object[][] { // { "resource-name", "isExcludes", "isRecursive" } { "db", true, true }, { "tbl", null, true }, { "col", true, false }, }; private Object[][] policyResourceMap_happyPath = new Object[][] { // { "resource-name", "values" "isExcludes", "isRecursive" } // values collection is null as it isn't relevant to the part being tested with this data { "db", null, null, true }, // null should be treated as false { "tbl", null, false, false }, // set to false where def is null and def is true { "col", null, true, null} // set to null where def is false }; @Test public final void test_isValidResourceFlags_happyPath() { Map<String, RangerPolicyResource> resourceMap = _utils.createPolicyResourceMap(policyResourceMap_happyPath); List<RangerResourceDef> resourceDefs = _utils.createResourceDefs(resourceDef_happyPath); when(_serviceDef.getResources()).thenReturn(resourceDefs); Assert.assertTrue(_validator.isValidResourceFlags(resourceMap, _failures, resourceDefs, "a-service-def", "a-policy", true)); // Since one of the resource has excludes set to true, without admin privilages it should fail and contain appropriate error messages Assert.assertFalse(_validator.isValidResourceFlags(resourceMap, _failures, resourceDefs, "a-service-def", "a-policy", false)); _utils.checkFailureForSemanticError(_failures, "isExcludes", "isAdmin"); } private Object[][] policyResourceMap_failures = new Object[][] { // { "resource-name", "values" "isExcludes", "isRecursive" } // values collection is null as it isn't relevant to the part being tested with this data { "db", null, true, true }, // ok: def has true for both { "tbl", null, true, null }, // excludes: definition does not allow excludes by resource has it set to true { "col", null, false, true } // recursive: def==null (i.e. false), policy==true }; @Test public final void test_isValidResourceFlags_failures() { // passing true when def says false/null List<RangerResourceDef> resourceDefs = _utils.createResourceDefs(resourceDef_happyPath); Map<String, RangerPolicyResource> resourceMap = _utils.createPolicyResourceMap(policyResourceMap_failures); when(_serviceDef.getResources()).thenReturn(resourceDefs); // should not error out on Assert.assertFalse(_validator.isValidResourceFlags(resourceMap, _failures, resourceDefs, "a-service-def", "a-policy", false)); _utils.checkFailureForSemanticError(_failures, "isExcludes", "tbl"); _utils.checkFailureForSemanticError(_failures, "isRecursive", "col"); _utils.checkFailureForSemanticError(_failures, "isExcludes", "isAdmin"); } @Test public final void test_isPolicyResourceUnique() throws Exception { // if store does not contain any matching policies then check should succeed RangerPolicyResourceSignature signature = mock(RangerPolicyResourceSignature.class); String hash = "hash-1"; when(signature.getSignature()).thenReturn(hash); when(_factory.createPolicyResourceSignature(_policy)).thenReturn(signature); when(_policy.getService()).thenReturn("service-name"); List<RangerPolicy> policies = null; when(_store.getPoliciesByResourceSignature("service-name", hash, true)).thenReturn(policies); policies = new ArrayList<>(); for (Action action : cu) { Assert.assertTrue(_validator.isPolicyResourceUnique(_policy, _failures, action)); Assert.assertTrue(_validator.isPolicyResourceUnique(_policy, _failures, action)); } /* * If store has a policy with matching signature then the check should fail with appropriate error message. * - For create any match is a problem * - Signature check can never fail for disabled policies! */ RangerPolicy policy1 = mock(RangerPolicy.class); policies.add(policy1); when(_store.getPoliciesByResourceSignature("service-name", hash, true)).thenReturn(policies); when(_policy.getIsEnabled()).thenReturn(true); // ensure policy is enabled _failures.clear(); Assert.assertFalse(_validator.isPolicyResourceUnique(_policy, _failures, Action.CREATE)); _utils.checkFailureForSemanticError(_failures, "resources"); // same check should pass if the policy is disabled when(_policy.getIsEnabled()).thenReturn(false); _failures.clear(); Assert.assertTrue(_validator.isPolicyResourceUnique(_policy, _failures, Action.CREATE)); Assert.assertTrue("failures collection wasn't empty!", _failures.isEmpty()); // For Update match with itself is not a problem as long as it isn't itself, i.e. same id. when(_policy.getIsEnabled()).thenReturn(true); // ensure policy is enabled when(policy1.getId()).thenReturn(103L); when(_policy.getId()).thenReturn(103L); Assert.assertTrue(_validator.isPolicyResourceUnique(_policy, _failures, Action.UPDATE)); // matching policy can't be some other policy (i.e. different id) because that implies a conflict. when(policy1.getId()).thenReturn(104L); Assert.assertFalse(_validator.isPolicyResourceUnique(_policy, _failures, Action.UPDATE)); _utils.checkFailureForSemanticError(_failures, "resources"); // same check should pass if the policy is disabled when(_policy.getIsEnabled()).thenReturn(false); _failures.clear(); Assert.assertTrue(_validator.isPolicyResourceUnique(_policy, _failures, Action.UPDATE)); Assert.assertTrue("failures collection wasn't empty!", _failures.isEmpty()); // And validation should never pass if there are more than one policies with matching signature, regardless of their ID!! RangerPolicy policy2 = mock(RangerPolicy.class); when(policy2.getId()).thenReturn(103L); // has same id as the policy being tested (_policy) policies.add(policy2); when(_policy.getIsEnabled()).thenReturn(true); // ensure policy is enabled Assert.assertFalse(_validator.isPolicyResourceUnique(_policy, _failures, Action.UPDATE)); _utils.checkFailureForSemanticError(_failures, "resources"); // same check should pass if the policy is disabled when(_policy.getIsEnabled()).thenReturn(false); _failures.clear(); Assert.assertTrue(_validator.isPolicyResourceUnique(_policy, _failures, Action.UPDATE)); Assert.assertTrue("failures collection wasn't empty!", _failures.isEmpty()); } @Test public final void test_isValidResourceNames_happyPath() { String serviceName = "a-service-def"; // setup service-def Date now = new Date(); when(_serviceDef.getName()).thenReturn(serviceName ); when(_serviceDef.getUpdateTime()).thenReturn(now); List<RangerResourceDef> resourceDefs = _utils.createResourceDefs(resourceDefData_multipleHierarchies); when(_serviceDef.getResources()).thenReturn(resourceDefs); // setup policy Map<String, RangerPolicyResource> policyResources = _utils.createPolicyResourceMap(policyResourceMap_goodMultipleHierarchies); when(_policy.getResources()).thenReturn(policyResources); Assert.assertTrue(_validator.isValidResourceNames(_policy, _failures, _serviceDef)); } @Test public final void test_isValidResourceNames_failures() { String serviceName = "a-service-def"; // setup service-def Date now = new Date(); when(_serviceDef.getName()).thenReturn(serviceName ); when(_serviceDef.getUpdateTime()).thenReturn(now); List<RangerResourceDef> resourceDefs = _utils.createResourceDefs(resourceDefData_multipleHierarchies); when(_serviceDef.getResources()).thenReturn(resourceDefs); // setup policy Map<String, RangerPolicyResource> policyResources = _utils.createPolicyResourceMap(policyResourceMap_bad); when(_policy.getResources()).thenReturn(policyResources); Assert.assertFalse("Missing required resource and unknown resource", _validator.isValidResourceNames(_policy, _failures, _serviceDef)); _utils.checkFailureForSemanticError(_failures, "policy resources"); // another bad resource map that straddles multiple hierarchies policyResources = _utils.createPolicyResourceMap(policyResourceMap_bad_multiple_hierarchies); when(_policy.getResources()).thenReturn(policyResources); _failures.clear(); Assert.assertFalse("Policy with resources for multiple hierarchies", _validator.isValidResourceNames(_policy, _failures, _serviceDef)); _utils.checkFailureForSemanticError(_failures, "policy resources", "incompatible"); // another bad policy resource map that could match multiple hierarchies but is short on mandatory resources for all of those matches policyResources = _utils.createPolicyResourceMap(policyResourceMap_bad_multiple_hierarchies_missing_mandatory); when(_policy.getResources()).thenReturn(policyResources); _failures.clear(); Assert.assertFalse("Policy with resources for multiple hierarchies missing mandatory resources for all pontential matches", _validator.isValidResourceNames(_policy, _failures, _serviceDef)); _utils.checkFailureForSemanticError(_failures, "policy resources", "missing mandatory"); } private ValidationTestUtils _utils = new ValidationTestUtils(); private List<ValidationFailureDetails> _failures = new ArrayList<ValidationFailureDetails>(); private ServiceStore _store; private RangerPolicy _policy; private RangerPolicyValidator _validator; private RangerServiceDef _serviceDef; private RangerObjectFactory _factory; }