diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 7fa64fdc5f..446dc4a38e 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -720,9 +720,19 @@ class TeamRolesList(SubListAttachDetachAPIView): team = get_object_or_404(models.Team, pk=self.kwargs['pk']) credential_content_type = ContentType.objects.get_for_model(models.Credential) if role.content_type == credential_content_type: - if not role.content_object.organization or role.content_object.organization.id != team.organization.id: - data = dict(msg=_("You cannot grant credential access to a team when the Organization field isn't set, or belongs to a different organization")) + if not role.content_object.organization: + data = dict( + msg=_("You cannot grant access to a credential that is not assigned to an organization (private credentials cannot be assigned to teams)") + ) return Response(data, status=status.HTTP_400_BAD_REQUEST) + elif role.content_object.organization.id != team.organization.id: + if not request.user.is_superuser: + data = dict( + msg=_( + "You cannot grant a team access to a credential in a different organization. Only superusers can grant cross-organization credential access to teams" + ) + ) + return Response(data, status=status.HTTP_400_BAD_REQUEST) return super(TeamRolesList, self).post(request, *args, **kwargs) @@ -4203,9 +4213,21 @@ class RoleTeamsList(SubListAttachDetachAPIView): credential_content_type = ContentType.objects.get_for_model(models.Credential) if role.content_type == credential_content_type: - if not role.content_object.organization or role.content_object.organization.id != team.organization.id: - data = dict(msg=_("You cannot grant credential access to a team when the Organization field isn't set, or belongs to a different organization")) + # Private credentials (no organization) are never allowed for teams + if not role.content_object.organization: + data = dict( + msg=_("You cannot grant access to a credential that is not assigned to an organization (private credentials cannot be assigned to teams)") + ) return Response(data, status=status.HTTP_400_BAD_REQUEST) + # Cross-organization credentials are only allowed for superusers + elif role.content_object.organization.id != team.organization.id: + if not request.user.is_superuser: + data = dict( + msg=_( + "You cannot grant a team access to a credential in a different organization. Only superusers can grant cross-organization credential access to teams" + ) + ) + return Response(data, status=status.HTTP_400_BAD_REQUEST) action = 'attach' if request.data.get('disassociate', None): diff --git a/awx/main/tests/functional/api/test_credential.py b/awx/main/tests/functional/api/test_credential.py index 2ed8a6a88c..7956ebed4f 100644 --- a/awx/main/tests/functional/api/test_credential.py +++ b/awx/main/tests/functional/api/test_credential.py @@ -287,6 +287,72 @@ def test_sa_grant_private_credential_to_team_through_role_teams(post, credential assert response.status_code == 400 +@pytest.mark.django_db +def test_grant_credential_to_team_different_organization_through_role_teams(post, get, credential, organizations, admin, org_admin, team, team_member): + # # Test that credential from different org can be assigned to team by a superuser through role_teams_list endpoint + orgs = organizations(2) + credential.organization = orgs[0] + credential.save() + team.organization = orgs[1] + team.save() + + # Non-superuser (org_admin) trying cross-org assignment should be denied + response = post(reverse('api:role_teams_list', kwargs={'pk': credential.use_role.id}), {'id': team.id}, org_admin) + assert response.status_code == 400 + assert ( + "You cannot grant a team access to a credential in a different organization. Only superusers can grant cross-organization credential access to teams" + in response.data['msg'] + ) + + # Superuser (admin) can do cross-org assignment + response = post(reverse('api:role_teams_list', kwargs={'pk': credential.use_role.id}), {'id': team.id}, admin) + assert response.status_code == 204 + + assert credential.use_role in team.member_role.children.all() + assert team_member in credential.read_role + assert team_member in credential.use_role + assert team_member not in credential.admin_role + + +@pytest.mark.django_db +def test_grant_credential_to_team_different_organization(post, get, credential, organizations, admin, org_admin, team, team_member): + # Test that credential from different org can be assigned to team by a superuser + orgs = organizations(2) + credential.organization = orgs[0] + credential.save() + team.organization = orgs[1] + team.save() + + # Non-superuser (org_admin, ...) trying cross-org assignment should be denied + response = post(reverse('api:team_roles_list', kwargs={'pk': team.id}), {'id': credential.use_role.id}, org_admin) + assert response.status_code == 400 + assert ( + "You cannot grant a team access to a credential in a different organization. Only superusers can grant cross-organization credential access to teams" + in response.data['msg'] + ) + + # Superuser (system admin) can do cross-org assignment + response = post(reverse('api:team_roles_list', kwargs={'pk': team.id}), {'id': credential.use_role.id}, admin) + assert response.status_code == 204 + + assert credential.use_role in team.member_role.children.all() + + assert team_member in credential.read_role + assert team_member in credential.use_role + assert team_member not in credential.admin_role + + # Team member can see the credential in API + response = get(reverse('api:team_credentials_list', kwargs={'pk': team.id}), team_member) + assert response.status_code == 200 + assert response.data['count'] == 1 + assert response.data['results'][0]['id'] == credential.id + + # Team member can see the credential in general credentials API + response = get(reverse('api:credential_list'), team_member) + assert response.status_code == 200 + assert any(cred['id'] == credential.id for cred in response.data['results']) + + @pytest.mark.django_db def test_sa_grant_private_credential_to_team_through_team_roles(post, credential, admin, team): # not even a system admin can grant a private cred to a team though