Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace RBAC internals with lib from django-ansible-base #14735

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a0d1c58
Replace role system with permissions-based DB roles
AlanCoding Nov 13, 2023
a86d5fb
Make old views as deprecated and update for upstream changes
AlanCoding Feb 6, 2024
37f7f35
Update more changed names
AlanCoding Feb 6, 2024
6de61c4
Fix fat-fingered bug with get_or_create
AlanCoding Feb 6, 2024
d1b250e
Provide summary fields to DAB views
AlanCoding Feb 6, 2024
c162f5a
Fix access list failures from type change
AlanCoding Feb 7, 2024
4070511
bump migration
AlanCoding Feb 7, 2024
974926f
Update to new model names
AlanCoding Feb 7, 2024
cf19cba
Rename migration logic to _dab_rbac
AlanCoding Feb 7, 2024
bfd5b36
Update view names in root links
AlanCoding Feb 7, 2024
0b28695
Beyond the translation layer, handle delete permission
AlanCoding Feb 7, 2024
8e53e49
Add test for only giving add permission
AlanCoding Feb 8, 2024
3e6e088
Do not give permissions to non permission models
AlanCoding Feb 8, 2024
300ceda
Connect DAB summary_fields to User model
AlanCoding Feb 9, 2024
301fbbc
Fix v1 used for new endpoints
AlanCoding Feb 9, 2024
992f8b1
Fix case where org admin_role was duplicated
AlanCoding Feb 12, 2024
2d7bf31
Fix migration bugs in role definition defaults
AlanCoding Feb 13, 2024
fe4ec87
Use new DAB RBAC migration utility
AlanCoding Feb 14, 2024
11a2407
Test and fix bug making org access_list error
AlanCoding Feb 14, 2024
f1ab099
Switch AWX to custom Permission model
AlanCoding Feb 14, 2024
5c7c9e5
Use more code from DAB RBAC migration utils
AlanCoding Feb 16, 2024
348027d
Mark object role endpoints as deprecated
AlanCoding Feb 19, 2024
0bb39c4
Fix exception case for notification template role
AlanCoding Feb 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion awx/api/generics.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@
from rest_framework.renderers import StaticHTMLRenderer
from rest_framework.negotiation import DefaultContentNegotiation

# django-ansible-base
from ansible_base.rest_filters.rest_framework.field_lookup_backend import FieldLookupBackend
from ansible_base.lib.utils.models import get_all_field_names
from ansible_base.rbac.models import RoleEvaluation
from ansible_base.rbac.permission_registry import permission_registry

# AWX
from awx.main.models import UnifiedJob, UnifiedJobTemplate, User, Role, Credential, WorkflowJobTemplateNode, WorkflowApprovalTemplate
from awx.main.models.rbac import give_creator_permissions
from awx.main.access import optimize_queryset
from awx.main.utils import camelcase_to_underscore, get_search_fields, getattrd, get_object_or_400, decrypt_field, get_awx_version
from awx.main.utils.licensing import server_product_name
Expand Down Expand Up @@ -472,7 +476,11 @@ def skip_related_name(name):

class ListCreateAPIView(ListAPIView, generics.ListCreateAPIView):
# Base class for a list view that allows creating new objects.
pass
def perform_create(self, serializer):
super().perform_create(serializer)
if serializer.Meta.model in permission_registry.all_registered_models:
if self.request and self.request.user:
give_creator_permissions(self.request.user, serializer.instance)


class ParentMixin(object):
Expand Down Expand Up @@ -799,6 +807,11 @@ def get_queryset(self):
obj = self.get_parent_object()

content_type = ContentType.objects.get_for_model(obj)

if settings.ANSIBLE_BASE_ROLE_SYSTEM_ACTIVATED:
ancestors = set(RoleEvaluation.objects.filter(content_type_id=content_type.id, object_id=obj.id).values_list('role_id', flat=True))
return (User.objects.filter(has_roles__in=ancestors) | User.objects.filter(is_superuser=True)).distinct()

roles = set(Role.objects.filter(content_type=content_type, object_id=obj.id))

ancestors = set()
Expand Down Expand Up @@ -959,6 +972,7 @@ def post(self, request, *args, **kwargs):
)
if hasattr(new_obj, 'admin_role') and request.user not in new_obj.admin_role.members.all():
new_obj.admin_role.members.add(request.user)
give_creator_permissions(request.user, new_obj)
if sub_objs:
permission_check_func = None
if hasattr(type(self), 'deep_copy_permission_check_func'):
Expand Down
87 changes: 73 additions & 14 deletions awx/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# Django
from django.conf import settings
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.models import User
from django.contrib.auth.models import User, Permission
from django.contrib.auth.password_validation import validate_password as django_validate_password
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError
Expand All @@ -43,11 +43,13 @@
# Django-Polymorphic
from polymorphic.models import PolymorphicModel

# django-ansible-base
from ansible_base.lib.utils.models import get_type_for_model
from ansible_base.rbac.models import RoleEvaluation

# AWX
from awx.main.access import get_user_capabilities
from awx.main.constants import ACTIVE_STATES, CENSOR_VALUE
from awx.main.constants import ACTIVE_STATES, CENSOR_VALUE, org_role_to_permission
from awx.main.models import (
ActivityStream,
AdHocCommand,
Expand Down Expand Up @@ -102,7 +104,7 @@
CLOUD_INVENTORY_SOURCES,
)
from awx.main.models.base import VERBOSITY_CHOICES, NEW_JOB_TYPE_CHOICES
from awx.main.models.rbac import role_summary_fields_generator, RoleAncestorEntry
from awx.main.models.rbac import role_summary_fields_generator, give_creator_permissions, get_role_codenames, to_permissions, get_role_from_object_role
from awx.main.fields import ImplicitRoleField
from awx.main.utils import (
get_model_for_type,
Expand Down Expand Up @@ -2762,13 +2764,23 @@ def to_representation(self, user):
team_content_type = ContentType.objects.get_for_model(Team)
content_type = ContentType.objects.get_for_model(obj)

def get_roles_on_resource(parent_role):
"Returns a string list of the roles a parent_role has for current obj."
return list(
RoleAncestorEntry.objects.filter(ancestor=parent_role, content_type_id=content_type.id, object_id=obj.id)
.values_list('role_field', flat=True)
.distinct()
)
reversed_org_map = {}
for k, v in org_role_to_permission.items():
reversed_org_map[v] = k
reversed_role_map = {}
for k, v in to_permissions.items():
reversed_role_map[v] = k

def get_roles_from_perms(perm_list):
"""given a list of permission codenames return a list of role names"""
role_names = set()
for codename in perm_list:
action = codename.split('_', 1)[0]
if action in reversed_role_map:
role_names.add(reversed_role_map[action])
elif codename in reversed_org_map:
role_names.add(codename)
return list(role_names)

def format_role_perm(role):
role_dict = {'id': role.id, 'name': role.name, 'description': role.description}
Expand All @@ -2785,13 +2797,21 @@ def format_role_perm(role):
else:
# Singleton roles should not be managed from this view, as per copy/edit rework spec
role_dict['user_capabilities'] = {'unattach': False}
return {'role': role_dict, 'descendant_roles': get_roles_on_resource(role)}

if role.singleton_name:
descendant_perms = list(Permission.objects.filter(content_type=content_type).values_list('codename', flat=True))
else:
model_name = content_type.model
descendant_perms = [codename for codename in get_role_codenames(role) if codename.endswith(model_name)]

return {'role': role_dict, 'descendant_roles': get_roles_from_perms(descendant_perms)}

def format_team_role_perm(naive_team_role, permissive_role_ids):
ret = []
team = naive_team_role.content_object
team_role = naive_team_role
if naive_team_role.role_field == 'admin_role':
team_role = naive_team_role.content_object.member_role
team_role = team.member_role
for role in team_role.children.filter(id__in=permissive_role_ids).all():
role_dict = {
'id': role.id,
Expand All @@ -2811,10 +2831,48 @@ def format_team_role_perm(naive_team_role, permissive_role_ids):
else:
# Singleton roles should not be managed from this view, as per copy/edit rework spec
role_dict['user_capabilities'] = {'unattach': False}
ret.append({'role': role_dict, 'descendant_roles': get_roles_on_resource(team_role)})

descendant_perms = list(
RoleEvaluation.objects.filter(role__in=team.has_roles.all(), object_id=obj.id, content_type_id=content_type.id)
.values_list('codename', flat=True)
.distinct()
)

ret.append({'role': role_dict, 'descendant_roles': get_roles_from_perms(descendant_perms)})
return ret

gfk_kwargs = dict(content_type_id=content_type.id, object_id=obj.id)
direct_permissive_role_ids = Role.objects.filter(**gfk_kwargs).values_list('id', flat=True)

if settings.ANSIBLE_BASE_ROLE_SYSTEM_ACTIVATED:
ret['summary_fields']['direct_access'] = []
ret['summary_fields']['indirect_access'] = []

new_roles_seen = set()
all_team_roles = set()
all_permissive_role_ids = set()
for evaluation in RoleEvaluation.objects.filter(role__users=user, **gfk_kwargs).prefetch_related('role'):
new_role = evaluation.role
if new_role.id in new_roles_seen:
continue
new_roles_seen.add(new_role.id)
old_role = get_role_from_object_role(new_role)
all_permissive_role_ids.add(old_role.id)

if int(new_role.object_id) == obj.id and new_role.content_type_id == content_type.id:
ret['summary_fields']['direct_access'].append(format_role_perm(old_role))
elif new_role.content_type_id == team_content_type.id:
all_team_roles.add(old_role)
else:
ret['summary_fields']['indirect_access'].append(format_role_perm(old_role))

ret['summary_fields']['direct_access'].extend(
[y for x in (format_team_role_perm(r, direct_permissive_role_ids) for r in all_team_roles) for y in x]
)
ret['summary_fields']['direct_access'].extend([y for x in (format_team_role_perm(r, all_permissive_role_ids) for r in all_team_roles) for y in x])

return ret

direct_permissive_role_ids = Role.objects.filter(content_type=content_type, object_id=obj.id).values_list('id', flat=True)
all_permissive_role_ids = Role.objects.filter(content_type=content_type, object_id=obj.id).values_list('ancestors__id', flat=True)

direct_access_roles = user.roles.filter(id__in=direct_permissive_role_ids).all()
Expand Down Expand Up @@ -3084,6 +3142,7 @@ def create(self, validated_data):

if user:
credential.admin_role.members.add(user)
give_creator_permissions(user, credential)
if team:
if not credential.organization or team.organization.id != credential.organization.id:
raise serializers.ValidationError({"detail": _("Credential organization must be set and match before assigning to a team")})
Expand Down
Loading
Loading