Skip to content

Commit 6ae0e0e

Browse files
committed
Handle sync of system roles
1 parent b4e8524 commit 6ae0e0e

File tree

3 files changed

+66
-10
lines changed

3 files changed

+66
-10
lines changed

ansible_base/rbac/models/permission.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@
77

88
class DABPermissionManager(models.Manager):
99
def load_remote_objects(self, remote_data: list[dict], update_managed=False):
10+
"""Load the permission list from a remote system, requires types are loaded first
11+
12+
The remote_data should be the results from the /service-index/role-permissions/ endpoint
13+
of another system.
14+
This will save those remote permissions into the local database so we can track remote RBAC.
15+
16+
update_managed being True will refresh the managed roles like Organization Admin
17+
so that if the algorithm includes any new permissions, those are added.
18+
"""
1019
for remote_type in remote_data:
1120
codename = remote_type.pop('codename')
1221
ct_slug = remote_type.pop('content_type')

ansible_base/rbac/service_api/serializers.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,16 @@ def validate(self, attrs):
8787
has_oid = bool(self.initial_data.get('object_id'))
8888
has_oaid = bool(self.initial_data.get('object_ansible_id'))
8989

90-
if not self.partial and not has_oid and not has_oaid:
91-
raise serializers.ValidationError("You must provide either 'object_id' or 'object_ansible_id'.")
92-
elif not has_oaid:
93-
# need to remove blank and null fields or else it can overwrite the non-null non-blank field
94-
attrs['object_id'] = self.initial_data['object_id']
90+
rd = attrs['role_definition']
91+
if rd.content_type_id:
92+
if not self.partial and not has_oid and not has_oaid:
93+
raise serializers.ValidationError("You must provide either 'object_id' or 'object_ansible_id'.")
94+
elif not has_oaid:
95+
# need to remove blank and null fields or else it can overwrite the non-null non-blank field
96+
attrs['object_id'] = self.initial_data['object_id']
97+
else:
98+
if has_oaid or has_oid:
99+
raise serializers.ValidationError("Can not provide either 'object_id' or 'object_ansible_id' for system role")
95100

96101
# NOTE: right now not enforcing the case you provide both, could check for consistency later
97102

@@ -100,9 +105,12 @@ def validate(self, attrs):
100105
def find_existing_assignment(self, queryset):
101106
actor = self.validated_data[self.actor_field]
102107
role_definition = self.validated_data['role_definition']
103-
object_id = self.validated_data['object_id']
104-
filter_kwargs = {self.actor_field: actor}
105-
return queryset.filter(role_definition=role_definition, object_id=object_id, **filter_kwargs).first()
108+
filter_kwargs = {self.actor_field: actor, 'role_definition': role_definition}
109+
if role_definition.content_type_id:
110+
filter_kwargs['object_id'] = self.validated_data['object_id']
111+
else:
112+
filter_kwargs['object_id'] = None
113+
return queryset.filter(**filter_kwargs).first()
106114

107115
def create(self, validated_data):
108116
rd = validated_data['role_definition']
@@ -115,7 +123,7 @@ def create(self, validated_data):
115123
# Unlike the public view, the action is attributed to the specified user in data
116124
with impersonate(as_user):
117125

118-
object_id = validated_data['object_id']
126+
object_id = validated_data.get('object_id')
119127
obj = None
120128
if object_id:
121129
model = rd.content_type.model_class()

test_app/tests/rbac/remote/test_service_api.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import pytest
44

55
from ansible_base.lib.utils.response import get_relative_url
6-
from ansible_base.rbac.models import DABContentType, DABPermission
6+
from ansible_base.rbac.models import DABContentType, DABPermission, RoleDefinition
7+
from test_app.models import Team, User
78

89

910
@pytest.mark.django_db
@@ -196,6 +197,44 @@ def test_unassign_endpoint_for_team(team, org_inv_rd, inventory, admin_api_clien
196197
assert not rando.has_obj_perm(inventory, 'change')
197198

198199

200+
@pytest.mark.django_db
201+
@pytest.mark.parametrize('actor_type', ['user', 'team'])
202+
def test_assign_and_unassign_system_role(inventory, admin_api_client, actor_type, organization, member_rd):
203+
if actor_type == 'user':
204+
actor = User.objects.create(username='user1')
205+
user = actor
206+
else:
207+
actor = Team.objects.create(name='random_team', organization=organization)
208+
user = User.objects.create(username='user1')
209+
member_rd.give_permission(user, actor)
210+
211+
rd = RoleDefinition.objects.managed.sys_auditor
212+
assert 'view_inventory' in set(rd.permissions.values_list('codename', flat=True))
213+
assert not user.has_obj_perm(inventory, 'view')
214+
215+
url = get_relative_url(f'service{actor_type}assignment-assign')
216+
data = {"role_definition": rd.name, f"{actor_type}_ansible_id": str(actor.resource.ansible_id)}
217+
response = admin_api_client.post(url, data)
218+
assert response.status_code == 201, response.data
219+
if hasattr(actor, '_singleton_permissions'):
220+
delattr(actor, '_singleton_permissions')
221+
assert user.has_obj_perm(inventory, 'view') # gave system wide view permission
222+
223+
# Second try, response code indicates global assignment already exists
224+
response = admin_api_client.post(url, data=data)
225+
assert response.status_code == 200, response.data
226+
227+
unassign_url = get_relative_url(f'service{actor_type}assignment-unassign')
228+
response = admin_api_client.post(unassign_url, data)
229+
assert response.status_code == 204, response.data
230+
if hasattr(actor, '_singleton_permissions'):
231+
delattr(actor, '_singleton_permissions')
232+
assert not user.has_obj_perm(inventory, 'view') # permission removed
233+
234+
response = admin_api_client.post(unassign_url, data)
235+
assert response.status_code == 200, response.data
236+
237+
199238
@pytest.mark.django_db
200239
def test_filter_assignment_list(admin_api_client, rando, inv_rd, view_inv_rd, org_inv_rd, inventory):
201240
inv_rd.give_permission(rando, inventory)

0 commit comments

Comments
 (0)