AAP-47283 [2.6] Unified display of RBAC & synchronization (#7001)

* Working branch for testing DAB RBAC changes

* 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

* Fix for rearrangement of post_migration methods

* Directly include RBAC service URLs

* Add a run before remote permission additions

* Sync old rbac to remote rbac (#7025)

Signed-off-by: Seth Foster <fosterbseth@gmail.com>

* Set DAB requirement back to devel

---------

Signed-off-by: Seth Foster <fosterbseth@gmail.com>
Co-authored-by: Seth Foster <fosterseth@users.noreply.github.com>
This commit is contained in:
Alan Rominger
2025-07-29 16:31:29 -04:00
committed by thedoubl3j
parent a3f2401740
commit c5fb0c351d
16 changed files with 115 additions and 36 deletions

View File

@@ -64,6 +64,7 @@ from ansible_base.lib.utils.requests import get_remote_hosts
from ansible_base.rbac.models import RoleEvaluation from ansible_base.rbac.models import RoleEvaluation
from ansible_base.resource_registry.shared_types import OrganizationType, TeamType, UserType from ansible_base.resource_registry.shared_types import OrganizationType, TeamType, UserType
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

View File

@@ -8,7 +8,7 @@ from awx.main.migrations._dab_rbac import migrate_to_new_rbac, create_permission
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('main', '0191_add_django_permissions'), ('main', '0191_add_django_permissions'),
('dab_rbac', '__first__'), ('dab_rbac', '0003_alter_dabpermission_codename_and_more'),
] ]
operations = [ operations = [

View File

@@ -12,6 +12,10 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
('main', '0202_convert_controller_role_definitions'), ('main', '0202_convert_controller_role_definitions'),
] ]
# The DAB RBAC app makes substantial model changes which by change-ordering comes after this
# not including run_before might sometimes work but this enforces a more strict and stable order
# for both applying migrations forwards and backwards
run_before = [("dab_rbac", "0004_remote_permissions_additions")]
operations = [ operations = [
migrations.RunPython(consolidate_indirect_user_roles, migrations.RunPython.noop), migrations.RunPython(consolidate_indirect_user_roles, migrations.RunPython.noop),

View File

@@ -18,7 +18,14 @@ logger = logging.getLogger('awx.main.migrations._dab_rbac')
def create_permissions_as_operation(apps, schema_editor): def create_permissions_as_operation(apps, schema_editor):
logger.info('Running data migration create_permissions_as_operation')
# 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)
""" """
@@ -113,6 +120,11 @@ 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')
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') ContentType = apps.get_model('contenttypes', 'ContentType')
perm_list = [] perm_list = []
@@ -156,11 +168,15 @@ def migrate_to_new_rbac(apps, schema_editor):
This method moves the assigned permissions from the old rbac.py models This method moves the assigned permissions from the old rbac.py models
to the new RoleDefinition and ObjectRole models to the new RoleDefinition and ObjectRole models
""" """
logger.info('Running data migration migrate_to_new_rbac')
Role = apps.get_model('main', 'Role') Role = apps.get_model('main', 'Role')
RoleDefinition = apps.get_model('dab_rbac', 'RoleDefinition') RoleDefinition = apps.get_model('dab_rbac', 'RoleDefinition')
RoleUserAssignment = apps.get_model('dab_rbac', 'RoleUserAssignment') RoleUserAssignment = apps.get_model('dab_rbac', 'RoleUserAssignment')
Permission = apps.get_model('dab_rbac', 'DABPermission') Permission = apps.get_model('dab_rbac', 'DABPermission')
if Permission.objects.count() == 0:
raise RuntimeError('Running migrate_to_new_rbac requires DABPermission objects created first')
# remove add premissions that are not valid for migrations from old versions # remove add premissions that are not valid for migrations from old versions
for perm_str in ('add_organization', 'add_jobtemplate'): for perm_str in ('add_organization', 'add_jobtemplate'):
perm = Permission.objects.filter(codename=perm_str).first() perm = Permission.objects.filter(codename=perm_str).first()
@@ -278,6 +294,7 @@ def setup_managed_role_definitions(apps, schema_editor):
""" """
Idempotent method to create or sync the managed role definitions Idempotent method to create or sync the managed role definitions
""" """
logger.info('Running data migration setup_managed_role_definitions')
to_create = { to_create = {
'object_admin': '{cls.__name__} Admin', 'object_admin': '{cls.__name__} Admin',
'org_admin': 'Organization Admin', 'org_admin': 'Organization Admin',
@@ -285,7 +302,13 @@ def setup_managed_role_definitions(apps, schema_editor):
'special': '{cls.__name__} {action}', 'special': '{cls.__name__} {action}',
} }
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') 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)

View File

@@ -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)

View File

@@ -27,6 +27,8 @@ 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.sync import maybe_reverse_sync_assignment, maybe_reverse_sync_unassignment
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
@@ -562,7 +564,7 @@ def get_role_definition(role):
rd_name = f'{model_print} {action_name.title()} Compat' rd_name = f'{model_print} {action_name.title()} Compat'
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',
} }
@@ -614,12 +616,14 @@ def get_role_from_object_role(object_role):
return getattr(object_role.content_object, role_name) return getattr(object_role.content_object, role_name)
def give_or_remove_permission(role, actor, giving=True): def give_or_remove_permission(role, actor, giving=True, rd=None):
obj = role.content_object obj = role.content_object
if obj is None: if obj is None:
return return
if not rd:
rd = get_role_definition(role) rd = get_role_definition(role)
rd.give_or_remove_permission(actor, obj, giving=giving) assignment = rd.give_or_remove_permission(actor, obj, giving=giving)
return assignment
class SyncEnabled(threading.local): class SyncEnabled(threading.local):
@@ -671,7 +675,14 @@ def sync_members_to_new_rbac(instance, action, model, pk_set, reverse, **kwargs)
role = Role.objects.get(pk=user_or_role_id) role = Role.objects.get(pk=user_or_role_id)
else: else:
user = get_user_model().objects.get(pk=user_or_role_id) user = get_user_model().objects.get(pk=user_or_role_id)
give_or_remove_permission(role, user, giving=is_giving) rd = get_role_definition(role)
assignment = give_or_remove_permission(role, user, giving=is_giving, rd=rd)
# sync to resource server
if is_giving:
maybe_reverse_sync_assignment(assignment)
else:
maybe_reverse_sync_unassignment(rd, user, role.content_object)
def sync_parents_to_new_rbac(instance, action, model, pk_set, reverse, **kwargs): def sync_parents_to_new_rbac(instance, action, model, pk_set, reverse, **kwargs):
@@ -714,7 +725,14 @@ def sync_parents_to_new_rbac(instance, action, model, pk_set, reverse, **kwargs)
from awx.main.models.organization import Team from awx.main.models.organization import Team
team = Team.objects.get(pk=parent_role.object_id) team = Team.objects.get(pk=parent_role.object_id)
give_or_remove_permission(child_role, team, giving=is_giving) rd = get_role_definition(child_role)
assignment = give_or_remove_permission(child_role, team, giving=is_giving, rd=rd)
# sync to resource server
if is_giving:
maybe_reverse_sync_assignment(assignment)
else:
maybe_reverse_sync_unassignment(rd, team, child_role.content_object)
ROLE_DEFINITION_TO_ROLE_FIELD = { ROLE_DEFINITION_TO_ROLE_FIELD = {

View File

@@ -33,6 +33,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
@@ -217,20 +218,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()
) )

View File

@@ -1,3 +1,5 @@
import logging
# Python # Python
import pytest import pytest
from unittest import mock from unittest import mock
@@ -8,7 +10,7 @@ import importlib
# Django # Django
from django.urls import resolve from django.urls import resolve
from django.http import Http404 from django.http import Http404
from django.apps import apps from django.apps import apps as global_apps
from django.core.handlers.exception import response_for_exception from django.core.handlers.exception import response_for_exception
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
@@ -48,6 +50,8 @@ from awx.main.models.oauth import OAuth2Application as Application
from awx.main.models.execution_environments import ExecutionEnvironment from awx.main.models.execution_environments import ExecutionEnvironment
from awx.main.utils import is_testing from awx.main.utils import is_testing
logger = logging.getLogger(__name__)
__SWAGGER_REQUESTS__ = {} __SWAGGER_REQUESTS__ = {}
@@ -55,8 +59,17 @@ __SWAGGER_REQUESTS__ = {}
dab_rr_initial = importlib.import_module('ansible_base.resource_registry.migrations.0001_initial') dab_rr_initial = importlib.import_module('ansible_base.resource_registry.migrations.0001_initial')
def create_service_id(app_config, apps=global_apps, **kwargs):
try:
apps.get_model("dab_resource_registry", "ServiceID")
except LookupError:
logger.info('Looks like reverse migration, not creating resource registry ServiceID')
return
dab_rr_initial.create_service_id(apps, None)
if is_testing(): if is_testing():
post_migrate.connect(lambda **kwargs: dab_rr_initial.create_service_id(apps, None)) post_migrate.connect(create_service_id)
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
@@ -127,7 +140,7 @@ def execution_environment():
@pytest.fixture @pytest.fixture
def setup_managed_roles(): def setup_managed_roles():
"Run the migration script to pre-create managed role definitions" "Run the migration script to pre-create managed role definitions"
setup_managed_role_definitions(apps, None) setup_managed_role_definitions(global_apps, None)
@pytest.fixture @pytest.fixture

View File

@@ -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]
@@ -26,17 +26,20 @@ def test_managed_roles_created(setup_managed_roles):
def test_custom_read_role(admin_user, post, setup_managed_roles): def test_custom_read_role(admin_user, post, setup_managed_roles):
rd_url = django_reverse('roledefinition-list') rd_url = django_reverse('roledefinition-list')
resp = post( resp = post(
url=rd_url, data={"name": "read role made for test", "content_type": "awx.inventory", "permissions": ['view_inventory']}, user=admin_user, expect=201 url=rd_url,
data={"name": "read role made for test", "content_type": "awx.inventory", "permissions": ['awx.view_inventory']},
user=admin_user,
expect=201,
) )
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
def test_custom_system_roles_prohibited(admin_user, post): def test_custom_system_roles_prohibited(admin_user, post):
rd_url = django_reverse('roledefinition-list') rd_url = django_reverse('roledefinition-list')
resp = post(url=rd_url, data={"name": "read role made for test", "content_type": None, "permissions": ['view_inventory']}, user=admin_user, expect=400) resp = post(url=rd_url, data={"name": "read role made for test", "content_type": None, "permissions": ['awx.view_inventory']}, user=admin_user, expect=400)
assert 'System-wide roles are not enabled' in str(resp.data) assert 'System-wide roles are not enabled' in str(resp.data)
@@ -71,7 +74,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 +88,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')

View File

@@ -15,6 +15,14 @@ def test_roles_to_not_create(setup_managed_roles):
raise Exception(f'Found RoleDefinitions that should not exist: {bad_names}') raise Exception(f'Found RoleDefinitions that should not exist: {bad_names}')
@pytest.mark.django_db
def test_org_admin_role(setup_managed_roles):
rd = RoleDefinition.objects.get(name='Organization Admin')
codenames = list(rd.permissions.values_list('codename', flat=True))
assert 'view_inventory' in codenames
assert 'change_inventory' in codenames
@pytest.mark.django_db @pytest.mark.django_db
def test_project_update_role(setup_managed_roles): def test_project_update_role(setup_managed_roles):
"""Role to allow updating a project on the object-level should exist""" """Role to allow updating a project on the object-level should exist"""

View File

@@ -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'

View File

@@ -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),
) )

View File

@@ -1,5 +1,7 @@
from ansible_base.resource_registry.registry import ParentResource, ResourceConfig, ServiceAPIConfig, SharedResource from ansible_base.resource_registry.registry import ParentResource, ResourceConfig, ServiceAPIConfig, SharedResource
from ansible_base.resource_registry.shared_types import OrganizationType, TeamType, UserType from ansible_base.resource_registry.shared_types import OrganizationType, TeamType, UserType
from ansible_base.rbac.models import RoleDefinition
from ansible_base.resource_registry.shared_types import RoleDefinitionType
from awx.main import models from awx.main import models
@@ -19,4 +21,8 @@ RESOURCE_LIST = (
shared_resource=SharedResource(serializer=TeamType, is_provider=False), shared_resource=SharedResource(serializer=TeamType, is_provider=False),
parent_resources=[ParentResource(model=models.Organization, field_name="organization")], parent_resources=[ParentResource(model=models.Organization, field_name="organization")],
), ),
ResourceConfig(
RoleDefinition,
shared_resource=SharedResource(serializer=RoleDefinitionType, is_provider=False),
),
) )

View File

@@ -5,6 +5,7 @@ from django.conf import settings
from django.urls import re_path, include, path from django.urls import re_path, include, path
from ansible_base.lib.dynamic_config.dynamic_urls import api_urls, api_version_urls, root_urls from ansible_base.lib.dynamic_config.dynamic_urls import api_urls, api_version_urls, root_urls
from ansible_base.rbac.service_api.urls import rbac_service_urls
from ansible_base.resource_registry.urls import urlpatterns as resource_api_urls from ansible_base.resource_registry.urls import urlpatterns as resource_api_urls
@@ -25,6 +26,7 @@ def get_urlpatterns(prefix=None):
urlpatterns += [ urlpatterns += [
path(f'api{prefix}v2/', include(resource_api_urls)), path(f'api{prefix}v2/', include(resource_api_urls)),
path(f'api{prefix}v2/', include(rbac_service_urls)),
path(f'api{prefix}v2/', include(api_version_urls)), path(f'api{prefix}v2/', include(api_version_urls)),
path(f'api{prefix}', include(api_urls)), path(f'api{prefix}', include(api_urls)),
path('', include(root_urls)), path('', include(root_urls)),

View File

@@ -18,6 +18,7 @@ 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.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
from awx.main.models import ( from awx.main.models import (
@@ -36,7 +37,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
@@ -341,7 +341,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)

View File

@@ -1,4 +1,4 @@
git+https://github.com/ansible/system-certifi.git@devel#egg=certifi git+https://github.com/ansible/system-certifi.git@devel#egg=certifi
# Remove pbr from requirements.in when moving ansible-runner to requirements.in # Remove pbr from requirements.in when moving ansible-runner to requirements.in
git+https://github.com/ansible/python3-saml.git@devel#egg=python3-saml git+https://github.com/ansible/python3-saml.git@devel#egg=python3-saml
django-ansible-base @ git+ssh://git@github.com/ansible-automation-platform/django-ansible-base@devel#egg=django-ansible-base[rest-filters,jwt_consumer,resource-registry,rbac,feature-flags] django-ansible-base @ git+https://github.com/ansible/django-ansible-base@devel#egg=django-ansible-base[rest-filters,jwt_consumer,resource-registry,rbac,feature-flags]