def test_not_allowed_when_similar_policies_have_at_least_one_deny_access(): st = MemoryStorage() policies = ( Policy( uid='1', effect=ALLOW_ACCESS, subjects=['foo'], actions=['bar'], resources=['baz'], ), Policy( uid='2', effect=DENY_ACCESS, subjects=['foo'], actions=['bar'], resources=['baz'], ), ) for p in policies: st.add(p) g = Guard(st, RegexChecker()) assert not g.is_allowed( Inquiry(subject='foo', action='bar', resource='baz'))
def test_find_for_inquiry_with_regex_checker_for_mongodb_prior_to_4_2( self, st): # mock db server version for this test st.db_server_version = (3, 4, 0) st.add(Policy('1', subjects=['<[mM]ax>', '<.*>'])) st.add(Policy('2', subjects=['sam<.*>', 'foo'])) st.add( Policy('3', subjects=['Jim'], actions=['delete'], resources=['server'])) st.add( Policy('3.1', subjects=['Jim'], actions=[r'del<\w+>'], resources=['server'])) st.add(Policy('4', subjects=[{'stars': Eq(90)}, Eq('Max')])) st.add(Policy('5', subjects=[Eq('Jim'), Eq('Nina')])) inquiry = Inquiry(subject='Jim', action='delete', resource='server') found = st.find_for_inquiry(inquiry, RegexChecker()) found = list(found) # should return all string-based polices, but not only matched ones assert 4 == len(found) assert ['1', '2', '3', '3.1'] == sorted(map(attrgetter('uid'), found))
class TestRedisStorage: # We test storage with all available serializers @pytest.fixture(params=[JSONSerializer(), PickleSerializer()]) def st(self, request): client = create_client() yield RedisStorage(client, collection=COLLECTION, serializer=request.param) client.flushdb() client.close() @pytest.fixture() def log(self): al = logging.getLogger('vakt.storage.redis') initial_handlers = al.handlers[:] initial_level = al.getEffectiveLevel() yield al al.handlers = initial_handlers al.setLevel(initial_level) def test_storage_uses_pickle_serializer_by_default(self): client = create_client() try: s = RedisStorage(client, collection=COLLECTION) sr = s.sr assert isinstance(sr, PickleSerializer) finally: client.close() def test_add(self, st): id = str(uuid.uuid4()) p = Policy( uid=id, description='foo bar баз', subjects=('Edward Rooney', 'Florence Sparrow'), actions=['好'], resources=['<.*>'], context={ 'secret': Equal('i-am-a-teacher'), 'rating': And(Eq(80), Greater(80)) }, ) st.add(p) back = st.get(id) assert id == back.uid assert 'foo bar баз' == back.description assert isinstance(back.context['secret'], Equal) assert isinstance(back.context['rating'], And) assert '好' == back.actions[0] st.add( Policy('2', actions=[Eq('get'), Eq('put')], subjects=[Any()], resources=[{ 'books': Eq('Harry') }])) assert '2' == st.get('2').uid assert 2 == len(st.get('2').actions) assert 1 == len(st.get('2').subjects) assert isinstance(st.get('2').subjects[0], Any) assert 1 == len(st.get('2').resources) assert isinstance(st.get('2').resources[0]['books'], Eq) assert 'Harry' == st.get('2').resources[0]['books'].val def test_policy_create_existing(self, st): uid = str(uuid.uuid4()) st.add(Policy(uid, description='foo')) with pytest.raises(PolicyExistsError): st.add(Policy(uid, description='bar')) def test_get(self, st): st.add(Policy('1')) st.add(Policy(2, description='some text')) assert isinstance(st.get('1'), Policy) assert '1' == st.get('1').uid assert 2 == st.get(2).uid assert 'some text' == st.get(2).description def test_get_nonexistent(self, st): assert None is st.get(123456789) @pytest.mark.parametrize('limit, offset, result', [ (50, 0, 20), (11, 1, 11), (50, 5, 15), (20, 0, 20), (20, 1, 19), (19, 0, 19), (20, 5, 15), (0, 0, 0), (0, 10, 0), (1, 0, 1), (5, 4, 5), (10000, 0, 20), (5, 18, 2), (5, 19, 1), (5, 20, 0), (5, 21, 0), ]) def test_get_all(self, st, limit, offset, result): for i in range(20): desc = ''.join(random.choice('abcde') for _ in range(30)) st.add(Policy(str(i), description=desc)) policies = list(st.get_all(limit=limit, offset=offset)) ll = len(policies) assert result == ll def test_get_all_for_empty_database(self, st): assert [] == list(st.get_all(limit=100, offset=0)) def test_get_all_check_policy_properties(self, st): p = Policy( uid='1', description='foo bar баз', subjects=('Edward Rooney', 'Florence Sparrow'), actions=['<.*>'], resources=['<.*>'], context={ 'secret': Equal('i-am-a-teacher'), }, ) st.add(p) policies = list(st.get_all(100, 0)) assert 1 == len(policies) assert '1' == policies[0].uid assert 'foo bar баз' == policies[0].description assert ['Edward Rooney', 'Florence Sparrow'] == policies[0].subjects or \ ('Edward Rooney', 'Florence Sparrow') == policies[0].subjects assert ['<.*>'] == policies[0].actions assert ['<.*>'] == policies[0].resources assert isinstance(policies[0].context['secret'], Equal) def test_get_all_with_incorrect_args(self, st): for i in range(10): st.add(Policy(str(i), description='foo')) with pytest.raises(ValueError) as e: list(st.get_all(-1, 9)) assert "Limit can't be negative" == str(e.value) with pytest.raises(ValueError) as e: list(st.get_all(0, -3)) assert "Offset can't be negative" == str(e.value) def test_get_all_returns_generator(self, st): st.add(Policy('1')) st.add(Policy('2')) found = st.get_all(500, 0) assert isinstance(found, types.GeneratorType) pols = [] for p in found: pols.append(p.uid) assert 2 == len(pols) @pytest.mark.parametrize('checker, expect_number', [ (None, 6), (RegexChecker(), 6), (RulesChecker(), 6), (StringExactChecker(), 6), (StringFuzzyChecker(), 6), ]) def test_find_for_inquiry_returns_all_policies_for_any_checker_and_inquiry( self, st, checker, expect_number): st.add(Policy('1', subjects=['<[mM]ax>', '<.*>'])) st.add(Policy('2', subjects=['sam<.*>', 'foo'])) st.add( Policy('3', subjects=['Jim'], actions=['delete'], resources=['server'])) st.add( Policy('3.1', subjects=['Jim'], actions=[r'del<\w+>'], resources=['server'])) st.add(Policy('4', subjects=[{'stars': Eq(90)}, Eq('Max')])) st.add(Policy('5', subjects=[Eq('Jim'), Eq('Nina')])) inquiry = Inquiry(subject='Jim', action='delete', resource='server') found = st.find_for_inquiry(inquiry, checker) assert expect_number == len(list(found)) def test_find_for_inquiry_for_empty_database(self, st): assert [] == list(st.find_for_inquiry(Inquiry(), RegexChecker())) def test_find_for_inquiry_returns_generator(self, st): st.add( Policy('1', subjects=['max', 'bob'], actions=['get'], resources=['comics'])) st.add( Policy('2', subjects=['max', 'bob'], actions=['get'], resources=['comics'])) inquiry = Inquiry(subject='max', action='get', resource='comics') found = st.find_for_inquiry(inquiry) assert isinstance(found, types.GeneratorType) pols = [] for p in found: pols.append(p.uid) assert 2 == len(pols) def test_find_for_inquiry_returns_empty_list_if_nothing_is_returned( self, st): inquiry = Inquiry(subject='max', action='get', resource='comics') assert [] == st.find_for_inquiry(inquiry) mock_client = Mock(spec=Redis, **{'hgetall.side_effect': [None]}) st.client = mock_client assert [] == st.find_for_inquiry(inquiry) def test_update(self, st): id = str(uuid.uuid4()) policy = Policy(id) st.add(policy) assert id == st.get(id).uid assert None is st.get(id).description assert () == st.get(id).actions or [] == st.get(id).actions policy.description = 'foo' policy.actions = ['a', 'b', 'c'] st.update(policy) assert id == st.get(id).uid assert 'foo' == st.get(id).description assert ['a', 'b', 'c'] == st.get(id).actions p = Policy(2, actions=[Any()], subjects=[Eq('max'), Eq('bob')]) st.add(p) assert 2 == st.get(2).uid p.actions = [Eq('get')] st.update(p) assert 1 == len(st.get(2).actions) assert 'get' == st.get(2).actions[0].val def test_update_non_existing_does_not_create_anything(self, st, log): log_capture_str = io.StringIO() h = logging.StreamHandler(log_capture_str) h.setLevel(logging.INFO) log.setLevel(logging.INFO) log.addHandler(h) # test uid = str(uuid.uuid4()) st.update(Policy(uid, actions=['get'], description='bar')) assert '' == log_capture_str.getvalue().strip() assert st.get(uid) is None def test_delete(self, st, log): policy = Policy('1') st.add(policy) assert '1' == st.get('1').uid log_capture_str = io.StringIO() h = logging.StreamHandler(log_capture_str) h.setLevel(logging.INFO) log.setLevel(logging.INFO) log.addHandler(h) # test st.delete('1') assert 'Deleted Policy with UID=1' == log_capture_str.getvalue().strip( ) assert None is st.get('1') def test_delete_nonexistent(self, st, log): log_capture_str = io.StringIO() h = logging.StreamHandler(log_capture_str) h.setLevel(logging.INFO) log.setLevel(logging.INFO) log.addHandler(h) # test uid = '123456789_not_here' st.delete(uid) assert 'Nothing to delete by UID=123456789_not_here' == log_capture_str.getvalue( ).strip() assert None is st.get(uid) def test_returned_condition(self, st): uid = str(uuid.uuid4()) p = Policy( uid=uid, context={ 'secret': Equal('i-am-a-teacher'), 'secret2': Equal('i-am-a-husband'), }, ) st.add(p) context = st.get(uid).context assert context['secret'].satisfied('i-am-a-teacher') assert context['secret2'].satisfied('i-am-a-husband') def test_custom_pickle_serializer_works(self, st): st.sr = PickleSerializer(3, fix_imports=False) st.add(Policy('1')) st.add(Policy(2, description='some text')) assert isinstance(st.get('1'), Policy) assert '1' == st.get('1').uid assert 2 == st.get(2).uid assert 'some text' == st.get(2).description
def test_find_for_inquiry_for_empty_database(self, st): assert [] == list(st.find_for_inquiry(Inquiry(), RegexChecker()))
class TestSQLStorage: @pytest.yield_fixture def session(self): engine = create_test_sql_engine() Base.metadata.create_all(engine) session = scoped_session(sessionmaker(bind=engine)) yield session Base.metadata.drop_all(engine) @pytest.yield_fixture def st(self, session): yield SQLStorage(scoped_session=session) session.remove() def test_add(self, st): id = str(uuid.uuid4()) p = Policy( uid=id, description='foo bar баз', subjects=('Edward Rooney', 'Florence Sparrow'), actions=['<.*>'], resources=['<.*>'], context={ 'secret': Equal('i-am-a-teacher'), }, ) st.add(p) back = st.get(id) assert id == back.uid assert 'foo bar баз' == back.description assert isinstance(back.context['secret'], Equal) st.add( Policy('2', actions=[Eq('get'), Eq('put')], subjects=[Any()], resources=[{ 'books': Eq('Harry') }])) assert '2' == st.get('2').uid assert 2 == len(st.get('2').actions) assert 1 == len(st.get('2').subjects) assert isinstance(st.get('2').subjects[0], Any) assert 1 == len(st.get('2').resources) assert isinstance(st.get('2').resources[0]['books'], Eq) assert 'Harry' == st.get('2').resources[0]['books'].val def test_add_with_bson_object_id(self, st): id = str(ObjectId()) p = Policy( uid=id, description='foo', ) st.add(p) back = st.get(id) assert id == back.uid def test_policy_create_existing(self, st): id = str(uuid.uuid4()) st.add(Policy(id, description='foo')) with pytest.raises(PolicyExistsError): st.add(Policy(id, description='bar')) def test_get(self, st): st.add(Policy('1')) st.add(Policy(2, description='some text')) assert isinstance(st.get('1'), Policy) assert '1' == st.get('1').uid # SQL storage stores all uid as string assert '2' == st.get('2').uid assert 'some text' == st.get('2').description def test_get_nonexistent(self, st): assert None is st.get('123456789') @pytest.mark.parametrize( 'limit, offset, result', [ (500, 0, 200), (101, 1, 101), (500, 50, 150), (200, 0, 200), (200, 1, 199), (199, 0, 199), (200, 50, 150), # (0, 0, 200), --> The output of this test should be 0 as checked by the following parameters (0, 0, 0), (1, 0, 1), (5, 4, 5), ]) def test_get_all(self, st, limit, offset, result): for i in range(200): desc = ''.join(random.choice('abcde') for _ in range(30)) st.add(Policy(str(i), description=desc)) policies = list(st.get_all(limit=limit, offset=offset)) assert result == len(policies) def test_get_all_check_policy_properties(self, st): p = Policy( uid='1', description='foo bar баз', subjects=('Edward Rooney', 'Florence Sparrow'), actions=['<.*>'], resources=['<.*>'], context={ 'secret': Equal('i-am-a-teacher'), }, ) st.add(p) policies = list(st.get_all(100, 0)) assert 1 == len(policies) assert '1' == policies[0].uid assert 'foo bar баз' == policies[0].description assert ['Edward Rooney', 'Florence Sparrow'] == policies[0].subjects assert ['<.*>'] == policies[0].actions assert ['<.*>'] == policies[0].resources assert isinstance(policies[0].context['secret'], Equal) def test_get_all_with_incorrect_args(self, st): for i in range(10): st.add(Policy(str(i), description='foo')) with pytest.raises(ValueError) as e: list(st.get_all(-1, 9)) assert "Limit can't be negative" == str(e.value) with pytest.raises(ValueError) as e: list(st.get_all(0, -3)) assert "Offset can't be negative" == str(e.value) def test_get_all_returns_generator(self, st): st.add(Policy('1')) st.add(Policy('2')) found = st.get_all(500, 0) assert isinstance(found, types.GeneratorType) l = [] for p in found: l.append(p.uid) assert 2 == len(l) @pytest.mark.parametrize('checker, expect_number', [ (None, 5), (RegexChecker(), 3), (RulesChecker(), 2), (StringExactChecker(), 1), (StringFuzzyChecker(), 1), ]) def test_find_for_inquiry_returns_existing_policies( self, st, checker, expect_number): st.add(Policy('1', subjects=['<[mM]ax', '<.*>'])) st.add(Policy('2', subjects=['sam<.*>', 'foo'])) st.add(Policy('3', subjects=[{'stars': Eq(90)}, Eq('Max')])) st.add( Policy('4', subjects=['Jim'], actions=['delete'], resources=['server'])) st.add(Policy('5', subjects=[Eq('Jim'), Eq('Nina')])) inquiry = Inquiry(subject='Jim', action='delete', resource='server') found = st.find_for_inquiry(inquiry, checker) assert expect_number == len(list(found)) def test_find_for_inquiry_with_exact_string_checker(self, st): st.add( Policy('1', subjects=['max', 'bob'], actions=['get'], resources=['books', 'comics', 'magazines'])) st.add( Policy('2', subjects=['maxim'], actions=['get'], resources=['books', 'comics', 'magazines'])) st.add(Policy('3', subjects=['sam', 'nina'])) st.add(Policy('4', subjects=[Eq('sam'), Eq('nina')])) inquiry = Inquiry(subject='max', action='get', resource='books') found = st.find_for_inquiry(inquiry, StringExactChecker()) found = list(found) assert 1 == len(found) assert '1' == found[0].uid def test_find_for_inquiry_with_fuzzy_string_checker(self, st): st.add( Policy('1', subjects=['max', 'bob'], actions=['get'], resources=['books', 'comics', 'magazines'])) st.add( Policy('2', subjects=['maxim'], actions=['get'], resources=['books', 'foos'])) st.add( Policy('3', subjects=['Max'], actions=['get'], resources=['books', 'comics'])) st.add(Policy('4', subjects=['sam', 'nina'])) st.add(Policy('5', subjects=[Eq('sam'), Eq('nina')])) inquiry = Inquiry(subject='max', action='et', resource='oo') found = st.find_for_inquiry(inquiry, StringFuzzyChecker()) found = list(found) assert 2 == len(found) ids = [found[0].uid, found[1].uid] assert '1' in ids assert '2' in ids inquiry = Inquiry(subject='Max', action='get', resource='comics') found = st.find_for_inquiry(inquiry, StringFuzzyChecker()) found = list(found) assert 1 == len(found) assert '3' == found[0].uid @pytest.mark.parametrize('policies, inquiry, expected_reference', [ ( [ Policy(uid=1, actions=['get', 'post'], effect=ALLOW_ACCESS, resources=['<.*>'], subjects=['<[Mm]ax>', '<Jim>']), ], Inquiry(action='get', resource='printer', subject='Max'), True, ), ( [ Policy(uid=1, actions=['<.*>'], effect=ALLOW_ACCESS, resources=['<.*>'], subjects=['<.*>']), ], Inquiry(action='get', resource='printer', subject='Max'), True, ), ( [ Policy(uid=1, actions=['<.*>'], effect=ALLOW_ACCESS, resources=['library:books:<.+>'], subjects=['<.*>']), ], Inquiry( action='get', resource='library:books:dracula', subject='Max'), True, ), ( [ Policy(uid=1, actions=[r'<\d+>'], effect=ALLOW_ACCESS, resources=[r'<\w{1,3}>'], subjects=[r'<\w{2}-\d+>']), ], Inquiry(action='12', resource='Pie', subject='Jo-1'), True, ), ]) def test_find_for_inquiry_with_regex_checker(self, st, policies, inquiry, expected_reference): mem_storage = MemoryStorage( ) # it returns all stored policies so we consider Guard as a reference for p in policies: st.add(p) mem_storage.add(p) reference_answer = Guard(mem_storage, RegexChecker()).is_allowed(inquiry) assert expected_reference == reference_answer, 'Check reference answer' assert reference_answer == Guard(st, RegexChecker()).is_allowed(inquiry), \ 'SQL storage should give the same answers as reference' def test_find_for_inquiry_with_rules_checker(self, st): assertions = unittest.TestCase('__init__') st.add( Policy(1, subjects=[{ 'name': Equal('Max') }], actions=[{ 'foo': Equal('bar') }])) st.add( Policy(2, subjects=[{ 'name': Equal('Max') }], actions=[{ 'foo': Equal('bar2') }])) st.add(Policy(3, subjects=['sam', 'nina'])) st.add( Policy(4, actions=[r'<\d+>'], effect=ALLOW_ACCESS, resources=[r'<\w{1,3}>'], subjects=[r'<\w{2}-\d+>'])) st.add( Policy(5, subjects=[{ 'name': Equal('Jim') }], actions=[{ 'foo': Equal('bar3') }])) inquiry = Inquiry(subject={'name': 'max'}, action='get', resource='books') found = st.find_for_inquiry(inquiry, RulesChecker()) found = list(found) assert 3 == len(found) # assertions.assertListEqual([1, 2, 5], list(map(operator.attrgetter('uid'), found))) --> SQL storage treats # UID as strings assertions.assertListEqual(['1', '2', '5'], list(map(operator.attrgetter('uid'), found))) def test_find_for_inquiry_with_unknown_checker(self, st): st.add(Policy('1')) inquiry = Inquiry(subject='sam', action='get', resource='books') with pytest.raises(UnknownCheckerType): list(st.find_for_inquiry(inquiry, Inquiry())) def test_find_for_inquiry_returns_generator(self, st): st.add( Policy('1', subjects=['max', 'bob'], actions=['get'], resources=['comics'])) st.add( Policy('2', subjects=['max', 'bob'], actions=['get'], resources=['comics'])) inquiry = Inquiry(subject='max', action='get', resource='comics') found = st.find_for_inquiry(inquiry) assert isinstance(found, types.GeneratorType) l = [] for p in found: l.append(p.uid) assert 2 == len(l) def test_update(self, st): # SQL storage stores all uids as string id = str(uuid.uuid4()) policy = Policy(id) st.add(policy) assert id == st.get(id).uid assert None is st.get(id).description assert [] == st.get(id).actions policy.description = 'foo' policy.actions = ['a', 'b', 'c'] st.update(policy) assert id == st.get(id).uid assert 'foo' == st.get(id).description assert ['a', 'b', 'c'] == st.get(id).actions p = Policy('2', actions=[Any()], subjects=[Eq('max'), Eq('bob')]) st.add(p) assert '2' == st.get('2').uid p.actions = [Eq('get')] st.update(p) assert 1 == len(st.get('2').actions) assert 'get' == st.get('2').actions[0].val def test_update_non_existing_does_not_create_anything(self, st): id = str(uuid.uuid4()) st.update(Policy(id, actions=['get'], description='bar')) assert st.get(id) is None def test_delete(self, st): policy = Policy('1') st.add(policy) assert '1' == st.get('1').uid st.delete('1') assert None is st.get('1') def test_delete_nonexistent(self, st): uid = str(ObjectId()) st.delete(uid) assert None is st.get(uid)
def test_is_allowed_for_none_policies(): g = Guard(MemoryStorage(), RegexChecker()) assert not g.is_allowed( Inquiry(subject='foo', action='bar', resource='baz'))
@pytest.fixture() def logger(): log = logging.getLogger('vakt.guard') initial_handlers = log.handlers[:] initial_level = log.getEffectiveLevel() yield log log.handlers = initial_handlers log.setLevel(initial_level) @pytest.mark.parametrize('desc, inquiry, should_be_allowed, checker', [ ( 'Empty inquiry carries no information, so nothing is allowed, even empty Policy #4', Inquiry(), False, RegexChecker(), ), ( 'Max is allowed to update anything', Inquiry(subject='Max', resource='myrn:example.com:resource:123', action='update'), True, RegexChecker(), ), ( 'Max is allowed to update anything, even empty one', Inquiry(subject='Max', resource='', action='update'), True, RegexChecker(), ),
cl = 0 print() print() def single_inquiry_benchmark(): global guard if guard.is_allowed(inq): return True return False overall_policies_created = 0 similar_regexp_policies_created = 0 store = MemoryStorage() checker = RegexChecker(CACHE_SIZE) if CACHE_SIZE else RegexChecker() guard = Guard(store, checker) inq = Inquiry(action='get', subject='xo', resource='library:books:1234', context={'ip': '127.0.0.1'}) if __name__ == '__main__': line_length = 80 print('=' * line_length) print('Populating MemoryStorage with Policies') print_generation(populate_storage, int(POLICIES_NUMBER / 100 * 1), line_length) print('START BENCHMARK!') start = timeit.default_timer() allowed = single_inquiry_benchmark()
def test_matches(policy, field, what, result): c = RegexChecker() assert result == c.fits(policy, field, what)
def test_is_allowed(desc, inquiry, should_be_allowed): g = Guard(st, RegexChecker()) assert should_be_allowed == g.is_allowed(inquiry)
class TestMongoStorage: @pytest.fixture() def st(self): client = create_client() yield MongoStorage(client, DB_NAME, collection=COLLECTION) client[DB_NAME][COLLECTION].delete_many({}) client.close() def test_add(self, st): id = str(uuid.uuid4()) p = Policy( uid=id, description='foo bar баз', subjects=('Edward Rooney', 'Florence Sparrow'), actions=['<.*>'], resources=['<.*>'], context={ 'secret': Equal('i-am-a-teacher'), }, ) st.add(p) back = st.get(id) assert id == back.uid assert 'foo bar баз' == back.description assert isinstance(back.context['secret'], Equal) st.add( Policy('2', actions=[Eq('get'), Eq('put')], subjects=[Any()], resources=[{ 'books': Eq('Harry') }])) assert '2' == st.get('2').uid assert 2 == len(st.get('2').actions) assert 1 == len(st.get('2').subjects) assert isinstance(st.get('2').subjects[0], Any) assert 1 == len(st.get('2').resources) assert isinstance(st.get('2').resources[0]['books'], Eq) assert 'Harry' == st.get('2').resources[0]['books'].val def test_add_with_bson_object_id(self, st): id = str(ObjectId()) p = Policy( uid=id, description='foo', ) st.add(p) back = st.get(id) assert id == back.uid def test_policy_create_existing(self, st): id = str(uuid.uuid4()) st.add(Policy(id, description='foo')) with pytest.raises(PolicyExistsError): st.add(Policy(id, description='bar')) def test_get(self, st): st.add(Policy('1')) st.add(Policy(2, description='some text')) assert isinstance(st.get('1'), Policy) assert '1' == st.get('1').uid assert 2 == st.get(2).uid assert 'some text' == st.get(2).description def test_get_nonexistent(self, st): assert None is st.get(123456789) @pytest.mark.parametrize('limit, offset, result', [ (50, 0, 20), (11, 1, 11), (50, 5, 15), (20, 0, 20), (20, 1, 19), (19, 0, 19), (20, 5, 15), (0, 0, 0), (0, 10, 0), (1, 0, 1), (5, 4, 5), ]) def test_get_all(self, st, limit, offset, result): for i in range(20): desc = ''.join(random.choice('abcde') for _ in range(30)) st.add(Policy(str(i), description=desc)) policies = list(st.get_all(limit=limit, offset=offset)) assert result == len(policies) def test_get_all_check_policy_properties(self, st): p = Policy( uid='1', description='foo bar баз', subjects=('Edward Rooney', 'Florence Sparrow'), actions=['<.*>'], resources=['<.*>'], context={ 'secret': Equal('i-am-a-teacher'), }, ) st.add(p) policies = list(st.get_all(100, 0)) assert 1 == len(policies) assert '1' == policies[0].uid assert 'foo bar баз' == policies[0].description assert ['Edward Rooney', 'Florence Sparrow'] == policies[0].subjects assert ['<.*>'] == policies[0].actions assert ['<.*>'] == policies[0].resources assert isinstance(policies[0].context['secret'], Equal) def test_get_all_with_incorrect_args(self, st): for i in range(10): st.add(Policy(str(i), description='foo')) with pytest.raises(ValueError) as e: list(st.get_all(-1, 9)) assert "Limit can't be negative" == str(e.value) with pytest.raises(ValueError) as e: list(st.get_all(0, -3)) assert "Offset can't be negative" == str(e.value) def test_get_all_returns_generator(self, st): st.add(Policy('1')) st.add(Policy('2')) found = st.get_all(500, 0) assert isinstance(found, types.GeneratorType) l = [] for p in found: l.append(p.uid) assert 2 == len(l) def test_get_all_ascending_sorting_order(self, st): for i in range(1, 20): st.add(Policy(i)) assert list(range(1, 20)) == list( map(attrgetter('uid'), st.get_all(30, 0))) @pytest.mark.parametrize('checker, expect_number', [ (None, 6), (RegexChecker(), 2), (RulesChecker(), 2), (StringExactChecker(), 1), (StringFuzzyChecker(), 1), ]) def test_find_for_inquiry_returns_existing_policies( self, st, checker, expect_number): st.add(Policy('1', subjects=['<[mM]ax>', '<.*>'])) st.add(Policy('2', subjects=['sam<.*>', 'foo'])) st.add( Policy('3', subjects=['Jim'], actions=['delete'], resources=['server'])) st.add( Policy('3.1', subjects=['Jim'], actions=[r'del<\w+>'], resources=['server'])) st.add(Policy('4', subjects=[{'stars': Eq(90)}, Eq('Max')])) st.add(Policy('5', subjects=[Eq('Jim'), Eq('Nina')])) inquiry = Inquiry(subject='Jim', action='delete', resource='server') found = st.find_for_inquiry(inquiry, checker) assert expect_number == len(list(found)) def test_find_for_inquiry_with_exact_string_checker(self, st): st.add( Policy('1', subjects=['max', 'bob'], actions=['get'], resources=['books', 'comics', 'magazines'])) st.add( Policy('2', subjects=['maxim'], actions=['get'], resources=['books', 'comics', 'magazines'])) st.add(Policy('3', subjects=['sam', 'nina'])) st.add(Policy('4', subjects=[Eq('sam'), Eq('nina')])) inquiry = Inquiry(subject='max', action='get', resource='books') found = st.find_for_inquiry(inquiry, StringExactChecker()) found = list(found) assert 1 == len(found) assert '1' == found[0].uid def test_find_for_inquiry_with_fuzzy_string_checker(self, st): st.add( Policy('1', subjects=['max', 'bob'], actions=['get'], resources=['books', 'comics', 'magazines'])) st.add( Policy('2', subjects=['maxim'], actions=['get'], resources=['books', 'foos'])) st.add( Policy('3', subjects=['Max'], actions=['get'], resources=['books', 'comics'])) st.add(Policy('4', subjects=['sam', 'nina'])) st.add(Policy('5', subjects=[Eq('sam'), Eq('nina')])) inquiry = Inquiry(subject='max', action='et', resource='oo') found = st.find_for_inquiry(inquiry, StringFuzzyChecker()) found = list(found) assert 2 == len(found) ids = [found[0].uid, found[1].uid] assert '1' in ids assert '2' in ids inquiry = Inquiry(subject='Max', action='get', resource='comics') found = st.find_for_inquiry(inquiry, StringFuzzyChecker()) found = list(found) assert 1 == len(found) assert '3' == found[0].uid @pytest.mark.parametrize('policies, inquiry, expected_reference', [ ( [ Policy(uid=1, actions=['get', 'post'], effect=ALLOW_ACCESS, resources=['<.*>'], subjects=['<[Mm]ax>', '<Jim>']), ], Inquiry(action='get', resource='printer', subject='Max'), True, ), ( [ Policy(uid=1, actions=['<.*>'], effect=ALLOW_ACCESS, resources=['<.*>'], subjects=['<.*>']), ], Inquiry(action='get', resource='printer', subject='Max'), True, ), ( [ Policy(uid=1, actions=['<.*>'], effect=ALLOW_ACCESS, resources=['library:books:<.+>'], subjects=['<.*>']), ], Inquiry( action='get', resource='library:books:dracula', subject='Max'), True, ), ( [ Policy(uid=1, actions=[r'<\d+>'], effect=ALLOW_ACCESS, resources=[r'<\w{1,3}>'], subjects=[r'<\w{2}-\d+>']), ], Inquiry(action='12', resource='Pie', subject='Jo-1'), True, ), ( [ Policy(uid=1, actions=['parse'], effect=ALLOW_ACCESS, resources=['library:books'], subjects=['Max']), ], Inquiry(action='parse', resource='library:books', subject='Max'), True, ), ( [ Policy(uid=1, actions=['parse'], effect=ALLOW_ACCESS, resources=['library:manu<(al|scripts)>'], subjects=['Max']), ], Inquiry(action='parse', resource='library:books', subject='Max'), False, ), ( [ Policy(uid=1, actions=['parse'], effect=ALLOW_ACCESS, resources=['library:books'], subjects=['Max']), Policy(uid=2, actions=['parse'], effect=ALLOW_ACCESS, resources=['library:manu<(al|scripts)>'], subjects=['Max']), ], Inquiry( action='parse', resource='library:manuscripts', subject='Max'), True, ), ]) def test_find_for_inquiry_with_regex_checker(self, st, policies, inquiry, expected_reference): mem_storage = MemoryStorage( ) # it returns all stored policies so we consider Guard as a reference for p in policies: st.add(p) mem_storage.add(p) reference_answer = Guard(mem_storage, RegexChecker()).is_allowed(inquiry) assert expected_reference == reference_answer, 'Check reference answer' assert reference_answer == Guard(st, RegexChecker()).is_allowed(inquiry), \ 'Mongo storage should give the same answers as reference' def test_find_for_inquiry_with_regex_checker_for_mongodb_prior_to_4_2( self, st): # mock db server version for this test st.db_server_version = (3, 4, 0) st.add(Policy('1', subjects=['<[mM]ax>', '<.*>'])) st.add(Policy('2', subjects=['sam<.*>', 'foo'])) st.add( Policy('3', subjects=['Jim'], actions=['delete'], resources=['server'])) st.add( Policy('3.1', subjects=['Jim'], actions=[r'del<\w+>'], resources=['server'])) st.add(Policy('4', subjects=[{'stars': Eq(90)}, Eq('Max')])) st.add(Policy('5', subjects=[Eq('Jim'), Eq('Nina')])) inquiry = Inquiry(subject='Jim', action='delete', resource='server') found = st.find_for_inquiry(inquiry, RegexChecker()) found = list(found) # should return all string-based polices, but not only matched ones assert 4 == len(found) assert ['1', '2', '3', '3.1'] == sorted(map(attrgetter('uid'), found)) def test_find_for_inquiry_with_rules_checker(self, st): assertions = unittest.TestCase('__init__') st.add( Policy(1, subjects=[{ 'name': Equal('Max') }], actions=[{ 'foo': Equal('bar') }])) st.add( Policy(2, subjects=[{ 'name': Equal('Max') }], actions=[{ 'foo': Equal('bar2') }])) st.add(Policy(3, subjects=['sam', 'nina'])) st.add( Policy(4, actions=[r'<\d+>'], effect=ALLOW_ACCESS, resources=[r'<\w{1,3}>'], subjects=[r'<\w{2}-\d+>'])) st.add( Policy(5, subjects=[{ 'name': Equal('Jim') }], actions=[{ 'foo': Equal('bar3') }])) inquiry = Inquiry(subject={'name': 'max'}, action='get', resource='books') found = st.find_for_inquiry(inquiry, RulesChecker()) found = list(found) assert 3 == len(found) assertions.assertListEqual([1, 2, 5], list(map(operator.attrgetter('uid'), found))) def test_find_for_inquiry_with_unknown_checker(self, st): st.add(Policy('1')) inquiry = Inquiry(subject='sam', action='get', resource='books') with pytest.raises(UnknownCheckerType): list(st.find_for_inquiry(inquiry, Inquiry())) def test_find_for_inquiry_returns_generator(self, st): st.add( Policy('1', subjects=['max', 'bob'], actions=['get'], resources=['comics'])) st.add( Policy('2', subjects=['max', 'bob'], actions=['get'], resources=['comics'])) inquiry = Inquiry(subject='max', action='get', resource='comics') found = st.find_for_inquiry(inquiry) assert isinstance(found, types.GeneratorType) l = [] for p in found: l.append(p.uid) assert 2 == len(l) def test_update(self, st): id = str(uuid.uuid4()) policy = Policy(id) st.add(policy) assert id == st.get(id).uid assert None is st.get(id).description assert [] == st.get(id).actions policy.description = 'foo' policy.actions = ['a', 'b', 'c'] st.update(policy) assert id == st.get(id).uid assert 'foo' == st.get(id).description assert ['a', 'b', 'c'] == st.get(id).actions p = Policy(2, actions=[Any()], subjects=[Eq('max'), Eq('bob')]) st.add(p) assert 2 == st.get(2).uid p.actions = [Eq('get')] st.update(p) assert 1 == len(st.get(2).actions) assert 'get' == st.get(2).actions[0].val def test_update_non_existing_does_not_create_anything(self, st): id = str(uuid.uuid4()) st.update(Policy(id, actions=['get'], description='bar')) assert st.get(id) is None def test_delete(self, st): policy = Policy('1') st.add(policy) assert '1' == st.get('1').uid st.delete('1') assert None is st.get('1') def test_delete_nonexistent(self, st): uid = str(ObjectId()) st.delete(uid) assert None is st.get(uid) def test_returned_condition(self, st): uid = str(uuid.uuid4()) p = Policy( uid=uid, context={ 'secret': Equal('i-am-a-teacher'), 'secret2': Equal('i-am-a-husband'), }, ) st.add(p) context = st.get(uid).context assert context['secret'].satisfied('i-am-a-teacher') assert context['secret2'].satisfied('i-am-a-husband')
class TestMongoStorage: @pytest.fixture() def st(self): client = create_client() yield MongoStorage(client, DB_NAME, collection=COLLECTION) client[DB_NAME][COLLECTION].remove() client.close() def test_add(self, st): id = str(uuid.uuid4()) p = Policy( uid=id, description='foo bar баз', subjects=('Edward Rooney', 'Florence Sparrow'), actions=['<.*>'], resources=['<.*>'], rules={ 'secret': StringEqualRule('i-am-a-teacher'), }, ) st.add(p) back = st.get(id) assert id == back.uid assert 'foo bar баз' == back.description assert isinstance(back.rules['secret'], StringEqualRule) def test_add_with_bson_object_id(self, st): id = str(ObjectId()) p = Policy( uid=id, description='foo', ) st.add(p) back = st.get(id) assert id == back.uid def test_policy_create_existing(self, st): id = str(uuid.uuid4()) st.add(Policy(id, description='foo')) with pytest.raises(PolicyExistsError): st.add(st.add(Policy(id, description='bar'))) def test_get(self, st): st.add(Policy('1')) st.add(Policy(2, description='some text')) assert isinstance(st.get('1'), Policy) assert '1' == st.get('1').uid assert 2 == st.get(2).uid assert 'some text' == st.get(2).description def test_get_nonexistent(self, st): assert None is st.get(123456789) @pytest.mark.parametrize('limit, offset, result', [ (500, 0, 200), (101, 1, 101), (500, 50, 150), (200, 0, 200), (200, 1, 199), (199, 0, 199), (200, 50, 150), (0, 0, 200), (1, 0, 1), (5, 4, 5), ]) def test_get_all(self, st, limit, offset, result): for i in range(200): desc = ''.join(random.choice('abcde') for _ in range(30)) st.add(Policy(str(i), description=desc)) policies = list(st.get_all(limit=limit, offset=offset)) assert result == len(policies) def test_get_all_check_policy_properties(self, st): p = Policy( uid='1', description='foo bar баз', subjects=('Edward Rooney', 'Florence Sparrow'), actions=['<.*>'], resources=['<.*>'], rules={ 'secret': StringEqualRule('i-am-a-teacher'), }, ) st.add(p) policies = list(st.get_all(100, 0)) assert 1 == len(policies) assert '1' == policies[0].uid assert 'foo bar баз' == policies[0].description assert ['Edward Rooney', 'Florence Sparrow'] == policies[0].subjects assert ['<.*>'] == policies[0].actions assert ['<.*>'] == policies[0].resources assert isinstance(policies[0].rules['secret'], StringEqualRule) def test_get_all_with_incorrect_args(self, st): for i in range(10): st.add(Policy(str(i), description='foo')) with pytest.raises(ValueError) as e: list(st.get_all(-1, 9)) assert "Limit can't be negative" == str(e.value) with pytest.raises(ValueError) as e: list(st.get_all(0, -3)) assert "Offset can't be negative" == str(e.value) def test_get_all_returns_generator(self, st): st.add(Policy('1')) st.add(Policy('2')) found = st.get_all(500, 0) assert isinstance(found, types.GeneratorType) l = [] for p in found: l.append(p.uid) assert 2 == len(l) @pytest.mark.parametrize('checker', [ None, RegexChecker(256), ]) def test_find_for_inquiry_with_regex_or_none_checker_specified_return_all_existing_policies(self, st, checker): st.add(Policy('1', subjects=['max', 'bob'])) st.add(Policy('2', subjects=['sam', 'foo'])) st.add(Policy('3', subjects=['bar'])) inquiry = Inquiry(subject='Jim', action='delete', resource='server') found = st.find_for_inquiry(inquiry, checker) found = list(found) assert 3 == len(found) def test_find_for_inquiry_with_exact_string_checker(self, st): st.add(Policy('1', subjects=['max', 'bob'], actions=['get'], resources=['books', 'comics', 'magazines'])) st.add(Policy('2', subjects=['maxim'], actions=['get'], resources=['books', 'comics', 'magazines'])) st.add(Policy('3', subjects=['sam', 'nina'])) inquiry = Inquiry(subject='max', action='get', resource='books') found = st.find_for_inquiry(inquiry, StringExactChecker()) found = list(found) assert 1 == len(found) assert '1' == found[0].uid def test_find_for_inquiry_with_fuzzy_string_checker(self, st): st.add(Policy('1', subjects=['max', 'bob'], actions=['get'], resources=['books', 'comics', 'magazines'])) st.add(Policy('2', subjects=['maxim'], actions=['get'], resources=['books', 'foos'])) st.add(Policy('3', subjects=['Max'], actions=['get'], resources=['books', 'comics'])) st.add(Policy('4', subjects=['sam', 'nina'])) inquiry = Inquiry(subject='max', action='et', resource='oo') found = st.find_for_inquiry(inquiry, StringFuzzyChecker()) found = list(found) assert 2 == len(found) ids = [found[0].uid, found[1].uid] assert '1' in ids assert '2' in ids inquiry = Inquiry(subject='Max', action='get', resource='comics') found = st.find_for_inquiry(inquiry, StringFuzzyChecker()) found = list(found) assert 1 == len(found) assert '3' == found[0].uid def test_find_for_inquiry_returns_generator(self, st): st.add(Policy('1', subjects=['max', 'bob'], actions=['get'], resources=['comics'])) st.add(Policy('2', subjects=['max', 'bob'], actions=['get'], resources=['comics'])) inquiry = Inquiry(subject='max', action='get', resource='comics') found = st.find_for_inquiry(inquiry) assert isinstance(found, types.GeneratorType) l = [] for p in found: l.append(p.uid) assert 2 == len(l) def test_find_for_inquiry_with_unknown_checker(self, st): st.add(Policy('1')) inquiry = Inquiry(subject='sam', action='get', resource='books') with pytest.raises(UnknownCheckerType): list(st.find_for_inquiry(inquiry, Inquiry())) def test_update(self, st): id = str(uuid.uuid4()) policy = Policy(id) st.add(policy) assert id == st.get(id).uid assert None is st.get(id).description assert [] == st.get(id).actions policy.description = 'foo' policy.actions = ['a', 'b', 'c'] st.update(policy) assert id == st.get(id).uid assert 'foo' == st.get(id).description assert ['a', 'b', 'c'] == st.get(id).actions def test_update_non_existing_does_not_create_anything(self, st): id = str(uuid.uuid4()) st.update(Policy(id, actions=['get'], description='bar')) assert st.get(id) is None def test_delete(self, st): policy = Policy('1') st.add(policy) assert '1' == st.get('1').uid st.delete('1') assert None is st.get('1') def test_delete_nonexistent(self, st): uid = str(ObjectId()) st.delete(uid) assert None is st.get(uid) def test_returned_condition(self, st): uid = str(uuid.uuid4()) p = Policy( uid=uid, rules={ 'secret': StringEqualRule('i-am-a-teacher'), 'secret2': StringEqualRule('i-am-a-husband'), }, ) st.add(p) rules = st.get(uid).rules assert rules['secret'].satisfied('i-am-a-teacher') assert rules['secret2'].satisfied('i-am-a-husband')
import pytest from vakt.exceptions import PolicyExistsError, UnknownCheckerType from vakt.checker import RegexChecker class MyChecker(RegexChecker): pass @pytest.mark.parametrize('uid, message', [ ('', 'Conflicting UID = '), ('123', 'Conflicting UID = 123'), ]) def test_poicy_exists_message(uid, message): ex = PolicyExistsError(uid) assert message == str(ex) @pytest.mark.parametrize('obj, message', [ (RegexChecker(), "Can't determine Checker type. Given: RegexChecker"), (MyChecker(), "Can't determine Checker type. Given: MyChecker"), (1, "Can't determine Checker type. Given: int"), ]) def test_unknown_checker_message(obj, message): ex = UnknownCheckerType(obj) assert message == str(ex)