Skip to content

Commit 011f49c

Browse files
authored
Add migration-safe utility function for global permission assignment (ansible#870)
Fix AttributeError in migrations where fake models lack custom methods like give_global_permission. This addresses Django's migration system creating __fake__ models that only include database fields and basic ORM methods, not custom instance methods. Solution: - Add give_global_permission_to_actor() utility function in _utils.py - Function replicates RoleDefinition.give_global_permission() logic - Works with migration fake models using apps.get_model() - Enhanced team detection for migration context using model_name check - Enables sharing code between models and migrations (Django best practice) The utility function handles: - User and team assignment creation via get_or_create() - Settings validation for singleton roles - Cache clearing for permission updates - Proper error handling with descriptive messages This fix allows migrations to perform global permission assignments without relying on custom model methods that don't exist on fake models. Signed-off-by: Fabricio Aguiar <[email protected]>
1 parent 93579cf commit 011f49c

File tree

1 file changed

+51
-0
lines changed

1 file changed

+51
-0
lines changed

ansible_base/rbac/migrations/_utils.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,57 @@ def cleanup_orphaned_permissions(apps):
105105
return deleted_count
106106

107107

108+
def give_global_permission_to_actor(role_definition, actor, apps):
109+
"""
110+
Migration-safe utility function to give global permission to an actor (user or team).
111+
112+
This function replicates the logic from RoleDefinition.give_global_permission()
113+
but works with migration fake models that don't have custom methods.
114+
115+
Args:
116+
role_definition: RoleDefinition instance (can be fake model from migration)
117+
actor: User or Team instance to grant permission to
118+
apps: Django apps registry from migration context
119+
"""
120+
from django.conf import settings
121+
from rest_framework.exceptions import ValidationError
122+
123+
if role_definition.content_type is not None:
124+
raise ValidationError('Role definition content type must be null to assign globally')
125+
126+
# Get the assignment classes through apps registry (migration-safe)
127+
RoleUserAssignment = apps.get_model('dab_rbac', 'RoleUserAssignment')
128+
RoleTeamAssignment = apps.get_model('dab_rbac', 'RoleTeamAssignment')
129+
130+
if actor._meta.model_name == 'user':
131+
if not settings.ANSIBLE_BASE_ALLOW_SINGLETON_USER_ROLES:
132+
raise ValidationError('Global roles are not enabled for users')
133+
kwargs = {'object_role': None, 'user': actor, 'role_definition': role_definition}
134+
cls = RoleUserAssignment
135+
elif hasattr(actor, '_meta') and actor._meta.model_name == 'team':
136+
# In migration context, check by model name since isinstance checks might fail
137+
if not settings.ANSIBLE_BASE_ALLOW_SINGLETON_TEAM_ROLES:
138+
raise ValidationError('Global roles are not enabled for teams')
139+
kwargs = {'object_role': None, 'team': actor, 'role_definition': role_definition}
140+
cls = RoleTeamAssignment
141+
else:
142+
raise RuntimeError(f'Cannot give permission for {actor} (type: {type(actor)}, model_name: {getattr(actor._meta, "model_name", "unknown")}), must be a user or team')
143+
144+
assignment, _ = cls.objects.get_or_create(**kwargs)
145+
146+
# Clear any cached permissions
147+
if actor._meta.model_name == 'user':
148+
if hasattr(actor, '_singleton_permissions'):
149+
delattr(actor, '_singleton_permissions')
150+
else:
151+
# when team permissions change, users in memory may be affected by this
152+
# but there is no way to know what users, so we use a global flag
153+
from ansible_base.rbac.evaluations import bound_singleton_permissions
154+
bound_singleton_permissions._team_clear_signal = True
155+
156+
return assignment
157+
158+
108159
def migrate_content_type(apps, schema_editor):
109160
"""
110161
Migrate content type references from Django ContentType to DABContentType.

0 commit comments

Comments
 (0)