diff --git a/awx/main/access.py b/awx/main/access.py index 93f94489a6..33df6677a0 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -740,12 +740,13 @@ class InventoryAccess(BaseAccess): # If no data is specified, just checking for generic add permission? if not data: return Organization.accessible_objects(self.user, 'inventory_admin_role').exists() - - return self.check_related('organization', Organization, data, role_field='inventory_admin_role') + return (self.check_related('organization', Organization, data, role_field='inventory_admin_role') and + self.check_related('insights_credential', Credential, data, role_field='use_role')) @check_superuser def can_change(self, obj, data): - return self.can_admin(obj, data) + return (self.can_admin(obj, data) and + self.check_related('insights_credential', Credential, data, obj=obj, role_field='use_role')) @check_superuser def can_admin(self, obj, data): @@ -1198,14 +1199,15 @@ class ProjectAccess(BaseAccess): @check_superuser def can_add(self, data): if not data: # So the browseable API will work - return Organization.accessible_objects(self.user, 'project_admin_role').exists() - return self.check_related('organization', Organization, data, role_field='project_admin_role', mandatory=True) + return Organization.accessible_objects(self.user, 'admin_role').exists() + return (self.check_related('organization', Organization, data, mandatory=True) and + self.check_related('credential', Credential, data, role_field='use_role')) @check_superuser def can_change(self, obj, data): - if not self.check_related('organization', Organization, data, obj=obj, role_field='project_admin_role'): - return False - return self.user in obj.admin_role + return (self.check_related('organization', Organization, data, obj=obj, role_field='project_admin_role') and + self.user in obj.admin_role and + self.check_related('credential', Credential, data, obj=obj, role_field='use_role')) @check_superuser def can_start(self, obj, validate_license=True): diff --git a/awx/main/tests/functional/api/test_inventory.py b/awx/main/tests/functional/api/test_inventory.py index 2e4b7df63e..fb2da5f804 100644 --- a/awx/main/tests/functional/api/test_inventory.py +++ b/awx/main/tests/functional/api/test_inventory.py @@ -450,6 +450,17 @@ class TestInsightsCredential: {'insights_credential': insights_credential.id}, admin_user, expect=200) + def test_insights_credential_protection(self, post, patch, insights_inventory, alice, insights_credential): + insights_inventory.organization.admin_role.members.add(alice) + insights_inventory.admin_role.members.add(alice) + post(reverse('api:inventory_list'), { + "name": "test", + "organization": insights_inventory.organization.id, + "insights_credential": insights_credential.id + }, alice, expect=403) + patch(insights_inventory.get_absolute_url(), + {'insights_credential': insights_credential.id}, alice, expect=403) + def test_non_insights_credential(self, patch, insights_inventory, admin_user, scm_credential): patch(insights_inventory.get_absolute_url(), {'insights_credential': scm_credential.id}, admin_user, diff --git a/awx/main/tests/functional/test_copy.py b/awx/main/tests/functional/test_copy.py index 0b651f59ca..bdfc4936c2 100644 --- a/awx/main/tests/functional/test_copy.py +++ b/awx/main/tests/functional/test_copy.py @@ -52,6 +52,7 @@ def test_project_copy(post, get, project, organization, scm_credential, alice): reverse('api:project_copy', kwargs={'pk': project.pk}), alice, expect=200 ).data['can_copy'] is False project.organization.admin_role.members.add(alice) + scm_credential.use_role.members.add(alice) assert get( reverse('api:project_copy', kwargs={'pk': project.pk}), alice, expect=200 ).data['can_copy'] is True diff --git a/awx/main/tests/functional/test_projects.py b/awx/main/tests/functional/test_projects.py index 55cc484006..f8466355f1 100644 --- a/awx/main/tests/functional/test_projects.py +++ b/awx/main/tests/functional/test_projects.py @@ -209,6 +209,25 @@ def test_create_project(post, organization, org_admin, org_member, admin, rando, assert Project.objects.filter(name='Project', organization=organization).exists() +@pytest.mark.django_db +def test_project_credential_protection(post, put, project, organization, scm_credential, org_admin): + project.save() + project.admin_role.members.add(org_admin) + put( + reverse('api:project_detail', kwargs={'pk':project.id}), { + 'name': 'should not change', + 'credential': scm_credential.id + }, org_admin, expect=403 + ) + post( + reverse('api:project_list'), { + 'name': 'should not create', + 'organization':organization.id, + 'credential': scm_credential.id + }, org_admin, expect=403 + ) + + @pytest.mark.django_db() def test_create_project_null_organization(post, organization, admin): post(reverse('api:project_list'), { 'name': 't', 'organization': None}, admin, expect=201)