From 44c518ef4434405893ddfcb0f3dfe69b903054db Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 23 May 2016 16:47:06 -0400 Subject: [PATCH 1/3] allow org admins read access to orphaned jobs --- awx/main/access.py | 15 ++++++++++- awx/main/tests/functional/test_rbac_job.py | 30 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 awx/main/tests/functional/test_rbac_job.py diff --git a/awx/main/access.py b/awx/main/access.py index 7e2289df42..30b2bc1fa4 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -839,10 +839,23 @@ class JobAccess(BaseAccess): if self.user.is_superuser: return qs.all() - return qs.filter( + qs_jt = qs.filter( job_template__in=JobTemplate.accessible_objects(self.user, 'read_role') ) + admin_of_organizations_qs = self.user.admin_of_organizations + if not admin_of_organizations_qs.exists(): + return qs_jt + + qs_scan_orphan = qs.filter( + job_type=PERM_INVENTORY_SCAN, + inventory__organization__in=admin_of_organizations_qs + ) + qs_orphan = qs.filter( + project__organization__in=admin_of_organizations_qs + ).exclude(job_type=PERM_INVENTORY_SCAN) + return (qs_jt | qs_orphan | qs_scan_orphan).distinct() + def can_add(self, data): if not data or '_method' in data: # So the browseable API will work? return True diff --git a/awx/main/tests/functional/test_rbac_job.py b/awx/main/tests/functional/test_rbac_job.py new file mode 100644 index 0000000000..4a176062d5 --- /dev/null +++ b/awx/main/tests/functional/test_rbac_job.py @@ -0,0 +1,30 @@ +import pytest + +from awx.main.access import JobAccess +from awx.main.models import Job + + +@pytest.fixture +def orphan_job(deploy_jobtemplate): + return Job.objects.create( + job_template=None, + project=deploy_jobtemplate.project, + inventory=deploy_jobtemplate.inventory + ) + +@pytest.mark.django_db +def test_superuser_sees_orphans(admin_user, orphan_job): + access = JobAccess(admin_user) + assert access.can_read(orphan_job) + +@pytest.mark.django_db +def test_org_member_does_not_see_orphans(org_member, orphan_job, project): + # Check that privledged access to project still does not grant access + project.admin_role.members.add(org_member) + access = JobAccess(org_member) + assert not access.can_read(orphan_job) + +@pytest.mark.django_db +def test_org_admin_sees_orphans(org_admin, orphan_job): + access = JobAccess(org_admin) + assert access.can_read(orphan_job) From 320803020278a3b3b43bc5d206bec2cc3857a005 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 24 May 2016 11:51:37 -0400 Subject: [PATCH 2/3] job read access for org auditors --- awx/main/access.py | 9 +++++---- awx/main/tests/functional/conftest.py | 7 +++++++ awx/main/tests/functional/test_rbac_job.py | 5 +++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index 30b2bc1fa4..a8a3782e86 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -843,16 +843,17 @@ class JobAccess(BaseAccess): job_template__in=JobTemplate.accessible_objects(self.user, 'read_role') ) - admin_of_organizations_qs = self.user.admin_of_organizations - if not admin_of_organizations_qs.exists(): + org_access_qs = Organization.objects.filter( + Q(admin_role__members=self.user) | Q(auditor_role__members=self.user)) + if not org_access_qs.exists(): return qs_jt qs_scan_orphan = qs.filter( job_type=PERM_INVENTORY_SCAN, - inventory__organization__in=admin_of_organizations_qs + inventory__organization__in=org_access_qs ) qs_orphan = qs.filter( - project__organization__in=admin_of_organizations_qs + project__organization__in=org_access_qs ).exclude(job_type=PERM_INVENTORY_SCAN) return (qs_jt | qs_orphan | qs_scan_orphan).distinct() diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py index 081ffca21e..94223ceb58 100644 --- a/awx/main/tests/functional/conftest.py +++ b/awx/main/tests/functional/conftest.py @@ -215,6 +215,13 @@ def org_admin(user, organization): organization.member_role.members.add(ret) return ret +@pytest.fixture +def org_auditor(user, organization): + ret = user('org-auditor', False) + organization.auditor_role.members.add(ret) + organization.member_role.members.add(ret) + return ret + @pytest.fixture def org_member(user, organization): ret = user('org-member', False) diff --git a/awx/main/tests/functional/test_rbac_job.py b/awx/main/tests/functional/test_rbac_job.py index 4a176062d5..4d37e926bf 100644 --- a/awx/main/tests/functional/test_rbac_job.py +++ b/awx/main/tests/functional/test_rbac_job.py @@ -28,3 +28,8 @@ def test_org_member_does_not_see_orphans(org_member, orphan_job, project): def test_org_admin_sees_orphans(org_admin, orphan_job): access = JobAccess(org_admin) assert access.can_read(orphan_job) + +@pytest.mark.django_db +def test_org_auditor_sees_orphans(org_auditor, orphan_job): + access = JobAccess(org_auditor) + assert access.can_read(orphan_job) From 381e44c2a2ae912874688e1f2db852f2ad3de475 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Thu, 26 May 2016 16:42:05 -0400 Subject: [PATCH 3/3] updates for job can_delete --- awx/main/access.py | 28 +++++++---- awx/main/tests/functional/test_rbac_job.py | 57 ++++++++++++++++++---- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index a8a3782e86..802e5b4064 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -828,6 +828,16 @@ class JobTemplateAccess(BaseAccess): return self.user in obj.admin_role class JobAccess(BaseAccess): + ''' + I can see jobs when: + - I am a superuser. + - I can see its job template + - I am an admin or auditor of the organization which contains its inventory + - I am an admin or auditor of the organization which contains its project + I can delete jobs when: + - I am an admin of the organization which contains its inventory + - I am an admin of the organization which contains its project + ''' model = Job @@ -848,14 +858,10 @@ class JobAccess(BaseAccess): if not org_access_qs.exists(): return qs_jt - qs_scan_orphan = qs.filter( - job_type=PERM_INVENTORY_SCAN, - inventory__organization__in=org_access_qs - ) - qs_orphan = qs.filter( - project__organization__in=org_access_qs - ).exclude(job_type=PERM_INVENTORY_SCAN) - return (qs_jt | qs_orphan | qs_scan_orphan).distinct() + return qs.filter( + Q(job_template__in=JobTemplate.accessible_objects(self.user, 'read_role')) | + Q(inventory__organization__in=org_access_qs) | + Q(project__organization__in=org_access_qs)).distinct() def can_add(self, data): if not data or '_method' in data: # So the browseable API will work? @@ -885,7 +891,11 @@ class JobAccess(BaseAccess): @check_superuser def can_delete(self, obj): - return self.user in obj.inventory.admin_role + if obj.inventory is not None and self.user in obj.inventory.organization.admin_role: + return True + if obj.project is not None and self.user in obj.project.organization.admin_role: + return True + return False def can_start(self, obj): self.check_license() diff --git a/awx/main/tests/functional/test_rbac_job.py b/awx/main/tests/functional/test_rbac_job.py index 4d37e926bf..f54d661095 100644 --- a/awx/main/tests/functional/test_rbac_job.py +++ b/awx/main/tests/functional/test_rbac_job.py @@ -5,31 +5,68 @@ from awx.main.models import Job @pytest.fixture -def orphan_job(deploy_jobtemplate): +def normal_job(deploy_jobtemplate): return Job.objects.create( - job_template=None, + job_template=deploy_jobtemplate, project=deploy_jobtemplate.project, inventory=deploy_jobtemplate.inventory ) +# Read permissions testing @pytest.mark.django_db -def test_superuser_sees_orphans(admin_user, orphan_job): +def test_superuser_sees_orphans(normal_job, admin_user): + normal_job.job_template = None access = JobAccess(admin_user) - assert access.can_read(orphan_job) + assert access.can_read(normal_job) @pytest.mark.django_db -def test_org_member_does_not_see_orphans(org_member, orphan_job, project): +def test_org_member_does_not_see_orphans(normal_job, org_member, project): + normal_job.job_template = None # Check that privledged access to project still does not grant access project.admin_role.members.add(org_member) access = JobAccess(org_member) - assert not access.can_read(orphan_job) + assert not access.can_read(normal_job) @pytest.mark.django_db -def test_org_admin_sees_orphans(org_admin, orphan_job): +def test_org_admin_sees_orphans(normal_job, org_admin): + normal_job.job_template = None access = JobAccess(org_admin) - assert access.can_read(orphan_job) + assert access.can_read(normal_job) @pytest.mark.django_db -def test_org_auditor_sees_orphans(org_auditor, orphan_job): +def test_org_auditor_sees_orphans(normal_job, org_auditor): + normal_job.job_template = None access = JobAccess(org_auditor) - assert access.can_read(orphan_job) + assert access.can_read(normal_job) + +# Delete permissions testing +@pytest.mark.django_db +def test_JT_admin_delete_denied(normal_job, rando): + normal_job.job_template.admin_role.members.add(rando) + access = JobAccess(rando) + assert not access.can_delete(normal_job) + +@pytest.mark.django_db +def test_inventory_admin_delete_denied(normal_job, rando): + normal_job.job_template.inventory.admin_role.members.add(rando) + access = JobAccess(rando) + assert not access.can_delete(normal_job) + +@pytest.mark.django_db +def test_null_related_delete_denied(normal_job, rando): + normal_job.project = None + normal_job.inventory = None + access = JobAccess(rando) + assert not access.can_delete(normal_job) + +@pytest.mark.django_db +def test_inventory_org_admin_delete_allowed(normal_job, org_admin): + normal_job.project = None # do this so we test job->inventory->org->admin connection + access = JobAccess(org_admin) + assert access.can_delete(normal_job) + +@pytest.mark.django_db +def test_project_org_admin_delete_allowed(normal_job, org_admin): + normal_job.inventory = None # do this so we test job->project->org->admin connection + access = JobAccess(org_admin) + assert access.can_delete(normal_job)