Skip to content

Commit 6910c02

Browse files
committed
Implement role permissions exception the right way
1 parent 47de239 commit 6910c02

File tree

8 files changed

+41
-57
lines changed

8 files changed

+41
-57
lines changed

ansible_base/rbac/models/role.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -326,15 +326,6 @@ def user_global_permissions(cls, user, permission_qs=None):
326326
perm_set.update(perm_qs)
327327
return perm_set
328328

329-
def save_remote_permissions(self, additional_data: list[str]):
330-
"""Save permissions from an external system.
331-
332-
This does very little more than just making the related permissions exactly equal to the set.
333-
If a provided permission is not present in the local system, we pretend we did not get it.
334-
However, we must still allow removing permissions from the role.
335-
"""
336-
self.permissions.set(DABPermission.objects.filter(api_slug__in=additional_data))
337-
338329
def summary_fields(self):
339330
return {'id': self.id, 'name': self.name, 'description': self.description, 'managed': self.managed}
340331

ansible_base/resource_registry/registry.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.contrib.auth import authenticate
55
from django.utils.translation import gettext_lazy as _
66

7-
from ansible_base.resource_registry.utils.resource_type_processor import ResourceTypeProcessor
7+
from ansible_base.resource_registry.utils.resource_type_processor import ResourceTypeProcessor, RoleDefinitionProcessor
88

99
ParentResource = namedtuple("ParentResource", ["model", "field_name"])
1010
SharedResource = namedtuple("SharedResource", ["serializer", "is_provider"])
@@ -27,7 +27,7 @@ class ServiceAPIConfig:
2727
"shared.team": ResourceTypeProcessor,
2828
"shared.organization": ResourceTypeProcessor,
2929
"shared.user": ResourceTypeProcessor,
30-
"shared.roledefinition": ResourceTypeProcessor,
30+
"shared.roledefinition": RoleDefinitionProcessor,
3131
}
3232

3333
custom_resource_processors = {}

ansible_base/resource_registry/rest_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111
ResourceRequestBody = namedtuple(
1212
"ResourceRequestBody",
13-
["ansible_id", "service_id", "is_partially_migrated", "resource_type", "resource_data", "additional_data"],
14-
defaults=(None, None, None, None, None, None),
13+
["ansible_id", "service_id", "is_partially_migrated", "resource_type", "resource_data"],
14+
defaults=(None, None, None, None, None),
1515
)
1616

1717

ansible_base/resource_registry/serializers.py

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def create(self, validated_data):
9696

9797

9898
class ResourceSerializer(ResourceListSerializer):
99-
additional_data = serializers.JSONField(write_only=True, required=False)
99+
additional_data = serializers.SerializerMethodField()
100100
resource_data = ResourceDataField(source="*")
101101

102102
class Meta:
@@ -106,37 +106,12 @@ class Meta:
106106
"additional_data",
107107
]
108108

109-
def to_representation(self, instance):
110-
ret = super().to_representation(instance)
111-
112-
additional_data = None
113-
if serializer := instance.content_type.resource_type.serializer_class:
109+
def get_additional_data(self, obj):
110+
if serializer := obj.content_type.resource_type.serializer_class:
114111
if serializer.ADDITIONAL_DATA_SERIALIZER is not None:
115-
additional_data = serializer.ADDITIONAL_DATA_SERIALIZER(instance.content_object).data
116-
117-
ret['additional_data'] = additional_data
118-
return ret
119-
120-
def _maybe_process_additional_data(self, instance, additional_data):
121-
if not additional_data:
122-
return
112+
return serializer.ADDITIONAL_DATA_SERIALIZER(obj.content_object).data
123113

124-
if serializer := instance.content_type.resource_type.serializer_class:
125-
if serializer and hasattr(serializer, 'process_additional_data'):
126-
logger.debug(f'Processing additional data for resource {str(instance.ansible_id)}')
127-
serializer(instance.content_object).process_additional_data(instance.content_object, additional_data)
128-
129-
def create(self, validated_data):
130-
additional_data = validated_data.pop('additional_data', None)
131-
instance = super().create(validated_data)
132-
self._maybe_process_additional_data(instance, additional_data)
133-
return instance
134-
135-
def update(self, instance, validated_data):
136-
additional_data = validated_data.pop('additional_data', None)
137-
instance = super().update(instance, validated_data)
138-
self._maybe_process_additional_data(instance, additional_data)
139-
return instance
114+
return None
140115

141116

142117
class ResourceTypeSerializer(serializers.ModelSerializer):

ansible_base/resource_registry/shared_types.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from rest_framework import serializers
22

3-
from ansible_base.rbac.models import DABContentType
3+
from ansible_base.rbac.models import DABContentType, DABPermission
44
from ansible_base.resource_registry.utils.resource_type_serializers import AnsibleResourceForeignKeyField, SharedResourceTypeSerializer
55
from ansible_base.resource_registry.utils.sso_provider import get_sso_provider_server
66

@@ -87,6 +87,17 @@ class RoleDefinitionPermissionsSerializer(serializers.Serializer):
8787
)
8888

8989

90+
class LenientPermissionSlugListField(serializers.ListField):
91+
child = serializers.CharField()
92+
93+
def to_internal_value(self, data):
94+
data = super().to_internal_value(data)
95+
return list(DABPermission.objects.filter(api_slug__in=data))
96+
97+
def to_representation(self, value):
98+
return [perm.api_slug for perm in value.all() if perm is not None]
99+
100+
90101
class RoleDefinitionType(SharedResourceTypeSerializer):
91102
RESOURCE_TYPE = "roledefinition"
92103
ADDITIONAL_DATA_SERIALIZER = RoleDefinitionPermissionsSerializer
@@ -101,6 +112,4 @@ class RoleDefinitionType(SharedResourceTypeSerializer):
101112
allow_null=True,
102113
default=None,
103114
)
104-
105-
def process_additional_data(self, instance, additional_data):
106-
instance.save_remote_permissions(additional_data.get('permissions'))
115+
permissions = LenientPermissionSlugListField()

ansible_base/resource_registry/utils/resource_type_processor.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,18 @@ def save(self, validated_data, is_new=False):
3939

4040
self.instance.save()
4141
return self.instance
42+
43+
44+
class RoleDefinitionProcessor(ResourceTypeProcessor):
45+
def save(self, validated_data, is_new=False):
46+
permissions = None # many-to-many field
47+
for k, val in validated_data.items():
48+
if k == 'permissions':
49+
permissions = val
50+
else:
51+
setattr(self.instance, k, val)
52+
53+
self.instance.save()
54+
if permissions is not None:
55+
self.instance.permissions.set(permissions)
56+
return self.instance

test_app/tests/rbac/models/test_role_definition.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,3 @@ def test_change_role_definition_member_permission(organization, inventory, org_t
105105
# Adding it back restores them
106106
member_rd.permissions.add(member_perm)
107107
assert [u.has_obj_perm(inventory, 'change') for u in (team_user, org_team_user)] == [True, True]
108-
109-
110-
@pytest.mark.django_db
111-
def test_save_remote_permissions(inv_rd):
112-
"This is tested as part of resource registry in test_additional_data_write"
113-
assert {perm.api_slug for perm in inv_rd.permissions.all()} == {'aap.view_inventory', 'aap.change_inventory'}
114-
assert len(DABPermission.objects.filter(api_slug__in={'aap.view_inventory', 'fooland.not_here'})) == 1
115-
inv_rd.permissions.set([DABPermission.objects.get(api_slug='aap.view_inventory')])
116-
assert {perm.api_slug for perm in inv_rd.permissions.all()} == {'aap.view_inventory'}

test_app/tests/resource_registry/test_resources_api_rest_client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,10 @@ def test_additional_data_write(resource_client, partial):
219219
assert resp.status_code == 200
220220
ref = resp.json()
221221

222-
data = ResourceRequestBody(additional_data={'permissions': ['aap.view_inventory', 'fooland.action_unicorns']}, resource_data=ref['resource_data'])
222+
res_data = ref['resource_data']
223+
res_data['permissions'] = ['aap.view_inventory', 'fooland.action_unicorns']
224+
225+
data = ResourceRequestBody(resource_data=res_data)
223226
resp = resource_client.update_resource(ansible_id, data, partial=partial)
224227
assert resp.status_code == 200, resp.__dict__
225228

0 commit comments

Comments
 (0)