Skip to content

Commit 302d51e

Browse files
Make resource_registry work without rbac
1 parent 97d5317 commit 302d51e

File tree

7 files changed

+296
-23
lines changed

7 files changed

+296
-23
lines changed

ansible_base/resource_registry/registry.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from collections import namedtuple
22
from typing import List, Optional
33

4+
from django.conf import settings
45
from django.contrib.auth import authenticate
56
from django.utils.translation import gettext_lazy as _
67

@@ -23,12 +24,16 @@ class ServiceAPIConfig:
2324
This will be the interface for configuring the resource registry for each service.
2425
"""
2526

26-
_default_resource_processors = {
27-
"shared.team": ResourceTypeProcessor,
28-
"shared.organization": ResourceTypeProcessor,
29-
"shared.user": ResourceTypeProcessor,
30-
"shared.roledefinition": RoleDefinitionProcessor,
31-
}
27+
@classmethod
28+
def _get_default_resource_processors(cls):
29+
processors = {
30+
"shared.team": ResourceTypeProcessor,
31+
"shared.organization": ResourceTypeProcessor,
32+
"shared.user": ResourceTypeProcessor,
33+
}
34+
if 'ansible_base.rbac' in settings.INSTALLED_APPS:
35+
processors["shared.roledefinition"] = RoleDefinitionProcessor
36+
return processors
3237

3338
custom_resource_processors = {}
3439

@@ -43,7 +48,7 @@ def authenticate_local_user(username: str, password: str):
4348

4449
@classmethod
4550
def get_processor(cls, resource_type):
46-
combined_processors = {**cls._default_resource_processors, **cls.custom_resource_processors}
51+
combined_processors = {**cls._get_default_resource_processors(), **cls.custom_resource_processors}
4752
return combined_processors[resource_type]
4853

4954

ansible_base/resource_registry/rest_client.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,17 @@
66
import requests
77
import urllib3
88
from django.apps import apps
9+
from django.conf import settings
910

1011
from ansible_base.resource_registry.resource_server import get_resource_server_config, get_service_token
1112

13+
14+
def _check_rbac_installed():
15+
"""Check if ansible_base.rbac is installed and raise RuntimeError if not."""
16+
if 'ansible_base.rbac' not in settings.INSTALLED_APPS:
17+
raise RuntimeError("This operation requires ansible_base.rbac to be installed")
18+
19+
1220
ResourceRequestBody = namedtuple(
1321
"ResourceRequestBody",
1422
["ansible_id", "service_id", "is_partially_migrated", "resource_type", "resource_data"],
@@ -166,26 +174,31 @@ def get_resource_type_manifest(self, name, filters: Optional[dict] = None):
166174

167175
# RBAC related methods
168176
def list_role_types(self, filters: Optional[dict] = None):
177+
_check_rbac_installed()
169178
return self._make_request("get", "role-types/", params=filters)
170179

171180
def list_role_permissions(self, filters: Optional[dict] = None):
181+
_check_rbac_installed()
172182
return self._make_request("get", "role-permissions/", params=filters)
173183

174184
def list_user_assignments(self, user_ansible_id: Optional[str] = None, filters: Optional[dict] = None):
175185
"""List user role assignments."""
186+
_check_rbac_installed()
176187
params = (filters or {}).copy()
177188
if user_ansible_id is not None:
178189
params['user_ansible_id'] = user_ansible_id
179190
return self._make_request("get", "role-user-assignments/", params=params)
180191

181192
def list_team_assignments(self, team_ansible_id: Optional[str] = None, filters: Optional[dict] = None):
182193
"""List team role assignments."""
194+
_check_rbac_installed()
183195
params = (filters or {}).copy()
184196
if team_ansible_id is not None:
185197
params['team_ansible_id'] = team_ansible_id
186198
return self._make_request("get", "role-team-assignments/", params=params)
187199

188200
def sync_assignment(self, assignment):
201+
_check_rbac_installed()
189202
from ansible_base.rbac.service_api.serializers import ServiceRoleTeamAssignmentSerializer, ServiceRoleUserAssignmentSerializer
190203

191204
if assignment._meta.model_name == 'roleuserassignment':
@@ -196,6 +209,7 @@ def sync_assignment(self, assignment):
196209
return self._sync_assignment(serializer.data)
197210

198211
def sync_unassignment(self, role_definition, actor, content_object):
212+
_check_rbac_installed()
199213
data = {'role_definition': role_definition.name}
200214
data[f'{actor._meta.model_name}_ansible_id'] = str(actor.resource.ansible_id)
201215

@@ -214,6 +228,7 @@ def sync_unassignment(self, role_definition, actor, content_object):
214228

215229
def sync_object_deletion(self, content_object):
216230
"""Sync object deletion to Gateway for cleanup of all related role assignments"""
231+
_check_rbac_installed()
217232
from ansible_base.rbac.models import DABContentType
218233

219234
# Get the content type information

ansible_base/resource_registry/shared_types.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
from django.conf import settings
12
from rest_framework import serializers
23
from rest_framework.exceptions import ValidationError
34

4-
from ansible_base.rbac.models import DABContentType, DABPermission
55
from ansible_base.resource_registry.utils.resource_type_serializers import AnsibleResourceForeignKeyField, SharedResourceTypeSerializer
66
from ansible_base.resource_registry.utils.sso_provider import get_sso_provider_server
77

@@ -84,6 +84,10 @@ class LenientPermissionSlugListField(serializers.ListField):
8484
child = serializers.CharField()
8585

8686
def to_internal_value(self, data):
87+
if 'ansible_base.rbac' not in settings.INSTALLED_APPS:
88+
raise RuntimeError("LenientPermissionSlugListField requires ansible_base.rbac to be installed")
89+
from ansible_base.rbac.models import DABPermission
90+
8791
data = super().to_internal_value(data)
8892
return list(DABPermission.objects.filter(api_slug__in=data))
8993

@@ -98,14 +102,24 @@ class RoleDefinitionType(SharedResourceTypeSerializer):
98102
name = serializers.CharField()
99103
description = serializers.CharField(default="", allow_blank=True)
100104
managed = serializers.BooleanField()
101-
content_type = serializers.SlugRelatedField(
102-
slug_field='api_slug',
103-
queryset=DABContentType.objects.all(),
104-
allow_null=True,
105-
default=None,
106-
)
107105
permissions = LenientPermissionSlugListField()
108106

107+
def __init__(self, *args, **kwargs):
108+
if 'ansible_base.rbac' not in settings.INSTALLED_APPS:
109+
raise RuntimeError("RoleDefinitionType requires ansible_base.rbac to be installed")
110+
111+
super().__init__(*args, **kwargs)
112+
113+
# Set up content_type field only when rbac is available
114+
from ansible_base.rbac.models import DABContentType
115+
116+
self.fields['content_type'] = serializers.SlugRelatedField(
117+
slug_field='api_slug',
118+
queryset=DABContentType.objects.all(),
119+
allow_null=True,
120+
default=None,
121+
)
122+
109123
def is_valid(self, raise_exception=False):
110124
try:
111125
return super().is_valid(raise_exception=raise_exception)

ansible_base/resource_registry/tasks/sync.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@
1818
from django.db.utils import Error, IntegrityError
1919
from requests import HTTPError
2020

21-
from ansible_base.rbac.models.role import AssignmentBase, RoleDefinition, RoleTeamAssignment, RoleUserAssignment
2221
from ansible_base.resource_registry.models import Resource, ResourceType
2322
from ansible_base.resource_registry.models.service_identifier import service_id
2423
from ansible_base.resource_registry.registry import get_registry
2524
from ansible_base.resource_registry.rest_client import ResourceAPIClient, get_resource_server_client
2625

2726
logger = logging.getLogger('ansible_base.resources_api.tasks.sync')
2827

28+
_is_rbac_installed = 'ansible_base.rbac' in settings.INSTALLED_APPS
29+
2930

3031
class ManifestNotFound(HTTPError):
3132
"""Raise when server returns 404 for a manifest"""
@@ -139,7 +140,9 @@ def fetch_manifest(
139140
return [ManifestItem(**row) for row in csv_reader]
140141

141142

142-
def get_ansible_id_or_pk(assignment: AssignmentBase) -> str:
143+
def get_ansible_id_or_pk(assignment) -> str:
144+
if not _is_rbac_installed:
145+
raise RuntimeError("get_ansible_id_or_pk requires ansible_base.rbac to be installed")
143146
# For object-scoped assignments, try to get the object's ansible_id
144147
if assignment.content_type.model in ('organization', 'team'):
145148
object_resource = Resource.objects.filter(object_id=assignment.object_id, content_type__model=assignment.content_type.model).first()
@@ -153,7 +156,9 @@ def get_ansible_id_or_pk(assignment: AssignmentBase) -> str:
153156
return str(ansible_id_or_pk)
154157

155158

156-
def get_content_object(role_definition: RoleDefinition, assignment_tuple: AssignmentTuple) -> Any:
159+
def get_content_object(role_definition, assignment_tuple: AssignmentTuple) -> Any:
160+
if not _is_rbac_installed:
161+
raise RuntimeError("get_content_object requires ansible_base.rbac to be installed")
157162
content_object = None
158163
if role_definition.content_type.model in ('organization', 'team'):
159164
object_resource = Resource.objects.get(ansible_id=assignment_tuple.ansible_id_or_pk)
@@ -167,6 +172,8 @@ def get_content_object(role_definition: RoleDefinition, assignment_tuple: Assign
167172

168173
def get_remote_assignments(api_client: ResourceAPIClient) -> set[AssignmentTuple]:
169174
"""Fetch remote assignments from the resource server and convert to tuples."""
175+
if not _is_rbac_installed:
176+
raise RuntimeError("get_remote_assignments requires ansible_base.rbac to be installed")
170177
assignments = set()
171178

172179
# Fetch user assignments with pagination
@@ -238,6 +245,10 @@ def get_remote_assignments(api_client: ResourceAPIClient) -> set[AssignmentTuple
238245

239246
def get_local_assignments() -> set[AssignmentTuple]:
240247
"""Get local assignments and convert to tuples."""
248+
if not _is_rbac_installed:
249+
raise RuntimeError("get_local_assignments requires ansible_base.rbac to be installed")
250+
from ansible_base.rbac.models.role import RoleTeamAssignment, RoleUserAssignment
251+
241252
assignments = set()
242253

243254
# Get user assignments
@@ -294,6 +305,10 @@ def get_local_assignments() -> set[AssignmentTuple]:
294305

295306
def delete_local_assignment(assignment_tuple: AssignmentTuple) -> bool:
296307
"""Delete a local assignment based on the tuple."""
308+
if not _is_rbac_installed:
309+
raise RuntimeError("delete_local_assignment requires ansible_base.rbac to be installed")
310+
from ansible_base.rbac.models.role import RoleDefinition
311+
297312
try:
298313
role_definition = RoleDefinition.objects.get(name=assignment_tuple.role_definition_name)
299314

@@ -320,6 +335,10 @@ def delete_local_assignment(assignment_tuple: AssignmentTuple) -> bool:
320335

321336
def create_local_assignment(assignment_tuple: AssignmentTuple) -> bool:
322337
"""Create a local assignment based on the tuple."""
338+
if not _is_rbac_installed:
339+
raise RuntimeError("create_local_assignment requires ansible_base.rbac to be installed")
340+
from ansible_base.rbac.models.role import RoleDefinition
341+
323342
try:
324343
role_definition = RoleDefinition.objects.get(name=assignment_tuple.role_definition_name)
325344

@@ -694,6 +713,10 @@ def _sync_assignments(self):
694713
if not self.sync_assignments:
695714
return
696715

716+
if not _is_rbac_installed:
717+
self.write(">>> Skipping role assignments sync (rbac not installed)")
718+
return
719+
697720
self.write(">>> Syncing role assignments")
698721

699722
try:

test_app/resource_api.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
from django.conf import settings
12
from django.contrib.auth import get_user_model
23

34
from ansible_base.authentication.models import Authenticator
4-
from ansible_base.rbac.models import RoleDefinition
55
from ansible_base.resource_registry.registry import ResourceConfig, ServiceAPIConfig, SharedResource
6-
from ansible_base.resource_registry.shared_types import OrganizationType, RoleDefinitionType, TeamType, UserType
6+
from ansible_base.resource_registry.shared_types import OrganizationType, TeamType, UserType
77
from ansible_base.resource_registry.utils.resource_type_processor import ResourceTypeProcessor
88
from test_app.models import Organization, Original1, Proxy2, ResourceMigrationTestModel, Team
99

@@ -38,13 +38,21 @@ class APIConfig(ServiceAPIConfig):
3838
Organization,
3939
shared_resource=SharedResource(serializer=OrganizationType, is_provider=False),
4040
),
41-
ResourceConfig(
42-
RoleDefinition,
43-
shared_resource=SharedResource(serializer=RoleDefinitionType, is_provider=False),
44-
),
4541
# Authenticators won't be a shared resource in production, but it's a convenient model to use for testing.
4642
ResourceConfig(Authenticator),
4743
ResourceConfig(ResourceMigrationTestModel),
4844
ResourceConfig(Original1),
4945
ResourceConfig(Proxy2),
5046
]
47+
48+
# Conditionally add RoleDefinition if RBAC is installed
49+
if 'ansible_base.rbac' in settings.INSTALLED_APPS:
50+
from ansible_base.rbac.models import RoleDefinition
51+
from ansible_base.resource_registry.shared_types import RoleDefinitionType
52+
53+
RESOURCE_LIST.append(
54+
ResourceConfig(
55+
RoleDefinition,
56+
shared_resource=SharedResource(serializer=RoleDefinitionType, is_provider=False),
57+
)
58+
)

0 commit comments

Comments
 (0)