mirror of
https://github.com/ansible/awx.git
synced 2026-06-27 09:28:01 -02:30
Merge remote-tracking branch 'tower/test_stable-2.6' into merge_26_2
This commit is contained in:
@@ -8,7 +8,7 @@ from awx.main.migrations._dab_rbac import migrate_to_new_rbac, create_permission
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('main', '0191_add_django_permissions'),
|
||||
('dab_rbac', '__first__'),
|
||||
('dab_rbac', '0003_alter_dabpermission_codename_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
||||
@@ -26,6 +26,11 @@ def change_inventory_source_org_unique(apps, schema_editor):
|
||||
logger.info(f'Set database constraint rule for {r} inventory source objects')
|
||||
|
||||
|
||||
def rename_wfjt(apps, schema_editor):
|
||||
cls = apps.get_model('main', 'WorkflowJobTemplate')
|
||||
_rename_duplicates(cls)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
@@ -40,6 +45,7 @@ class Migration(migrations.Migration):
|
||||
name='org_unique',
|
||||
field=models.BooleanField(blank=True, default=True, editable=False, help_text='Used internally to selectively enforce database constraint on name'),
|
||||
),
|
||||
migrations.RunPython(rename_wfjt, migrations.RunPython.noop),
|
||||
migrations.RunPython(change_inventory_source_org_unique, migrations.RunPython.noop),
|
||||
migrations.AddConstraint(
|
||||
model_name='unifiedjobtemplate',
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
from django.db import migrations
|
||||
|
||||
# AWX
|
||||
from awx.main.models import CredentialType
|
||||
from awx.main.utils.common import set_current_apps
|
||||
|
||||
|
||||
def setup_tower_managed_defaults(apps, schema_editor):
|
||||
set_current_apps(apps)
|
||||
CredentialType.setup_tower_managed_defaults(apps)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('main', '0200_template_name_constraint'),
|
||||
]
|
||||
|
||||
operations = []
|
||||
operations = [
|
||||
migrations.RunPython(setup_tower_managed_defaults),
|
||||
]
|
||||
|
||||
102
awx/main/migrations/0202_convert_controller_role_definitions.py
Normal file
102
awx/main/migrations/0202_convert_controller_role_definitions.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# Generated by Django migration for converting Controller role definitions
|
||||
|
||||
from ansible_base.rbac.migrations._utils import give_permissions
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def convert_controller_role_definitions(apps, schema_editor):
|
||||
"""
|
||||
Convert Controller role definitions to regular role definitions:
|
||||
- Controller Organization Admin -> Organization Admin
|
||||
- Controller Organization Member -> Organization Member
|
||||
- Controller Team Admin -> Team Admin
|
||||
- Controller Team Member -> Team Member
|
||||
- Controller System Auditor -> Platform Auditor
|
||||
|
||||
Then delete the old Controller role definitions.
|
||||
"""
|
||||
RoleDefinition = apps.get_model('dab_rbac', 'RoleDefinition')
|
||||
RoleUserAssignment = apps.get_model('dab_rbac', 'RoleUserAssignment')
|
||||
RoleTeamAssignment = apps.get_model('dab_rbac', 'RoleTeamAssignment')
|
||||
Permission = apps.get_model('dab_rbac', 'DABPermission')
|
||||
|
||||
# Mapping of old Controller role names to new role names
|
||||
role_mappings = {
|
||||
'Controller Organization Admin': 'Organization Admin',
|
||||
'Controller Organization Member': 'Organization Member',
|
||||
'Controller Team Admin': 'Team Admin',
|
||||
'Controller Team Member': 'Team Member',
|
||||
}
|
||||
|
||||
for old_name, new_name in role_mappings.items():
|
||||
# Find the old Controller role definition
|
||||
old_role = RoleDefinition.objects.filter(name=old_name).first()
|
||||
if not old_role:
|
||||
continue # Skip if the old role doesn't exist
|
||||
|
||||
# Find the new role definition
|
||||
new_role = RoleDefinition.objects.get(name=new_name)
|
||||
|
||||
# Collect all the assignments that need to be migrated
|
||||
# Group by object (content_type + object_id) to batch the give_permissions calls
|
||||
assignments_by_object = {}
|
||||
|
||||
# Get user assignments
|
||||
user_assignments = RoleUserAssignment.objects.filter(role_definition=old_role).select_related('object_role')
|
||||
for assignment in user_assignments:
|
||||
key = (assignment.object_role.content_type_id, assignment.object_role.object_id)
|
||||
if key not in assignments_by_object:
|
||||
assignments_by_object[key] = {'users': [], 'teams': []}
|
||||
assignments_by_object[key]['users'].append(assignment.user)
|
||||
|
||||
# Get team assignments
|
||||
team_assignments = RoleTeamAssignment.objects.filter(role_definition=old_role).select_related('object_role')
|
||||
for assignment in team_assignments:
|
||||
key = (assignment.object_role.content_type_id, assignment.object_role.object_id)
|
||||
if key not in assignments_by_object:
|
||||
assignments_by_object[key] = {'users': [], 'teams': []}
|
||||
assignments_by_object[key]['teams'].append(assignment.team.id)
|
||||
|
||||
# Use give_permissions to create new assignments with the new role definition
|
||||
for (content_type_id, object_id), data in assignments_by_object.items():
|
||||
if data['users'] or data['teams']:
|
||||
give_permissions(
|
||||
apps,
|
||||
new_role,
|
||||
users=data['users'],
|
||||
teams=data['teams'],
|
||||
object_id=object_id,
|
||||
content_type_id=content_type_id,
|
||||
)
|
||||
|
||||
# Delete the old role definition (this will cascade to delete old assignments and ObjectRoles)
|
||||
old_role.delete()
|
||||
|
||||
# Create or get Platform Auditor
|
||||
auditor_rd, created = RoleDefinition.objects.get_or_create(
|
||||
name='Platform Auditor',
|
||||
defaults={'description': 'Migrated singleton role giving read permission to everything', 'managed': True},
|
||||
)
|
||||
if created:
|
||||
auditor_rd.permissions.add(*list(Permission.objects.filter(codename__startswith='view')))
|
||||
|
||||
old_rd = RoleDefinition.objects.filter(name='Controller System Auditor').first()
|
||||
if old_rd:
|
||||
for assignment in RoleUserAssignment.objects.filter(role_definition=old_rd):
|
||||
RoleUserAssignment.objects.create(
|
||||
user=assignment.user,
|
||||
role_definition=auditor_rd,
|
||||
)
|
||||
|
||||
# Delete the Controller System Auditor role
|
||||
RoleDefinition.objects.filter(name='Controller System Auditor').delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('main', '0201_create_managed_creds'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(convert_controller_role_definitions),
|
||||
]
|
||||
22
awx/main/migrations/0203_remove_team_of_teams.py
Normal file
22
awx/main/migrations/0203_remove_team_of_teams.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import logging
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
from awx.main.migrations._dab_rbac import consolidate_indirect_user_roles
|
||||
|
||||
logger = logging.getLogger('awx.main.migrations')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('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 = [
|
||||
migrations.RunPython(consolidate_indirect_user_roles, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
|
||||
|
||||
logger = logging.getLogger('awx.main.migrations')
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
|
||||
from django.apps import apps as global_apps
|
||||
from django.db.models import ForeignKey
|
||||
@@ -17,6 +18,7 @@ logger = logging.getLogger('awx.main.migrations._dab_rbac')
|
||||
|
||||
|
||||
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)
|
||||
@@ -166,11 +168,15 @@ def migrate_to_new_rbac(apps, schema_editor):
|
||||
This method moves the assigned permissions from the old rbac.py models
|
||||
to the new RoleDefinition and ObjectRole models
|
||||
"""
|
||||
logger.info('Running data migration migrate_to_new_rbac')
|
||||
Role = apps.get_model('main', 'Role')
|
||||
RoleDefinition = apps.get_model('dab_rbac', 'RoleDefinition')
|
||||
RoleUserAssignment = apps.get_model('dab_rbac', 'RoleUserAssignment')
|
||||
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
|
||||
for perm_str in ('add_organization', 'add_jobtemplate'):
|
||||
perm = Permission.objects.filter(codename=perm_str).first()
|
||||
@@ -250,11 +256,14 @@ def migrate_to_new_rbac(apps, schema_editor):
|
||||
|
||||
# Create new replacement system auditor role
|
||||
new_system_auditor, created = RoleDefinition.objects.get_or_create(
|
||||
name='Controller System Auditor',
|
||||
name='Platform Auditor',
|
||||
defaults={'description': 'Migrated singleton role giving read permission to everything', 'managed': True},
|
||||
)
|
||||
new_system_auditor.permissions.add(*list(Permission.objects.filter(codename__startswith='view')))
|
||||
|
||||
if created:
|
||||
logger.info(f'Created RoleDefinition {new_system_auditor.name} pk={new_system_auditor.pk} with {new_system_auditor.permissions.count()} permissions')
|
||||
|
||||
# migrate is_system_auditor flag, because it is no longer handled by a system role
|
||||
old_system_auditor = Role.objects.filter(singleton_name='system_auditor').first()
|
||||
if old_system_auditor:
|
||||
@@ -283,8 +292,9 @@ def get_or_create_managed(name, description, ct, permissions, RoleDefinition):
|
||||
|
||||
def setup_managed_role_definitions(apps, schema_editor):
|
||||
"""
|
||||
Idepotent 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 = {
|
||||
'object_admin': '{cls.__name__} Admin',
|
||||
'org_admin': 'Organization Admin',
|
||||
@@ -448,3 +458,115 @@ def setup_managed_role_definitions(apps, schema_editor):
|
||||
for role_definition in unexpected_role_definitions:
|
||||
logger.info(f'Deleting old managed role definition {role_definition.name}, pk={role_definition.pk}')
|
||||
role_definition.delete()
|
||||
|
||||
|
||||
def get_team_to_team_relationships(apps, team_member_role):
|
||||
"""
|
||||
Find all team-to-team relationships where one team is a member of another.
|
||||
Returns a dict mapping parent_team_id -> [child_team_id, ...]
|
||||
"""
|
||||
team_to_team_relationships = defaultdict(list)
|
||||
|
||||
# Find all team assignments with the Team Member role
|
||||
RoleTeamAssignment = apps.get_model('dab_rbac', 'RoleTeamAssignment')
|
||||
team_assignments = RoleTeamAssignment.objects.filter(role_definition=team_member_role).select_related('team')
|
||||
|
||||
for assignment in team_assignments:
|
||||
parent_team_id = int(assignment.object_id)
|
||||
child_team_id = assignment.team.id
|
||||
team_to_team_relationships[parent_team_id].append(child_team_id)
|
||||
|
||||
return team_to_team_relationships
|
||||
|
||||
|
||||
def get_all_user_members_of_team(apps, team_member_role, team_id, team_to_team_map, visited=None):
|
||||
"""
|
||||
Recursively find all users who are members of a team, including through nested teams.
|
||||
"""
|
||||
if visited is None:
|
||||
visited = set()
|
||||
|
||||
if team_id in visited:
|
||||
return set() # Avoid infinite recursion
|
||||
|
||||
visited.add(team_id)
|
||||
all_users = set()
|
||||
|
||||
# Get direct user assignments to this team
|
||||
RoleUserAssignment = apps.get_model('dab_rbac', 'RoleUserAssignment')
|
||||
user_assignments = RoleUserAssignment.objects.filter(role_definition=team_member_role, object_id=team_id).select_related('user')
|
||||
|
||||
for assignment in user_assignments:
|
||||
all_users.add(assignment.user)
|
||||
|
||||
# Get team-to-team assignments and recursively find their users
|
||||
child_team_ids = team_to_team_map.get(team_id, [])
|
||||
for child_team_id in child_team_ids:
|
||||
nested_users = get_all_user_members_of_team(apps, team_member_role, child_team_id, team_to_team_map, visited.copy())
|
||||
all_users.update(nested_users)
|
||||
|
||||
return all_users
|
||||
|
||||
|
||||
def remove_team_to_team_assignment(apps, team_member_role, parent_team_id, child_team_id):
|
||||
"""
|
||||
Remove team-to-team memberships.
|
||||
"""
|
||||
Team = apps.get_model('main', 'Team')
|
||||
RoleTeamAssignment = apps.get_model('dab_rbac', 'RoleTeamAssignment')
|
||||
|
||||
parent_team = Team.objects.get(id=parent_team_id)
|
||||
child_team = Team.objects.get(id=child_team_id)
|
||||
|
||||
# Remove all team-to-team RoleTeamAssignments
|
||||
RoleTeamAssignment.objects.filter(role_definition=team_member_role, object_id=parent_team_id, team=child_team).delete()
|
||||
|
||||
# Check mirroring Team model for children under member_role
|
||||
parent_team.member_role.children.filter(object_id=child_team_id).delete()
|
||||
|
||||
|
||||
def consolidate_indirect_user_roles(apps, schema_editor):
|
||||
"""
|
||||
A user should have a member role for every team they were indirectly
|
||||
a member of. ex. Team A is a member of Team B. All users in Team A
|
||||
previously were only members of Team A. They should now be members of
|
||||
Team A and Team B.
|
||||
"""
|
||||
|
||||
# get models for membership on teams
|
||||
RoleDefinition = apps.get_model('dab_rbac', 'RoleDefinition')
|
||||
Team = apps.get_model('main', 'Team')
|
||||
|
||||
team_member_role = RoleDefinition.objects.get(name='Team Member')
|
||||
|
||||
team_to_team_map = get_team_to_team_relationships(apps, team_member_role)
|
||||
|
||||
if not team_to_team_map:
|
||||
return # No team-to-team relationships to consolidate
|
||||
|
||||
# Get content type for Team - needed for give_permissions
|
||||
try:
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
team_content_type = ContentType.objects.get_for_model(Team)
|
||||
except ImportError:
|
||||
# Fallback if ContentType is not available
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
team_content_type = ContentType.objects.get_for_model(Team)
|
||||
|
||||
# Get all users who should be direct members of a team
|
||||
for parent_team_id, child_team_ids in team_to_team_map.items():
|
||||
all_users = get_all_user_members_of_team(apps, team_member_role, parent_team_id, team_to_team_map)
|
||||
|
||||
# Create direct RoleUserAssignments for all users
|
||||
if all_users:
|
||||
give_permissions(apps=apps, rd=team_member_role, users=list(all_users), object_id=parent_team_id, content_type_id=team_content_type.id)
|
||||
|
||||
# Mirror assignments to Team model
|
||||
parent_team = Team.objects.get(id=parent_team_id)
|
||||
for user in all_users:
|
||||
parent_team.member_role.members.add(user.id)
|
||||
|
||||
# Remove all team-to-team assignments for parent team
|
||||
for child_team_id in child_team_ids:
|
||||
remove_team_to_team_assignment(apps, team_member_role, parent_team_id, child_team_id)
|
||||
|
||||
Reference in New Issue
Block a user