|
| 1 | +from typing import Union |
| 2 | + |
| 3 | +from django.apps import apps |
| 4 | +from django.conf import settings |
| 5 | +from django.db.models import F, Model, OuterRef |
| 6 | + |
| 7 | +from ansible_base.lib.utils.auth import get_organization_model, get_team_model |
| 8 | + |
| 9 | +from .models.content_type import DABContentType |
| 10 | +from .models.role import RoleDefinition |
| 11 | + |
| 12 | + |
| 13 | +def get_user_object_roles(user: Model) -> list[tuple[str, str, int]]: |
| 14 | + """Returns a list of tuples giving (role name, ansible_id, content_type_id) |
| 15 | +
|
| 16 | + This data is the role name joined with data about the resource""" |
| 17 | + resource_cls = apps.get_model('dab_resource_registry', 'Resource') |
| 18 | + resource_qs = resource_cls.objects.filter(object_id=OuterRef('object_id'), content_type=OuterRef('content_type')).values('ansible_id') |
| 19 | + assignment_qs = ( |
| 20 | + user.role_assignments.filter(content_type__isnull=False) |
| 21 | + .annotate(aid=resource_qs, rd_name=F('role_definition__name')) |
| 22 | + .filter(rd_name__in=settings.ANSIBLE_BASE_JWT_MANAGED_ROLES) |
| 23 | + ) |
| 24 | + return [(ra.rd_name, str(ra.aid), ra.content_type_id) for ra in assignment_qs] |
| 25 | + |
| 26 | + |
| 27 | +def get_user_claims(user: Model) -> dict[str, Union[list[str], dict[str, Union[str, list[dict[str, str]]]]]]: |
| 28 | + claims = {'objects': {}, 'object_roles': {}, 'global_roles': []} |
| 29 | + |
| 30 | + org_cls = get_organization_model() |
| 31 | + team_cls = get_team_model() |
| 32 | + |
| 33 | + cached_objects_index = {} # Entries like { <content_model>: {<ansible_id>: <array index integer> } } used to resolve ansible_ids to array indexes |
| 34 | + cached_content_types = {} # Entries like { <content id integer>: <content_model> } used to resolve a content id to a model type |
| 35 | + for content_type in DABContentType.objects.all().values('id', 'model'): |
| 36 | + content_type_id = content_type['id'] |
| 37 | + model = content_type['model'] |
| 38 | + cached_content_types[content_type_id] = model |
| 39 | + cached_objects_index[model] = {} |
| 40 | + |
| 41 | + required_data = {} # Entries like { <content_model>: { <ansible_id>|<id>: <required_data> } } Note: required_data could have references to ansible_ids |
| 42 | + |
| 43 | + # Populate the required_data for orgs |
| 44 | + org_content_type_model = DABContentType.objects.get_for_model(org_cls).model |
| 45 | + required_data[org_content_type_model] = {} |
| 46 | + for org in org_cls.objects.all().values('id', 'name', 'resource__ansible_id'): |
| 47 | + org_id = org['id'] |
| 48 | + name = org['name'] |
| 49 | + ansible_id = str(org['resource__ansible_id']) |
| 50 | + required_data[org_content_type_model][org_id] = {'ansible_id': ansible_id, 'name': name} |
| 51 | + required_data[org_content_type_model][ansible_id] = required_data[org_content_type_model][org_id] |
| 52 | + claims['objects'][org_content_type_model] = [] |
| 53 | + |
| 54 | + # Populate the required_Data for teams |
| 55 | + team_content_type_model = DABContentType.objects.get_for_model(team_cls).model |
| 56 | + required_data[team_content_type_model] = {} |
| 57 | + for team in team_cls.objects.all().values('id', 'name', 'resource__ansible_id', 'organization__resource__ansible_id'): |
| 58 | + team_id = team['id'] |
| 59 | + team_name = team['name'] |
| 60 | + ansible_id = str(team['resource__ansible_id']) |
| 61 | + related_org_ansible_id = str(team['organization__resource__ansible_id']) |
| 62 | + required_data[team_content_type_model][team_id] = {'ansible_id': ansible_id, 'name': team_name, 'org': related_org_ansible_id} |
| 63 | + required_data[team_content_type_model][ansible_id] = required_data[team_content_type_model][team_id] |
| 64 | + claims['objects'][team_content_type_model] = [] |
| 65 | + |
| 66 | + # We will now scan Org and Team roles and get users memberships to them. |
| 67 | + user_object_roles = get_user_object_roles(user) |
| 68 | + for role_name, ansible_id, content_type_id in user_object_roles: |
| 69 | + # Get the model for this content_type |
| 70 | + content_model_type = cached_content_types[content_type_id] |
| 71 | + |
| 72 | + # If the ansible_id is not in the cached_objects_index |
| 73 | + if ansible_id not in cached_objects_index[content_model_type]: |
| 74 | + # Cache the index the current len will be the next index when we append) |
| 75 | + cached_objects_index[content_model_type][ansible_id] = len(claims['objects'][content_model_type]) |
| 76 | + # Add the object to the payloads objects |
| 77 | + claims['objects'][content_model_type].append(required_data[content_model_type][ansible_id]) |
| 78 | + |
| 79 | + # Get the index value we want from the cache |
| 80 | + object_index = cached_objects_index[content_model_type][ansible_id] |
| 81 | + |
| 82 | + # If the role is not in the payload, insert it |
| 83 | + if role_name not in claims['object_roles']: |
| 84 | + claims['object_roles'][role_name] = {'content_type': content_model_type, 'objects': []} |
| 85 | + |
| 86 | + # The object is the object cache |
| 87 | + claims['object_roles'][role_name]['objects'].append(object_index) |
| 88 | + |
| 89 | + # Now we are going to trim up any team references to organizations with the index instead of the ansible ID |
| 90 | + # i.e. we currently have entries like: payload['objects']['team'][0]['org'] = <ansible_id> |
| 91 | + # and we are going to convert that to: payload['objects']['team'][0]['org'] = 0 |
| 92 | + |
| 93 | + for team in claims['objects'][team_content_type_model]: |
| 94 | + org_ansible_id = team['org'] |
| 95 | + if org_ansible_id in cached_objects_index[org_content_type_model]: |
| 96 | + team['org'] = cached_objects_index[org_content_type_model][org_ansible_id] |
| 97 | + else: |
| 98 | + # The user is in a team related to an org but we didn't pull that org in yet |
| 99 | + # Cache the index of the org, which is the current len |
| 100 | + cached_objects_index[org_content_type_model][org_ansible_id] = len(claims['objects'][org_content_type_model]) |
| 101 | + org_data = required_data[org_content_type_model][org_ansible_id] |
| 102 | + team['org'] = len(claims['objects'][org_content_type_model]) |
| 103 | + claims['objects'][org_content_type_model].append(org_data) |
| 104 | + |
| 105 | + # See if the user has any global roles |
| 106 | + for rd in RoleDefinition.objects.filter(content_type=None, user_assignments__user=user.pk, name__in=settings.ANSIBLE_BASE_JWT_MANAGED_ROLES): |
| 107 | + claims['global_roles'].append(rd.name) |
| 108 | + |
| 109 | + return claims |
0 commit comments