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
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_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_returns_existing_policies(self, st, checker, expect_number): st.add(Policy('1', subjects=['<[mM]ax>', '<.*>'], actions=['delete'], resources=['server'])) st.add(Policy('2', subjects=['Ji<[mM]+>'], actions=['delete'], resources=[r'server<\s*>'])) st.add(Policy('3', subjects=['sam<.*>', 'foo'])) st.add(Policy('5', subjects=['Jim'], actions=['delete'], resources=['server'])) st.add(Policy('4', subjects=[{'stars': Eq(90)}, Eq('Max')])) st.add(Policy('6', subjects=[Eq('Jim'), Eq('Nina')])) inquiry = Inquiry(subject='Jim', action='delete', resource='server') found = st.find_for_inquiry(inquiry, checker) found = list(found) assert expect_number == len(found)
def test_find_for_inquiry(st): st.add(Policy('1', subjects=['max', 'bob'])) st.add(Policy('2', subjects=['sam', 'nina'])) st.add(Policy('3', subjects=[Eq('max'), Eq('bob')])) inquiry = Inquiry(subject='sam', action='get', resource='books') found = st.find_for_inquiry(inquiry) found = list(found) assert 3 == len(found) assert ['max', 'bob'] == found[0].subjects or \ ['max', 'bob'] == found[1].subjects or \ ['max', 'bob'] == found[2].subjects
def test_pretty_print(): p = Policy('1', description='readme', subjects=['user']) assert "<class 'vakt.policy.Policy'>" in str(p) assert "'uid': '1'" in str(p) assert "'description': 'readme'" in str(p) assert "'subjects': ['user']" in str(p) assert "'effect': 'deny'" in str(p) assert "'resources': ()" in str(p) assert "'actions': ()" in str(p) assert "'context': {}" in str(p) p = Policy('2', actions=[Eq('get'), Eq('post')]) assert "vakt.rules.operator.Eq" in str(p.actions)
def test_update(self): 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'), }, ) p_model = PolicyModel.from_policy(p) new_p = Policy(2, actions=[], subjects=[Eq('max'), Eq('bob')]) p_model.update(new_p) policy__model_assert(p_model, new_p)
def test_PolicyAllow_and_PolicyDeny(klass, is_allowed, effect): p = klass(1, actions=['<foo.bar>'], resources=['asdf'], subjects=['<qwerty>'], description='test') assert is_allowed == p.allow_access() assert 1 == p.uid assert 'test' == p.description assert TYPE_STRING_BASED == p.type assert ['<foo.bar>'] == p.actions assert ['asdf'] == p.resources assert ['<qwerty>'] == p.subjects assert {} == p.context assert '{"actions": ["<foo.bar>"], "context": {}, "description": "test", "effect": "%s", ' % effect + \ '"resources": ["asdf"], "subjects": ["<qwerty>"], "type": 1, "uid": 1}' == p.to_json(sort=True) assert ['<foo.bar>'] == Policy.from_json(p.to_json()).actions p.effect = DENY_ACCESS assert DENY_ACCESS == p.effect p2 = klass(2, context={'a': Eq(100)}) assert isinstance(p2.context.get('a'), Eq) assert 100 == p2.context.get('a').val # check positional arguments p3 = Policy(1, actions=['<foo.bar>'], resources=['asdf'], subjects=['<qwerty>'], description='test', effect=ALLOW_ACCESS if is_allowed else DENY_ACCESS) p4 = klass(1, ['<qwerty>'], ['asdf'], ['<foo.bar>'], {}, 'test') assert p3.to_json(sort=True) == p4.to_json(sort=True)
def test_regex_checker_find_for_inquiry_policies_count_return_by_storage(self, st, dialect, expect_number): if st.dialect != dialect: pytest.skip('skipping for %s dialect' % dialect) st.add(Policy('1', subjects=['<[mM]ax>', '<.*>'], actions=['delete'], resources=['server'])) st.add(Policy('2', subjects=['Ji<[mM]+>'], actions=['delete'], resources=[r'server<\s*>'])) st.add(Policy('3', subjects=['sam<.*>', 'foo'])) st.add(Policy('5', subjects=['Jim'], actions=['delete'], resources=['server'])) st.add(Policy('6', subjects=['Ji<[mM]+>'], actions=[r'<[a-zA-Z]{6}>'], resources=['serve<(r|rs)>'])) st.add(Policy('7', subjects=['Ji<[mM]+>'], actions=[r'<[a-zA-Z]{6}>'], resources=['serve<(u|rs)>'])) st.add(Policy('8', subjects=['Ji<[mM]+>'], actions=[r'<[a-zA-Z]{6}>'], resources=['serve<(u|rs)>', 'server'])) st.add(Policy('40', subjects=[{'stars': Eq(90)}, Eq('Max')])) st.add(Policy('60', subjects=[Eq('Jim'), Eq('Nina')])) inquiry = Inquiry(subject='Jim', action='delete', resource='server') found = st.find_for_inquiry(inquiry, RegexChecker()) found = list(found) assert expect_number == len(found)
def test_update(st): policy = Policy('1') st.add(policy) assert '1' == st.get('1').uid assert None is st.get('1').description policy.description = 'foo' st.update(policy) assert '1' == st.get('1').uid assert 'foo' == st.get('1').description 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_policy_type_on_attribute_change(): p = Policy(1, actions=['<foo.bar>'], resources=['asdf'], subjects=['<qwerty>']) assert TYPE_STRING_BASED == p.type p.effect = ALLOW_ACCESS assert TYPE_STRING_BASED == p.type with pytest.raises(PolicyCreationError): p.actions = [{'ip': CIDR('0.0.0.0')}] assert TYPE_STRING_BASED == p.type with pytest.raises(PolicyCreationError): p.subjects = [{'ip': CIDR('0.0.0.0')}] with pytest.raises(PolicyCreationError): p.actions = [Any()] assert TYPE_STRING_BASED == p.type p.actions = ['<.*>'] assert TYPE_STRING_BASED == p.type p.subjects = ['<.*>'] assert TYPE_STRING_BASED == p.type p.type = TYPE_RULE_BASED # explicit assign doesn't help assert TYPE_STRING_BASED == p.type # testing the from the opposite direction p = Policy(2, actions=[Any()], resources=[{ 'book': Eq('UX Manual') }], subjects=[Eq('Sally'), Eq('Bob')]) assert TYPE_RULE_BASED == p.type p.effect = ALLOW_ACCESS assert TYPE_RULE_BASED == p.type with pytest.raises(PolicyCreationError): p.actions = ['<foo.bar>'] assert TYPE_RULE_BASED == p.type with pytest.raises(PolicyCreationError): p.subjects = ['<foo.bar>', 'baz'] with pytest.raises(PolicyCreationError): p.actions = ['baz<.*>'] assert TYPE_RULE_BASED == p.type p.actions = [Any()] assert TYPE_RULE_BASED == p.type p.subjects = [Any()] assert TYPE_RULE_BASED == p.type p.type = TYPE_STRING_BASED # explicit assign doesn't help assert TYPE_RULE_BASED == p.type
def test_add(st): st.add(Policy('1', description='foo')) assert '1' == st.get('1').uid assert 'foo' == st.get('1').description 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_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(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_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_or_rule_uses_short_circuit_but_and_rule_does_not(): x = [] def get_inc(x): def inc(): x.append(1) return True return inc f = get_inc(x) rules = [Eq(f), Truthy()] # test Or r = Or(*rules) assert r.satisfied(f, None) assert r.satisfied(f, None) assert 0 == len(x) # test And r = And(*rules) assert r.satisfied(f, None) assert 1 == len(x) assert r.satisfied(f, None) assert 2 == len(x)
Policy( uid=1, effect=ALLOW_ACCESS, subjects=['foo'], ), Inquiry(subject='foo'), RegexChecker(), False, ), ( 'RulesChecker: Should not match since actions and resources are not specified', Policy( uid=1, effect=ALLOW_ACCESS, subjects=[{ 'name': Eq('Sally') }], ), Inquiry(subject={'name': 'Sally'}), RulesChecker(), False, ), ( 'RulesChecker: Should not match since resources are not specified', Policy( uid=1, effect=ALLOW_ACCESS, subjects=[{ 'name': Eq('Sally') }], actions=[{
), Policy(uid='4'), Policy( uid='5', description='Allows Nina to update any resources that have only digits', effect=ALLOW_ACCESS, subjects=['Nina'], actions=['update'], resources=[r'<[\d]+>'], ), Policy( uid='6', description= 'Allows Nina to update any resources that have only digits. Defined by rules', effect=ALLOW_ACCESS, subjects=[Eq('Nina')], actions=[Eq('update'), Eq('read')], resources=[{ 'id': RegexMatch(r'\d+'), 'magazine': RegexMatch(r'[\d\w]+') }], ), ] for p in policies: st.add(p) @pytest.mark.parametrize('desc, inquiry, should_be_allowed, checker', [ ( 'Empty inquiry carries no information, so nothing is allowed, even empty Policy #4', Inquiry(),
def test_and_or_rules_bad_args(): expected_msg = "Arguments should be of Rule class or it's derivatives" with pytest.raises(TypeError) as excinfo: And(Inquiry()) assert expected_msg in str(excinfo.value) with pytest.raises(TypeError) as excinfo: Or(Inquiry(), 123) assert expected_msg in str(excinfo.value) @pytest.mark.parametrize('rules, what, inquiry, result', [ ([], 1, None, False), ([Greater(-1)], 1, None, True), ([Greater(55)], 1, None, False), ([Greater(-1), Less(10)], 1, None, True), ([Greater(-1), Less(10), Eq(700)], 1, None, False), ([Eq('read'), In('read', 'write'), ActionEqual()], 'read', Inquiry(action='read'), True), ([Eq('read'), In('write'), ActionEqual() ], 'read', Inquiry(action='read'), False), ]) def test_and_rule(rules, what, inquiry, result): r = And(*rules) assert result == r.satisfied(what, inquiry) # test after (de)serialization assert result == Rule.from_json(And(*rules).to_json()).satisfied( what, inquiry) @pytest.mark.parametrize('rules, what, inquiry, result', [ ([], 1, None, False),
p = Policy('789', rules={'ip': CIDR('127.0.0.1'), 'sub': Equal('baz')}) s = p.to_json() p1 = Policy.from_json(s) assert '789' == p1.uid assert 2 == len(p1.context) assert 'ip' in p1.context assert 'sub' in p1.context assert isinstance(p1.context['ip'], CIDR) assert isinstance(p1.context['sub'], Equal) assert p1.context['sub'].satisfied('baz') assert p1.context['ip'].satisfied('127.0.0.1') assert not hasattr(p1, 'rules') @pytest.mark.parametrize('policy', [ Policy(1, subjects=[{'name': Eq('Max'), 'rate': Greater(90)}], actions=[Eq('get'), Eq('post')], resources=[Any()]), Policy(2, subjects=[{'login': Eq('sally')}], actions=[Eq('get'), Eq('post')], context={'ip': Eq('127.0.0.1')}), Policy(3, subjects=[{'rating': AnyIn(1, 2)}], actions=[And(Eq('get'), Eq('post'))]), Policy(4, subjects=[{'rating': AnyIn(1, 2)}], actions=[And(Eq('get'), Eq('post'))]), Policy(5, actions=[Eq('get')]), ]) def test_json_roundtrip_of_a_rules_based_policy(policy): pj = policy.to_json() p2 = Policy.from_json(pj) assert policy.to_json() == p2.to_json() @pytest.mark.parametrize('data, exception, msg', [ ('{}', PolicyCreationError, "'uid'"), ('{"uid":}', ValueError, ''), ('', ValueError, ''),
from vakt.checker import StringFuzzyChecker from vakt.policy import Policy from vakt.rules.operator import Eq @pytest.mark.parametrize('policy, field, what, result', [ (Policy('1', actions=['get']), 'actions', 'get', True), (Policy('1', actions=['get']), 'actions', 'g', True), (Policy('1', actions=['get']), 'actions', 'et', True), (Policy('1', actions=['get']), 'actions', 't', True), (Policy('1', actions=['get', 'list']), 'actions', 'list', True), (Policy('1', actions=['get', 'list']), 'non_existing_field', 'get', False), (Policy('1', actions=['<get>']), 'actions', 'get', True), (Policy('1', actions=['<get>']), 'actions', 'e', True), (Policy('1', actions=['<get>']), 'actions', '<get>', False), (Policy('1', actions=['<get>']), 'actions', '<t>', False), (Policy('1', actions=['<get']), 'actions', 'get', True), (Policy('1', actions=['<get']), 'actions', 'ge', True), (Policy('1', resources=['books:1', 'books:2' ]), 'resources', 'books', True), (Policy('1', resources=['books:1', 'books:2']), 'resources', ':', True), (Policy('1', resources=['books:1', 'books:2']), 'resources', '3', False), (Policy('1', actions=[Eq('create')]), 'actions', 'create', False), (Policy('1', actions=[{ 'foo': Eq('create') }]), 'actions', 'create', False), ]) def test_fits(policy, field, what, result): c = StringFuzzyChecker() assert result == c.fits(policy, field, what)
def test_is_allowed(desc, inquiry, should_be_allowed, checker): # Create all required test policies st = MemoryStorage() policies = [ Policy( uid='1', description=""" Max, Nina, Ben, Henry are allowed to create, delete, get the resources only if the client IP matches and the inquiry states that any of them is the resource owner """, effect=ALLOW_ACCESS, subjects=('Max', 'Nina', '<Ben|Henry>'), resources=('myrn:example.com:resource:123', 'myrn:example.com:resource:345', 'myrn:something:foo:<.+>'), actions=('<create|delete>', 'get'), context={ 'ip': CIDR('127.0.0.1/32'), 'owner': SubjectEqual(), }, ), Policy( uid='2', description='Allows Max to update any resource', effect=ALLOW_ACCESS, subjects=['Max'], actions=['update'], resources=['<.*>'], ), Policy( uid='3', description='Max is not allowed to print any resource', effect=DENY_ACCESS, subjects=['Max'], actions=['print'], resources=['<.*>'], ), Policy(uid='4'), Policy( uid='5', description= 'Allows Nina to update any resources that have only digits', effect=ALLOW_ACCESS, subjects=['Nina'], actions=['update'], resources=[r'<[\d]+>'], ), Policy( uid='6', description= 'Allows Nina to update any resources that have only digits. Defined by rules', effect=ALLOW_ACCESS, subjects=[Eq('Nina')], actions=[Eq('update'), Eq('read')], resources=[{ 'id': RegexMatch(r'\d+'), 'magazine': RegexMatch(r'[\d\w]+') }], ), ] for p in policies: st.add(p) g = Guard(st, checker) assert should_be_allowed == g.is_allowed(inquiry)
p1 = Policy.from_json(s) assert '789' == p1.uid assert 2 == len(p1.context) assert 'ip' in p1.context assert 'sub' in p1.context assert isinstance(p1.context['ip'], CIDR) assert isinstance(p1.context['sub'], Equal) assert p1.context['sub'].satisfied('baz') assert p1.context['ip'].satisfied('127.0.0.1') assert not hasattr(p1, 'rules') @pytest.mark.parametrize('policy', [ Policy(1, subjects=[{ 'name': Eq('Max'), 'rate': Greater(90) }], actions=[Eq('get'), Eq('post')], resources=[Any()]), Policy(2, subjects=[{ 'login': Eq('sally') }], actions=[Eq('get'), Eq('post')], context={'ip': Eq('127.0.0.1')}), Policy(3, subjects=[{ 'rating': AnyIn(1, 2) }], actions=[And(Eq('get'), Eq('post'))]),