From 169384ddbeb64526a785679ffa8a70be3b4edc07 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Thu, 23 Mar 2017 09:49:32 -0400 Subject: [PATCH] Start RBAC unit testing system to test is_implicit_parent --- awx/main/fields.py | 32 +++--- awx/main/signals.py | 2 +- awx/main/tests/unit/models/test_rbac_unit.py | 103 +++++++++++++++++++ 3 files changed, 123 insertions(+), 14 deletions(-) create mode 100644 awx/main/tests/unit/models/test_rbac_unit.py diff --git a/awx/main/fields.py b/awx/main/fields.py index dc0d4babab..8d955ab6ce 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -96,31 +96,37 @@ def resolve_role_field(obj, field): return ret -def is_implicit_parent(role, instance): +def is_implicit_parent(parent_role, child_role): + ''' + Determine if the parent_role is an implicit parent as defined by + the model definition. This does not include any role parents that + might have been set by the user. + ''' # Get the list of implicit parents that were defined at the class level. - # We have to take this list from the class property to avoid including parents - # that may have been added since the creation of the ImplicitRoleField - implicit_parents = getattr(instance.content_object.__class__, instance.role_field).field.parent_role + implicit_parents = getattr( + child_role.content_object.__class__, child_role.role_field + ).field.parent_role if type(implicit_parents) != list: implicit_parents = [implicit_parents] + # Check to see if the role matches any in the implicit parents list for implicit_parent_path in implicit_parents: if '.' in implicit_parent_path: # Walk over multiple related objects to obtain the implicit parent - obj = instance.content_object - for next_field in implicit_parent_path.split('.')[:-1]: - obj = getattr(obj, next_field) - if obj is None: - return True - if role == getattr(obj, implicit_parent_path.split('.')[-1]): + related_obj = child_role.content_object + for next_field in implicit_parent_path.split('.'): + related_obj = getattr(related_obj, next_field) + if related_obj is None: + break + if related_obj and parent_role == related_obj: return True elif implicit_parent_path.startswith('singleton:'): - # Ignore any singleton parents we find. - if role.is_singleton() and role.singleton_name == implicit_parent_path[10:]: + # Singleton role isn't an object role, `singleton_name` uniquely identifies it + if parent_role.is_singleton() and parent_role.singleton_name == implicit_parent_path[10:]: return True else: # Direct field on the content object - if role == getattr(instance.content_object, implicit_parent_path): + if parent_role == getattr(child_role.content_object, implicit_parent_path): return True return False diff --git a/awx/main/signals.py b/awx/main/signals.py index f69179a580..9e5fa95692 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -181,7 +181,7 @@ def rbac_activity_stream(instance, sender, **kwargs): elif sender.__name__ == 'Role_parents': role = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']).first() # don't record implicit creation / parents in activity stream - if role is not None and is_implicit_parent(role, instance): + if role is not None and is_implicit_parent(parent_role=role, child_role=instance): return else: role = instance diff --git a/awx/main/tests/unit/models/test_rbac_unit.py b/awx/main/tests/unit/models/test_rbac_unit.py new file mode 100644 index 0000000000..24d9a657ba --- /dev/null +++ b/awx/main/tests/unit/models/test_rbac_unit.py @@ -0,0 +1,103 @@ +import pytest +import mock + +from django.contrib.contenttypes.models import ContentType + +from awx.main.models.rbac import ( + Role, + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, + ROLE_SINGLETON_SYSTEM_AUDITOR +) +from awx.main.models import Organization, JobTemplate, Project + +from awx.main.fields import ( + ImplicitRoleField, + is_implicit_parent +) + + +def apply_fake_roles(obj): + ''' + Creates an un-saved role for all the implicit role fields on an object + ''' + for fd in obj._meta.fields: + if not isinstance(fd, ImplicitRoleField): + continue + r = Role(role_field=fd.name) + setattr(obj, fd.name, r) + with mock.patch('django.contrib.contenttypes.fields.GenericForeignKey.get_content_type') as mck_ct: + mck_ct.return_value = ContentType(model=obj._meta.model_name) + r.content_object = obj + + +@pytest.fixture +def system_administrator(): + return Role( + role_field=ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, + singleton_name=ROLE_SINGLETON_SYSTEM_ADMINISTRATOR + ) + + +@pytest.fixture +def system_auditor(): + return Role( + role_field=ROLE_SINGLETON_SYSTEM_AUDITOR, + singleton_name=ROLE_SINGLETON_SYSTEM_AUDITOR + ) + + +@pytest.fixture +def organization(): + o = Organization(name='unit-test-org') + apply_fake_roles(o) + return o + + +@pytest.fixture +def project(organization): + p = Project(name='unit-test-proj', organization=organization) + apply_fake_roles(p) + return p + + +@pytest.fixture +def job_template(project): + jt = JobTemplate(name='unit-test-jt', project=project) + apply_fake_roles(jt) + return jt + + +class TestIsImplicitParent: + ''' + Tests to confirm that `is_implicit_parent` gives the right answers + ''' + def test_sys_admin_implicit_parent(self, organization, system_administrator): + assert is_implicit_parent( + parent_role=system_administrator, + child_role=organization.admin_role + ) + + + def test_admin_is_parent_of_member_role(self, organization): + assert is_implicit_parent( + parent_role=organization.admin_role, + child_role=organization.member_role + ) + + def test_member_is_not_parent_of_admin_role(self, organization): + assert not is_implicit_parent( + parent_role=organization.member_role, + child_role=organization.admin_role + ) + + def test_second_level_implicit_parent_role(self, job_template, organization): + assert is_implicit_parent( + parent_role=organization.admin_role, + child_role=job_template.admin_role + ) + + def test_second_level_is_not_an_implicit_parent_role(self, job_template, organization): + assert not is_implicit_parent( + parent_role=organization.member_role, + child_role=job_template.admin_role + )