@@ -227,7 +227,7 @@ def give_permission(self, actor, content_object):
227227 def remove_permission (self , actor , content_object ):
228228 return self .give_or_remove_permission (actor , content_object , giving = False )
229229
230- def get_or_create_object_role (self , ** kwargs ):
230+ def get_or_create_object_role (self , kwargs , defaults ):
231231 """Transaction-safe method to create ObjectRole
232232
233233 The UI will assign many permissions concurrently.
@@ -238,13 +238,13 @@ def get_or_create_object_role(self, **kwargs):
238238 if transaction .get_connection ().in_atomic_block :
239239 try :
240240 with transaction .atomic ():
241- object_role = ObjectRole .objects .create (** kwargs )
241+ object_role = ObjectRole .objects .create (** kwargs , ** defaults )
242242 return (object_role , True )
243243 except IntegrityError :
244244 object_role = ObjectRole .objects .get (** kwargs )
245245 return (object_role , False )
246246 else :
247- object_role = ObjectRole .objects .create (** kwargs )
247+ object_role = ObjectRole .objects .create (** kwargs , ** defaults )
248248 return (object_role , True )
249249
250250 def give_or_remove_permission (self , actor , content_object , giving = True , sync_action = False ):
@@ -260,13 +260,19 @@ def give_or_remove_permission(self, actor, content_object, giving=True, sync_act
260260 object_id = content_object ._meta .pk .get_db_prep_value (content_object .pk , connection )
261261
262262 kwargs = dict (role_definition = self , content_type = obj_ct , object_id = object_id )
263+ defaults = {}
264+
265+ # For remote objects, add parent reference so we can do evaluations if needed
266+ if isinstance (content_object , RemoteObject ):
267+ if content_object .parent_reference :
268+ defaults ['parent_reference' ] = content_object .parent_reference
263269
264270 created = False
265271 object_role = ObjectRole .objects .filter (** kwargs ).first ()
266272 if object_role is None :
267273 if not giving :
268274 return # nothing to do
269- object_role , created = self .get_or_create_object_role (** kwargs )
275+ object_role , created = self .get_or_create_object_role (kwargs , defaults )
270276
271277 from ansible_base .rbac .triggers import needed_updates_on_assignment , update_after_assignment
272278
@@ -564,28 +570,38 @@ def expected_direct_permissions(self, types_prefetch=None) -> set[tuple[str, int
564570 object_id = role_model ._meta .pk .to_python (self .object_id )
565571 for permission in types_prefetch .permissions_for_object_role (self ):
566572 permission_content_type = types_prefetch .get_content_type (permission .content_type_id )
573+ permission_model = permission_content_type .model_class ()
567574
568575 # direct object permission
569576 if permission .content_type_id == self .content_type_id :
570577 expected_evaluations .add ((permission .codename , self .content_type_id , object_id ))
571578 continue
572579
573- # add child permission on the parent object, usually only for add permission
580+ # Add evaluation for the parent object, usually only for add permission
574581 if is_add_perm (permission .codename ) or settings .ANSIBLE_BASE_CACHE_PARENT_PERMISSIONS :
575582 expected_evaluations .add ((permission .codename , self .content_type_id , object_id ))
576583
577- # add child object permission on child objects
578- # Only propogate add permission to children which are parents of the permission model
584+ # Add evaluations for child objects, where this role gives permission to child objects
579585 filter_path = None
580586 child_model = None
581587 if is_add_perm (permission .codename ):
582- for path , model in permission_registry .get_child_models (role_model ):
583- if '__' in path and model ._meta .model_name == permission_content_type .model :
584- path_to_parent , filter_path = path .split ('__' , 1 )
585- child_model = permission_content_type .model_class ()._meta .get_field (path_to_parent ).related_model
586- eval_ct = DABContentType .objects .get_for_model (child_model ).id
587- if not child_model :
588- continue
588+ # Add evaluations for add permission to children which are parents of the permission model
589+ # this only matters when for multi-layer inheritance (grandchildren)
590+ if issubclass (permission_model , RemoteObject ):
591+ eval_ct = permission_content_type .parent_content_type
592+ if eval_ct == role_content_type :
593+ continue # this permission is for a direct child model
594+ else :
595+ for path , model in permission_registry .get_child_models (role_model ):
596+ if '__' in path and model ._meta .model_name == permission_content_type .model :
597+ path_to_parent , filter_path = path .split ('__' , 1 )
598+ child_model = permission_model ._meta .get_field (path_to_parent ).related_model
599+ eval_ct = DABContentType .objects .get_for_model (child_model ).id
600+ break
601+ if not child_model :
602+ continue # this permission is for a direct child model
603+ elif issubclass (permission_model , RemoteObject ):
604+ eval_ct = permission .content_type_id
589605 else :
590606 for path , model in permission_registry .get_child_models (role_model ):
591607 if model ._meta .model_name == permission_content_type .model :
@@ -603,8 +619,15 @@ def expected_direct_permissions(self, types_prefetch=None) -> set[tuple[str, int
603619 if eval_ct in cached_id_lists :
604620 id_list = cached_id_lists [eval_ct ]
605621 else :
606- # TODO: build id_list from ObjectRole objects it is remote object
607- id_list = child_model .objects .filter (** {filter_path : object_id }).values_list ('pk' , flat = True )
622+ if issubclass (permission_model , RemoteObject ):
623+ # Build id_list from ObjectRole objects if it is remote object
624+ id_list = (
625+ ObjectRole .objects .filter (parent_reference = object_id , content_type = eval_ct )
626+ .values_list (Cast ('object_id' , output_field = permission_model ._meta .pk .django_field ()), flat = True )
627+ .distinct ()
628+ )
629+ else :
630+ id_list = child_model .objects .filter (** {filter_path : object_id }).values_list ('pk' , flat = True )
608631 cached_id_lists [eval_ct ] = list (id_list )
609632
610633 for id in id_list :
0 commit comments