From 94e5795dfc37b95c576d61f3e3b4e936c021548c Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Tue, 2 Jul 2024 17:05:22 -0400 Subject: [PATCH] Prevent assigning credential to user of other org (#15296) Utilizes the `validate_role_assignment` callback from dab (see dab PR #490) to prevent granting credential access to a user of another organization. This logic will work for role_user_assignments and role_team_assignments endpoints. Signed-off-by: Seth Foster --- awx/main/models/credential/__init__.py | 14 ++++++++++++ .../functional/dab_rbac/test_dab_rbac_api.py | 22 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/awx/main/models/credential/__init__.py b/awx/main/models/credential/__init__.py index 894b16ed2c36..489361ce7cf0 100644 --- a/awx/main/models/credential/__init__.py +++ b/awx/main/models/credential/__init__.py @@ -21,6 +21,10 @@ from django.utils.encoding import force_str from django.utils.functional import cached_property from django.utils.timezone import now +from django.contrib.auth.models import User + +# DRF +from rest_framework.serializers import ValidationError as DRFValidationError # AWX from awx.api.versioning import reverse @@ -41,6 +45,7 @@ ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, ROLE_SINGLETON_SYSTEM_AUDITOR, ) +from awx.main.models import Team, Organization from awx.main.utils import encrypt_field from . import injectors as builtin_injectors @@ -315,6 +320,15 @@ def _get_dynamic_input(self, field_name): else: raise ValueError('{} is not a dynamic input field'.format(field_name)) + def validate_role_assignment(self, actor, role_definition): + if isinstance(actor, User): + if actor.is_superuser or Organization.access_qs(actor, 'change').filter(id=self.organization.id).exists(): + return + if isinstance(actor, Team): + if actor.organization == self.organization: + return + raise DRFValidationError({'detail': _(f"You cannot grant credential access to a {actor._meta.object_name} not in the credentials' organization")}) + class CredentialType(CommonModelNameNotUnique): """ diff --git a/awx/main/tests/functional/dab_rbac/test_dab_rbac_api.py b/awx/main/tests/functional/dab_rbac/test_dab_rbac_api.py index 4d2f91129e68..ec7885a5330e 100644 --- a/awx/main/tests/functional/dab_rbac/test_dab_rbac_api.py +++ b/awx/main/tests/functional/dab_rbac/test_dab_rbac_api.py @@ -121,6 +121,28 @@ def test_workflow_creation_permissions(setup_managed_roles, organization, workfl assert access.can_add({'name': 'foo-flow', 'organization': organization.pk}) +@pytest.mark.django_db +def test_assign_credential_to_user_of_another_org(setup_managed_roles, credential, admin_user, rando, org_admin, organization, post): + '''Test that a credential can only be assigned to a user in the same organization''' + # cannot assign credential to rando, as rando is not in the same org as the credential + rd = RoleDefinition.objects.get(name="Credential Admin") + credential.organization = organization + credential.save(update_fields=['organization']) + assert credential.organization not in Organization.access_qs(rando, 'change') + url = django_reverse('roleuserassignment-list') + resp = post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=400) + assert "You cannot grant credential access to a User not in the credentials' organization" in str(resp.data) + + # can assign credential to superuser + rando.is_superuser = True + rando.save() + post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=201) + + # can assign credential to org_admin + assert credential.organization in Organization.access_qs(org_admin, 'change') + post(url=url, data={"user": org_admin.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=201) + + @pytest.mark.django_db @override_settings(ALLOW_LOCAL_RESOURCE_MANAGEMENT=False) def test_team_member_role_not_assignable(team, rando, post, admin_user, setup_managed_roles):