Skip to content

Commit 113ac6f

Browse files
committed
Move methods, just write things normally
1 parent b9d4aed commit 113ac6f

File tree

4 files changed

+273
-131
lines changed

4 files changed

+273
-131
lines changed

ansible_base/jwt_consumer/common/auth.py

Lines changed: 3 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import logging
22
import uuid
33
from datetime import datetime
4-
from typing import Optional, Tuple
4+
from typing import Optional
55

66
import jwt
7-
from django.apps import apps
8-
from django.conf import settings
97
from django.contrib.auth import get_user_model
108
from django.core.exceptions import ObjectDoesNotExist
11-
from django.db.models import Model
129
from django.db.utils import IntegrityError
1310
from rest_framework.authentication import BaseAuthentication
1411
from rest_framework.exceptions import AuthenticationFailed
@@ -19,7 +16,7 @@
1916
from ansible_base.lib.logging.runtime import log_excess_runtime
2017
from ansible_base.lib.utils.auth import get_user_by_ansible_id
2118
from ansible_base.lib.utils.translations import translatableConditionally as _
22-
from ansible_base.rbac.claims import get_claims_hash, get_user_claims, get_user_claims_hashable_form
19+
from ansible_base.rbac.claims import get_claims_hash, get_user_claims, get_user_claims_hashable_form, save_user_claims
2320
from ansible_base.resource_registry.models import Resource, ResourceType
2421
from ansible_base.resource_registry.rest_client import get_resource_server_client
2522
from ansible_base.resource_registry.signals.handlers import no_reverse_sync
@@ -241,25 +238,6 @@ def decode_jwt_token(self, unencrypted_token, decryption_key, additional_options
241238
algorithms=["RS256"],
242239
)
243240

244-
@staticmethod
245-
def get_role_definition(name: str) -> Optional[Model]:
246-
"""Simply get the RoleDefinition from the database if it exists and handler corner cases
247-
248-
If this is the name of a managed role for which we have a corresponding definition in code,
249-
and that role can not be found in the database, it may be created here
250-
"""
251-
from ansible_base.rbac.models import RoleDefinition
252-
253-
try:
254-
return RoleDefinition.objects.get(name=name)
255-
except RoleDefinition.DoesNotExist:
256-
257-
constructor = permission_registry().get_managed_role_constructor_by_name(name)
258-
if constructor:
259-
rd, _ = constructor.get_or_create(apps)
260-
return rd
261-
return None
262-
263241
def process_rbac_permissions(self):
264242
"""
265243
Process RBAC permissions using claims hash logic
@@ -314,7 +292,7 @@ def process_rbac_permissions(self):
314292
global_roles = gateway_claims.get('global_roles', [])
315293

316294
# Process the RBAC permissions with the gateway claims
317-
self._apply_rbac_permissions(objects, object_roles, global_roles)
295+
save_user_claims(self.user, objects, object_roles, global_roles)
318296

319297
# Update cache with the new hash
320298
self.cache.cache_claims_hash(user_ansible_id, jwt_claims_hash)
@@ -345,110 +323,6 @@ def _fetch_jwt_claims_from_gateway(self, user_ansible_id: str) -> Optional[dict]
345323
logger.error(f"Error fetching claims from gateway: {e}")
346324
return None
347325

348-
@staticmethod
349-
def _apply_rbac_permissions(user, objects: dict, object_roles: dict, global_roles: list) -> None:
350-
"""
351-
Apply RBAC permissions from claims data
352-
"""
353-
from ansible_base.rbac.models import RoleUserAssignment
354-
355-
role_diff = RoleUserAssignment.objects.filter(user=user, role_definition__name__in=settings.ANSIBLE_BASE_JWT_MANAGED_ROLES)
356-
357-
for system_role_name in global_roles:
358-
logger.debug(f"Processing system role {system_role_name} for {user.username}")
359-
rd = JWTCommonAuth.get_role_definition(system_role_name)
360-
if rd:
361-
if rd.name in settings.ANSIBLE_BASE_JWT_MANAGED_ROLES:
362-
assignment = rd.give_global_permission(user)
363-
role_diff = role_diff.exclude(pk=assignment.pk)
364-
logger.info(f"Granted user {user.username} global role {system_role_name}")
365-
else:
366-
logger.error(f"Unable to grant {user.username} system level role {system_role_name} because it is not a JWT managed role")
367-
else:
368-
logger.error(f"Unable to grant {user.username} system level role {system_role_name} because it does not exist")
369-
continue
370-
371-
for object_role_name in object_roles.keys():
372-
rd = JWTCommonAuth.get_role_definition(object_role_name)
373-
if rd is None:
374-
logger.error(f"Unable to grant {user.username} object role {object_role_name} because it does not exist")
375-
continue
376-
elif rd.name not in settings.ANSIBLE_BASE_JWT_MANAGED_ROLES:
377-
logger.error(f"Unable to grant {user.username} object role {object_role_name} because it is not a JWT managed role")
378-
continue
379-
380-
object_type = object_roles[object_role_name]['content_type']
381-
object_indexes = object_roles[object_role_name]['objects']
382-
383-
for index in object_indexes:
384-
object_data = objects[object_type][index]
385-
try:
386-
resource, obj = JWTCommonAuth.get_or_create_resource(objects, object_type, object_data)
387-
except IntegrityError as e:
388-
logger.warning(
389-
f"Got integrity error ({e}) on {object_data}. Skipping {object_type} assignment. "
390-
"Please make sure the sync task is running to prevent this warning in the future."
391-
)
392-
continue
393-
394-
if resource is not None:
395-
assignment = rd.give_permission(user, obj)
396-
role_diff = role_diff.exclude(pk=assignment.pk)
397-
logger.info(f"Granted user {user.username} role {object_role_name} to object {obj.name} with ansible_id {object_data['ansible_id']}")
398-
399-
# Remove all permissions not authorized by the JWT
400-
for role_assignment in role_diff:
401-
rd = role_assignment.role_definition
402-
content_object = role_assignment.content_object
403-
if content_object:
404-
rd.remove_permission(user, content_object)
405-
else:
406-
rd.remove_global_permission(user)
407-
408-
@staticmethod
409-
def get_or_create_resource(objects: dict, content_type: str, data: dict) -> Tuple[Optional[Resource], Optional[Model]]:
410-
"""
411-
Gets or creates a resource from a content type and its default data
412-
413-
This can only build or get organizations or teams
414-
"""
415-
object_ansible_id = data['ansible_id']
416-
try:
417-
resource = Resource.objects.get(ansible_id=object_ansible_id)
418-
logger.debug(f"Resource {object_ansible_id} already exists")
419-
return resource, resource.content_object
420-
except Resource.DoesNotExist:
421-
pass
422-
423-
# The resource was missing so we need to create its stub
424-
if content_type == 'team':
425-
# For a team we first have to make sure the org is there
426-
org_id = data['org']
427-
organization_data = objects["organization"][org_id]
428-
429-
# Now that we have the org we can build a team
430-
org_resource, _ = JWTCommonAuth.get_or_create_resource("organization", organization_data)
431-
432-
resource = Resource.create_resource(
433-
ResourceType.objects.get(name="shared.team"),
434-
{"name": data["name"], "organization": org_resource.ansible_id},
435-
ansible_id=data["ansible_id"],
436-
)
437-
438-
return resource, resource.content_object
439-
440-
elif content_type == 'organization':
441-
resource = Resource.create_resource(
442-
ResourceType.objects.get(name="shared.organization"),
443-
{"name": data["name"]},
444-
ansible_id=data["ansible_id"],
445-
)
446-
447-
return resource, resource.content_object
448-
else:
449-
logger.error(f"build_resource_stub does not know how to build an object of type {type}")
450-
return None, None
451-
452326

453327
class JWTAuthentication(BaseAuthentication):
454328
map_fields = default_mapped_user_fields

ansible_base/rbac/claims.py

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import hashlib
22
import json
3+
import logging
34
from collections import defaultdict
4-
from typing import Union
5+
from typing import Optional, Tuple, Union
56

67
from django.apps import apps
78
from django.conf import settings
89
from django.db.models import F, Model, OuterRef, QuerySet
10+
from django.db.utils import IntegrityError
911

1012
from ansible_base.lib.utils.auth import get_team_model
1113

1214
from .models.content_type import DABContentType
1315
from .models.role import RoleDefinition
16+
from .permission_registry import permission_registry
17+
18+
logger = logging.getLogger(__name__)
19+
20+
21+
# ---- for claims serialization ----
1422

1523

1624
def get_user_object_roles(user: Model) -> QuerySet:
@@ -233,6 +241,137 @@ def get_user_claims(user: Model) -> dict[str, Union[list[str], dict[str, Union[s
233241
return {'objects': object_arrays, 'object_roles': object_roles, 'global_roles': global_roles}
234242

235243

244+
# ---- for claims saving ----
245+
246+
247+
def get_role_definition(name: str) -> Optional[Model]:
248+
"""Simply get the RoleDefinition from the database if it exists and handler corner cases
249+
250+
If this is the name of a managed role for which we have a corresponding definition in code,
251+
and that role can not be found in the database, it may be created here
252+
"""
253+
from ansible_base.rbac.models import RoleDefinition
254+
255+
try:
256+
return RoleDefinition.objects.get(name=name)
257+
except RoleDefinition.DoesNotExist:
258+
259+
constructor = permission_registry().get_managed_role_constructor_by_name(name)
260+
if constructor:
261+
rd, _ = constructor.get_or_create(apps)
262+
return rd
263+
return None
264+
265+
266+
def get_or_create_resource(objects: dict, content_type: str, data: dict) -> Tuple[Optional[Model], Optional[Model]]:
267+
"""
268+
Gets or creates a resource from a content type and its default data
269+
270+
This can only build or get organizations or teams
271+
"""
272+
object_ansible_id = data['ansible_id']
273+
resource_cls = apps.get_model('dab_resource_registry', 'Resource')
274+
resource_type_cls = apps.get_model('dab_resource_registry', 'ResourceType')
275+
try:
276+
resource = resource_cls.objects.get(ansible_id=object_ansible_id)
277+
logger.debug(f"Resource {object_ansible_id} already exists")
278+
return resource, resource.content_object
279+
except resource_cls.DoesNotExist:
280+
pass
281+
282+
# The resource was missing so we need to create its stub
283+
if content_type == 'team':
284+
# For a team we first have to make sure the org is there
285+
org_id = data['org']
286+
organization_data = objects["organization"][org_id]
287+
288+
# Now that we have the org we can build a team
289+
org_resource, _ = get_or_create_resource("organization", organization_data)
290+
291+
resource = resource_cls.create_resource(
292+
resource_type_cls.objects.get(name="shared.team"),
293+
{"name": data["name"], "organization": org_resource.ansible_id},
294+
ansible_id=data["ansible_id"],
295+
)
296+
297+
return resource, resource.content_object
298+
299+
elif content_type == 'organization':
300+
resource = resource_cls.create_resource(
301+
resource_type_cls.objects.get(name="shared.organization"),
302+
{"name": data["name"]},
303+
ansible_id=data["ansible_id"],
304+
)
305+
306+
return resource, resource.content_object
307+
else:
308+
logger.error(f"build_resource_stub does not know how to build an object of type {type}")
309+
return None, None
310+
311+
312+
def save_user_claims(user: Model, objects: dict, object_roles: dict, global_roles: list) -> None:
313+
"""
314+
Apply RBAC permissions from claims data
315+
"""
316+
from ansible_base.rbac.models import RoleUserAssignment
317+
318+
role_diff = RoleUserAssignment.objects.filter(user=user, role_definition__name__in=settings.ANSIBLE_BASE_JWT_MANAGED_ROLES)
319+
320+
for system_role_name in global_roles:
321+
logger.debug(f"Processing system role {system_role_name} for {user.username}")
322+
rd = get_role_definition(system_role_name)
323+
if rd:
324+
if rd.name in settings.ANSIBLE_BASE_JWT_MANAGED_ROLES:
325+
assignment = rd.give_global_permission(user)
326+
role_diff = role_diff.exclude(pk=assignment.pk)
327+
logger.info(f"Granted user {user.username} global role {system_role_name}")
328+
else:
329+
logger.error(f"Unable to grant {user.username} system level role {system_role_name} because it is not a JWT managed role")
330+
else:
331+
logger.error(f"Unable to grant {user.username} system level role {system_role_name} because it does not exist")
332+
continue
333+
334+
for object_role_name in object_roles.keys():
335+
rd = get_role_definition(object_role_name)
336+
if rd is None:
337+
logger.error(f"Unable to grant {user.username} object role {object_role_name} because it does not exist")
338+
continue
339+
elif rd.name not in settings.ANSIBLE_BASE_JWT_MANAGED_ROLES:
340+
logger.error(f"Unable to grant {user.username} object role {object_role_name} because it is not a JWT managed role")
341+
continue
342+
343+
object_type = object_roles[object_role_name]['content_type']
344+
object_indexes = object_roles[object_role_name]['objects']
345+
346+
for index in object_indexes:
347+
object_data = objects[object_type][index]
348+
try:
349+
resource, obj = get_or_create_resource(objects, object_type, object_data)
350+
except IntegrityError as e:
351+
logger.warning(
352+
f"Got integrity error ({e}) on {object_data}. Skipping {object_type} assignment. "
353+
"Please make sure the sync task is running to prevent this warning in the future."
354+
)
355+
continue
356+
357+
if resource is not None:
358+
assignment = rd.give_permission(user, obj)
359+
role_diff = role_diff.exclude(pk=assignment.pk)
360+
logger.info(f"Granted user {user.username} role {object_role_name} to object {obj.name} with ansible_id {object_data['ansible_id']}")
361+
362+
# Remove all permissions not authorized by the JWT
363+
for role_assignment in role_diff:
364+
rd = role_assignment.role_definition
365+
content_object = role_assignment.content_object
366+
if content_object:
367+
rd.remove_permission(user, content_object)
368+
else:
369+
rd.remove_global_permission(user)
370+
371+
372+
# ---- for claims hashing ----
373+
374+
236375
def get_user_claims_hashable_form(claims: dict) -> dict[str, Union[list[str], dict[str, list[str]]]]:
237376
"""Convert user claims to hashable form suitable for generating deterministic hashes.
238377

0 commit comments

Comments
 (0)