mirror of
https://github.com/ansible/awx.git
synced 2026-02-17 19:20:05 -03:30
AAP-48392 Handle DAB RBAC either before or after new type model (for merge) (#16045)
* Handle DAB RBAC either before or after new type model * Translate CT to DAB CT * Fixes for content type switch * Use more compatible coding pattern * Deeper purge of content_type_id * revert, turns out that did not work * More content type replacements * Revert changes to serializer * Revert another content_type change * Fix for rearrangement of post_migration methods * Remove thing I am not going to do * Revert branch pin that was temporary
This commit is contained in:
@@ -56,6 +56,7 @@ from wsgiref.util import FileWrapper
|
|||||||
# django-ansible-base
|
# django-ansible-base
|
||||||
from ansible_base.lib.utils.requests import get_remote_hosts
|
from ansible_base.lib.utils.requests import get_remote_hosts
|
||||||
from ansible_base.rbac.models import RoleEvaluation, ObjectRole
|
from ansible_base.rbac.models import RoleEvaluation, ObjectRole
|
||||||
|
from ansible_base.rbac import permission_registry
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.tasks.system import send_notifications, update_inventory_computed_fields
|
from awx.main.tasks.system import send_notifications, update_inventory_computed_fields
|
||||||
@@ -750,8 +751,8 @@ class TeamProjectsList(SubListAPIView):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
team = self.get_parent_object()
|
team = self.get_parent_object()
|
||||||
self.check_parent_access(team)
|
self.check_parent_access(team)
|
||||||
model_ct = ContentType.objects.get_for_model(self.model)
|
model_ct = permission_registry.content_type_model.objects.get_for_model(self.model)
|
||||||
parent_ct = ContentType.objects.get_for_model(self.parent_model)
|
parent_ct = permission_registry.content_type_model.objects.get_for_model(self.parent_model)
|
||||||
|
|
||||||
rd = get_role_definition(team.member_role)
|
rd = get_role_definition(team.member_role)
|
||||||
role = ObjectRole.objects.filter(object_id=team.id, content_type=parent_ct, role_definition=rd).first()
|
role = ObjectRole.objects.filter(object_id=team.id, content_type=parent_ct, role_definition=rd).first()
|
||||||
|
|||||||
@@ -17,7 +17,13 @@ logger = logging.getLogger('awx.main.migrations._dab_rbac')
|
|||||||
|
|
||||||
|
|
||||||
def create_permissions_as_operation(apps, schema_editor):
|
def create_permissions_as_operation(apps, schema_editor):
|
||||||
|
# NOTE: the DAB ContentType changes adjusted how they fire
|
||||||
|
# before they would fire on every app config, like contenttypes
|
||||||
create_dab_permissions(global_apps.get_app_config("main"), apps=apps)
|
create_dab_permissions(global_apps.get_app_config("main"), apps=apps)
|
||||||
|
# This changed to only fire once and do a global creation
|
||||||
|
# so we need to call it for specifically the dab_rbac app
|
||||||
|
# multiple calls will not hurt anything
|
||||||
|
create_dab_permissions(global_apps.get_app_config("dab_rbac"), apps=apps)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -112,7 +118,12 @@ def get_descendents(f, children_map):
|
|||||||
|
|
||||||
def get_permissions_for_role(role_field, children_map, apps):
|
def get_permissions_for_role(role_field, children_map, apps):
|
||||||
Permission = apps.get_model('dab_rbac', 'DABPermission')
|
Permission = apps.get_model('dab_rbac', 'DABPermission')
|
||||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
try:
|
||||||
|
# After migration for remote permissions
|
||||||
|
ContentType = apps.get_model('dab_rbac', 'DABContentType')
|
||||||
|
except LookupError:
|
||||||
|
# If using DAB from before remote permissions are implemented
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
|
|
||||||
perm_list = []
|
perm_list = []
|
||||||
for child_field in get_descendents(role_field, children_map):
|
for child_field in get_descendents(role_field, children_map):
|
||||||
@@ -281,7 +292,13 @@ def setup_managed_role_definitions(apps, schema_editor):
|
|||||||
'special': '{cls.__name__} {action}',
|
'special': '{cls.__name__} {action}',
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
try:
|
||||||
|
# After migration for remote permissions
|
||||||
|
ContentType = apps.get_model('dab_rbac', 'DABContentType')
|
||||||
|
except LookupError:
|
||||||
|
# If using DAB from before remote permissions are implemented
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
|
|
||||||
Permission = apps.get_model('dab_rbac', 'DABPermission')
|
Permission = apps.get_model('dab_rbac', 'DABPermission')
|
||||||
RoleDefinition = apps.get_model('dab_rbac', 'RoleDefinition')
|
RoleDefinition = apps.get_model('dab_rbac', 'RoleDefinition')
|
||||||
Organization = apps.get_model(settings.ANSIBLE_BASE_ORGANIZATION_MODEL)
|
Organization = apps.get_model(settings.ANSIBLE_BASE_ORGANIZATION_MODEL)
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class ResourceMixin(models.Model):
|
|||||||
raise RuntimeError(f'Role filters only valid for users and ancestor role, received {accessor}')
|
raise RuntimeError(f'Role filters only valid for users and ancestor role, received {accessor}')
|
||||||
|
|
||||||
if content_types is None:
|
if content_types is None:
|
||||||
ct_kwarg = dict(content_type_id=ContentType.objects.get_for_model(cls).id)
|
ct_kwarg = dict(content_type=ContentType.objects.get_for_model(cls))
|
||||||
else:
|
else:
|
||||||
ct_kwarg = dict(content_type_id__in=content_types)
|
ct_kwarg = dict(content_type_id__in=content_types)
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from django.conf import settings
|
|||||||
|
|
||||||
# Ansible_base app
|
# Ansible_base app
|
||||||
from ansible_base.rbac.models import RoleDefinition, RoleUserAssignment, RoleTeamAssignment
|
from ansible_base.rbac.models import RoleDefinition, RoleUserAssignment, RoleTeamAssignment
|
||||||
|
from ansible_base.rbac import permission_registry
|
||||||
from ansible_base.lib.utils.models import get_type_for_model
|
from ansible_base.lib.utils.models import get_type_for_model
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
@@ -561,7 +562,7 @@ def get_role_definition(role):
|
|||||||
model_print = type(obj).__name__
|
model_print = type(obj).__name__
|
||||||
perm_list = get_role_codenames(role)
|
perm_list = get_role_codenames(role)
|
||||||
defaults = {
|
defaults = {
|
||||||
'content_type_id': role.content_type_id,
|
'content_type': permission_registry.content_type_model.objects.get_by_natural_key(role.content_type.app_label, role.content_type.model),
|
||||||
'description': f'Has {action_name.title()} permission to {model_print} for backwards API compatibility',
|
'description': f'Has {action_name.title()} permission to {model_print} for backwards API compatibility',
|
||||||
}
|
}
|
||||||
# use Controller-specific role definitions for Team/Organization and member/admin
|
# use Controller-specific role definitions for Team/Organization and member/admin
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ from polymorphic.models import PolymorphicModel
|
|||||||
|
|
||||||
from ansible_base.lib.utils.models import prevent_search, get_type_for_model
|
from ansible_base.lib.utils.models import prevent_search, get_type_for_model
|
||||||
from ansible_base.rbac import permission_registry
|
from ansible_base.rbac import permission_registry
|
||||||
|
from ansible_base.rbac.models import RoleEvaluation
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models.base import CommonModelNameNotUnique, PasswordFieldsModel, NotificationFieldsModel
|
from awx.main.models.base import CommonModelNameNotUnique, PasswordFieldsModel, NotificationFieldsModel
|
||||||
@@ -218,20 +219,21 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, ExecutionEn
|
|||||||
# do not use this if in a subclass
|
# do not use this if in a subclass
|
||||||
if cls != UnifiedJobTemplate:
|
if cls != UnifiedJobTemplate:
|
||||||
return super(UnifiedJobTemplate, cls).accessible_pk_qs(accessor, role_field)
|
return super(UnifiedJobTemplate, cls).accessible_pk_qs(accessor, role_field)
|
||||||
from ansible_base.rbac.models import RoleEvaluation
|
|
||||||
|
|
||||||
action = to_permissions[role_field]
|
action = to_permissions[role_field]
|
||||||
|
|
||||||
# Special condition for super auditor
|
# Special condition for super auditor
|
||||||
role_subclasses = cls._submodels_with_roles()
|
role_subclasses = cls._submodels_with_roles()
|
||||||
role_cts = ContentType.objects.get_for_models(*role_subclasses).values()
|
|
||||||
all_codenames = {f'{action}_{cls._meta.model_name}' for cls in role_subclasses}
|
all_codenames = {f'{action}_{cls._meta.model_name}' for cls in role_subclasses}
|
||||||
if not (all_codenames - accessor.singleton_permissions()):
|
if not (all_codenames - accessor.singleton_permissions()):
|
||||||
|
role_cts = ContentType.objects.get_for_models(*role_subclasses).values()
|
||||||
qs = cls.objects.filter(polymorphic_ctype__in=role_cts)
|
qs = cls.objects.filter(polymorphic_ctype__in=role_cts)
|
||||||
return qs.values_list('id', flat=True)
|
return qs.values_list('id', flat=True)
|
||||||
|
|
||||||
|
dab_role_cts = permission_registry.content_type_model.objects.get_for_models(*role_subclasses).values()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
RoleEvaluation.objects.filter(role__in=accessor.has_roles.all(), codename__in=all_codenames, content_type_id__in=[ct.id for ct in role_cts])
|
RoleEvaluation.objects.filter(role__in=accessor.has_roles.all(), codename__in=all_codenames, content_type_id__in=[ct.id for ct in dab_role_cts])
|
||||||
.values_list('object_id')
|
.values_list('object_id')
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.urls import reverse as django_reverse
|
from django.urls import reverse as django_reverse
|
||||||
|
|
||||||
from awx.api.versioning import reverse
|
from awx.api.versioning import reverse
|
||||||
@@ -8,13 +7,14 @@ from awx.main.models import JobTemplate, Inventory, Organization
|
|||||||
from awx.main.access import JobTemplateAccess, WorkflowJobTemplateAccess
|
from awx.main.access import JobTemplateAccess, WorkflowJobTemplateAccess
|
||||||
|
|
||||||
from ansible_base.rbac.models import RoleDefinition
|
from ansible_base.rbac.models import RoleDefinition
|
||||||
|
from ansible_base.rbac import permission_registry
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_managed_roles_created(setup_managed_roles):
|
def test_managed_roles_created(setup_managed_roles):
|
||||||
"Managed RoleDefinitions are created in post_migration signal, we expect to see them here"
|
"Managed RoleDefinitions are created in post_migration signal, we expect to see them here"
|
||||||
for cls in (JobTemplate, Inventory):
|
for cls in (JobTemplate, Inventory):
|
||||||
ct = ContentType.objects.get_for_model(cls)
|
ct = permission_registry.content_type_model.objects.get_for_model(cls)
|
||||||
rds = list(RoleDefinition.objects.filter(content_type=ct))
|
rds = list(RoleDefinition.objects.filter(content_type=ct))
|
||||||
assert len(rds) > 1
|
assert len(rds) > 1
|
||||||
assert f'{cls.__name__} Admin' in [rd.name for rd in rds]
|
assert f'{cls.__name__} Admin' in [rd.name for rd in rds]
|
||||||
@@ -30,7 +30,7 @@ def test_custom_read_role(admin_user, post, setup_managed_roles):
|
|||||||
)
|
)
|
||||||
rd_id = resp.data['id']
|
rd_id = resp.data['id']
|
||||||
rd = RoleDefinition.objects.get(id=rd_id)
|
rd = RoleDefinition.objects.get(id=rd_id)
|
||||||
assert rd.content_type == ContentType.objects.get_for_model(Inventory)
|
assert rd.content_type == permission_registry.content_type_model.objects.get_for_model(Inventory)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -71,7 +71,7 @@ def test_assign_custom_delete_role(admin_user, rando, inventory, delete, patch):
|
|||||||
rd, _ = RoleDefinition.objects.get_or_create(
|
rd, _ = RoleDefinition.objects.get_or_create(
|
||||||
name='inventory-delete',
|
name='inventory-delete',
|
||||||
permissions=['delete_inventory', 'view_inventory', 'change_inventory'],
|
permissions=['delete_inventory', 'view_inventory', 'change_inventory'],
|
||||||
content_type=ContentType.objects.get_for_model(Inventory),
|
content_type=permission_registry.content_type_model.objects.get_for_model(Inventory),
|
||||||
)
|
)
|
||||||
rd.give_permission(rando, inventory)
|
rd.give_permission(rando, inventory)
|
||||||
inv_id = inventory.pk
|
inv_id = inventory.pk
|
||||||
@@ -85,7 +85,9 @@ def test_assign_custom_delete_role(admin_user, rando, inventory, delete, patch):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_assign_custom_add_role(admin_user, rando, organization, post, setup_managed_roles):
|
def test_assign_custom_add_role(admin_user, rando, organization, post, setup_managed_roles):
|
||||||
rd, _ = RoleDefinition.objects.get_or_create(
|
rd, _ = RoleDefinition.objects.get_or_create(
|
||||||
name='inventory-add', permissions=['add_inventory', 'view_organization'], content_type=ContentType.objects.get_for_model(Organization)
|
name='inventory-add',
|
||||||
|
permissions=['add_inventory', 'view_organization'],
|
||||||
|
content_type=permission_registry.content_type_model.objects.get_for_model(Organization),
|
||||||
)
|
)
|
||||||
rd.give_permission(rando, organization)
|
rd.give_permission(rando, organization)
|
||||||
url = reverse('api:inventory_list')
|
url = reverse('api:inventory_list')
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import json
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
|
|
||||||
from crum import impersonate
|
from crum import impersonate
|
||||||
|
|
||||||
from awx.main.fields import ImplicitRoleField
|
from awx.main.fields import ImplicitRoleField
|
||||||
@@ -60,7 +58,7 @@ def test_role_migration_matches(request, model, setup_managed_roles):
|
|||||||
new_codenames = set(rd.permissions.values_list('codename', flat=True))
|
new_codenames = set(rd.permissions.values_list('codename', flat=True))
|
||||||
# all the old roles should map to a non-Compat role definition
|
# all the old roles should map to a non-Compat role definition
|
||||||
if 'Compat' not in rd.name:
|
if 'Compat' not in rd.name:
|
||||||
model_rds = RoleDefinition.objects.filter(content_type=ContentType.objects.get_for_model(obj))
|
model_rds = RoleDefinition.objects.filter(content_type=permission_registry.content_type_model.objects.get_for_model(obj))
|
||||||
rd_data = {}
|
rd_data = {}
|
||||||
for rd in model_rds:
|
for rd in model_rds:
|
||||||
rd_data[rd.name] = list(rd.permissions.values_list('codename', flat=True))
|
rd_data[rd.name] = list(rd.permissions.values_list('codename', flat=True))
|
||||||
@@ -76,7 +74,7 @@ def test_role_migration_matches(request, model, setup_managed_roles):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_role_naming(setup_managed_roles):
|
def test_role_naming(setup_managed_roles):
|
||||||
qs = RoleDefinition.objects.filter(content_type=ContentType.objects.get(model='jobtemplate'), name__endswith='dmin')
|
qs = RoleDefinition.objects.filter(content_type=permission_registry.content_type_model.objects.get(model='jobtemplate'), name__endswith='dmin')
|
||||||
assert qs.count() == 1 # sanity
|
assert qs.count() == 1 # sanity
|
||||||
rd = qs.first()
|
rd = qs.first()
|
||||||
assert rd.name == 'JobTemplate Admin'
|
assert rd.name == 'JobTemplate Admin'
|
||||||
@@ -86,7 +84,7 @@ def test_role_naming(setup_managed_roles):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_action_role_naming(setup_managed_roles):
|
def test_action_role_naming(setup_managed_roles):
|
||||||
qs = RoleDefinition.objects.filter(content_type=ContentType.objects.get(model='jobtemplate'), name__endswith='ecute')
|
qs = RoleDefinition.objects.filter(content_type=permission_registry.content_type_model.objects.get(model='jobtemplate'), name__endswith='ecute')
|
||||||
assert qs.count() == 1 # sanity
|
assert qs.count() == 1 # sanity
|
||||||
rd = qs.first()
|
rd = qs.first()
|
||||||
assert rd.name == 'JobTemplate Execute'
|
assert rd.name == 'JobTemplate Execute'
|
||||||
@@ -98,7 +96,7 @@ def test_action_role_naming(setup_managed_roles):
|
|||||||
def test_compat_role_naming(setup_managed_roles, job_template, rando, alice):
|
def test_compat_role_naming(setup_managed_roles, job_template, rando, alice):
|
||||||
with impersonate(alice):
|
with impersonate(alice):
|
||||||
job_template.read_role.members.add(rando)
|
job_template.read_role.members.add(rando)
|
||||||
qs = RoleDefinition.objects.filter(content_type=ContentType.objects.get(model='jobtemplate'), name__endswith='ompat')
|
qs = RoleDefinition.objects.filter(content_type=permission_registry.content_type_model.objects.get(model='jobtemplate'), name__endswith='ompat')
|
||||||
assert qs.count() == 1 # sanity
|
assert qs.count() == 1 # sanity
|
||||||
rd = qs.first()
|
rd = qs.first()
|
||||||
assert rd.name == 'JobTemplate Read Compat'
|
assert rd.name == 'JobTemplate Read Compat'
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
|
|
||||||
from awx.main.access import ExecutionEnvironmentAccess
|
from awx.main.access import ExecutionEnvironmentAccess
|
||||||
from awx.main.models import ExecutionEnvironment, Organization, Team
|
from awx.main.models import ExecutionEnvironment, Organization, Team
|
||||||
from awx.main.models.rbac import get_role_codenames
|
from awx.main.models.rbac import get_role_codenames
|
||||||
@@ -10,6 +8,7 @@ from awx.api.versioning import reverse
|
|||||||
from django.urls import reverse as django_reverse
|
from django.urls import reverse as django_reverse
|
||||||
|
|
||||||
from ansible_base.rbac.models import RoleDefinition
|
from ansible_base.rbac.models import RoleDefinition
|
||||||
|
from ansible_base.rbac import permission_registry
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -17,7 +16,7 @@ def ee_rd():
|
|||||||
return RoleDefinition.objects.create_from_permissions(
|
return RoleDefinition.objects.create_from_permissions(
|
||||||
name='EE object admin',
|
name='EE object admin',
|
||||||
permissions=['change_executionenvironment', 'delete_executionenvironment'],
|
permissions=['change_executionenvironment', 'delete_executionenvironment'],
|
||||||
content_type=ContentType.objects.get_for_model(ExecutionEnvironment),
|
content_type=permission_registry.content_type_model.objects.get_for_model(ExecutionEnvironment),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ def org_ee_rd():
|
|||||||
return RoleDefinition.objects.create_from_permissions(
|
return RoleDefinition.objects.create_from_permissions(
|
||||||
name='EE org admin',
|
name='EE org admin',
|
||||||
permissions=['add_executionenvironment', 'change_executionenvironment', 'delete_executionenvironment', 'view_organization'],
|
permissions=['add_executionenvironment', 'change_executionenvironment', 'delete_executionenvironment', 'view_organization'],
|
||||||
content_type=ContentType.objects.get_for_model(Organization),
|
content_type=permission_registry.content_type_model.objects.get_for_model(Organization),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import pytest
|
|||||||
from ansible.module_utils.six import raise_from
|
from ansible.module_utils.six import raise_from
|
||||||
|
|
||||||
from ansible_base.rbac.models import RoleDefinition, DABPermission
|
from ansible_base.rbac.models import RoleDefinition, DABPermission
|
||||||
|
from ansible_base.rbac import permission_registry
|
||||||
|
|
||||||
from awx.main.tests.conftest import load_all_credentials # noqa: F401; pylint: disable=unused-import
|
from awx.main.tests.conftest import load_all_credentials # noqa: F401; pylint: disable=unused-import
|
||||||
from awx.main.tests.functional.conftest import _request
|
from awx.main.tests.functional.conftest import _request
|
||||||
from awx.main.tests.functional.conftest import credentialtype_scm, credentialtype_ssh # noqa: F401; pylint: disable=unused-import
|
from awx.main.tests.functional.conftest import credentialtype_scm, credentialtype_ssh # noqa: F401; pylint: disable=unused-import
|
||||||
@@ -37,7 +39,6 @@ from awx.main.models import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
|
|
||||||
|
|
||||||
HAS_TOWER_CLI = False
|
HAS_TOWER_CLI = False
|
||||||
@@ -342,7 +343,7 @@ def notification_template(organization):
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_template_role_definition():
|
def job_template_role_definition():
|
||||||
rd = RoleDefinition.objects.create(name='test_view_jt', content_type=ContentType.objects.get_for_model(JobTemplate))
|
rd = RoleDefinition.objects.create(name='test_view_jt', content_type=permission_registry.content_type_model.objects.get_for_model(JobTemplate))
|
||||||
permission_codenames = ['view_jobtemplate', 'execute_jobtemplate']
|
permission_codenames = ['view_jobtemplate', 'execute_jobtemplate']
|
||||||
permissions = DABPermission.objects.filter(codename__in=permission_codenames)
|
permissions = DABPermission.objects.filter(codename__in=permission_codenames)
|
||||||
rd.permissions.add(*permissions)
|
rd.permissions.add(*permissions)
|
||||||
|
|||||||
Reference in New Issue
Block a user