55from  django .db .models  import  Model 
66from  django .utils .translation  import  gettext_lazy  as  _ 
77from  rest_framework  import  permissions 
8- from  rest_framework .exceptions  import  ValidationError 
8+ from  rest_framework .exceptions  import  NotFound ,  ValidationError 
99from  rest_framework .generics  import  GenericAPIView 
1010from  rest_framework .response  import  Response 
11- from  rest_framework .viewsets  import  ModelViewSet 
11+ from  rest_framework .viewsets  import  GenericViewSet ,  ModelViewSet ,  mixins 
1212
13+ from  ansible_base .lib .utils .auth  import  get_team_model , get_user_model 
1314from  ansible_base .lib .utils .views .django_app_api  import  AnsibleBaseDjangoAppApiView 
1415from  ansible_base .lib .utils .views .permissions  import  try_add_oauth2_scope_permission 
1516from  ansible_base .rbac .api .permissions  import  RoleDefinitionPermissions 
1920    RoleMetadataSerializer ,
2021    RoleTeamAssignmentSerializer ,
2122    RoleUserAssignmentSerializer ,
23+     TeamAccessListMixin ,
24+     UserAccessListMixin ,
2225)
2326from  ansible_base .rbac .evaluations  import  has_super_permission 
2427from  ansible_base .rbac .models  import  RoleDefinition 
2528from  ansible_base .rbac .permission_registry  import  permission_registry 
2629from  ansible_base .rbac .policies  import  check_can_remove_assignment 
2730from  ansible_base .rbac .validators  import  check_locally_managed , permissions_allowed_for_role , system_roles_enabled 
28- from  ansible_base .rest_filters .rest_framework . ansible_id_backend  import  TeamAnsibleIdAliasFilterBackend ,  UserAnsibleIdAliasFilterBackend 
31+ from  ansible_base .rest_filters .rest_framework  import  ansible_id_backend 
2932
30- from  ..models . content_type  import  DABContentType 
31- from  ..remote  import  get_resource_prefix 
33+ from  ..models  import  DABContentType ,  DABPermission ,  get_evaluation_model 
34+ from  ..remote  import  RemoteObject ,  get_resource_prefix 
3235
3336
3437def  list_combine_values (data : dict [Type [Model ], list [str ]]) ->  list [str ]:
@@ -174,7 +177,8 @@ class RoleTeamAssignmentViewSet(BaseAssignmentViewSet):
174177    serializer_class  =  RoleTeamAssignmentSerializer 
175178    prefetch_related  =  ('team' ,)
176179    filter_backends  =  BaseAssignmentViewSet .filter_backends  +  [
177-         TeamAnsibleIdAliasFilterBackend ,
180+         ansible_id_backend .TeamAnsibleIdAliasFilterBackend ,
181+         ansible_id_backend .RoleAssignmentFilterBackend ,
178182    ]
179183
180184
@@ -193,5 +197,108 @@ class RoleUserAssignmentViewSet(BaseAssignmentViewSet):
193197    serializer_class  =  RoleUserAssignmentSerializer 
194198    prefetch_related  =  ('user' ,)
195199    filter_backends  =  BaseAssignmentViewSet .filter_backends  +  [
196-         UserAnsibleIdAliasFilterBackend ,
200+         ansible_id_backend .UserAnsibleIdAliasFilterBackend ,
201+         ansible_id_backend .RoleAssignmentFilterBackend ,
197202    ]
203+ 
204+ 
205+ class  UserAccessViewSet (
206+     AnsibleBaseDjangoAppApiView ,
207+     mixins .ListModelMixin ,
208+     GenericViewSet ,
209+ ):
210+     """ 
211+     Use this endpoint to get a list of users who have access to a resource. 
212+     This is a list-only view that provides a list of users, plus extra data. 
213+     """ 
214+ 
215+     serializer_mixin  =  UserAccessListMixin 
216+ 
217+     def  get_actor_model (self ):
218+         return  get_user_model ()
219+ 
220+     def  get_data_from_url (self ):
221+         if  not  hasattr (self , 'related_object' ):
222+             model_name  =  self .kwargs .get ("model_name" )
223+             object_id  =  self .kwargs .get ("pk" )
224+ 
225+             # Prefer treating the URL as requesting for some permission 
226+             self .permission  =  DABPermission .objects .filter (api_slug = model_name ).first ()
227+ 
228+             if  not  self .permission :
229+                 self .content_type  =  DABContentType .objects .filter (api_slug = model_name ).first ()
230+                 if  not  self .content_type :
231+                     raise  NotFound (f'The slug { model_name }   is not a valid permission or type identifier' )
232+             else :
233+                 # Access list will be all permissions for the given object 
234+                 self .content_type  =  self .permission .content_type 
235+ 
236+             model_cls  =  self .content_type .model_class ()
237+             if  not  issubclass (model_cls , RemoteObject ):
238+                 try :
239+                     self .related_object  =  model_cls .objects .get (pk = object_id )
240+                 except  model_cls .DoesNotExist :
241+                     raise  NotFound 
242+             else :
243+                 self .related_object  =  model_cls (content_type = self .content_type , object_id = object_id )
244+ 
245+             if  not  self .request .user .has_obj_perm (self .related_object , 'view' ):
246+                 raise  NotFound 
247+ 
248+         return  (self .permission , self .content_type , self .related_object )
249+ 
250+     def  get_queryset (self ):
251+         permission , ct , obj  =  self .get_data_from_url ()
252+ 
253+         evaluation_cls  =  get_evaluation_model (obj )
254+         reverse_name  =  evaluation_cls ._meta .get_field ('role' ).remote_field .name 
255+         actor_cls  =  self .get_actor_model ()
256+         assignment_cls  =  actor_cls ._meta .get_field ('role_assignments' ).related_model 
257+ 
258+         if  permission :
259+             obj_eval_qs  =  evaluation_cls .objects .filter (codename = permission .codename , object_id = obj .pk , content_type_id = ct .id )
260+         else :
261+             # All relevant evaluations for the object 
262+             obj_eval_qs  =  evaluation_cls .objects .filter (object_id = obj .pk , content_type_id = ct .id )
263+         obj_assignment_qs  =  assignment_cls .objects .filter (** {f'object_role__{ reverse_name }  __in' : obj_eval_qs })
264+ 
265+         if  permission :
266+             global_assignment_qs  =  assignment_cls .objects .filter (content_type = None , role_definition__permissions = permission )
267+         else :
268+             global_assignment_qs  =  assignment_cls .objects .filter (content_type = None , role_definition__permissions__content_type = ct )
269+ 
270+         assignment_qs  =  obj_assignment_qs  |  global_assignment_qs 
271+         actor_qs  =  actor_cls .objects .filter (role_assignments__in = assignment_qs )
272+         if  actor_cls ._meta .model_name  ==  'user' :
273+             actor_qs  |=  actor_qs .filter (is_superuser = True )
274+         return  actor_qs 
275+ 
276+     def  get_serializer_class (self ):
277+         actor_cls  =  self .get_actor_model ()
278+ 
279+         class  DynamicActorSerializer (self .serializer_mixin ):
280+             class  Meta :
281+                 model  =  actor_cls 
282+                 fields  =  self .serializer_mixin .Meta .fields  +  self .serializer_mixin ._expected_fields 
283+ 
284+         return  DynamicActorSerializer 
285+ 
286+     def  get_serializer_context (self ):
287+         ctx  =  super ().get_serializer_context ()
288+         permission , ct , obj  =  self .get_data_from_url ()
289+ 
290+         ctx .update (
291+             {
292+                 "permission" : permission ,
293+                 "related_object" : obj ,
294+                 "content_type" : ct ,
295+             }
296+         )
297+         return  ctx 
298+ 
299+ 
300+ class  TeamAccessViewSet (UserAccessViewSet ):
301+     serializer_mixin  =  TeamAccessListMixin 
302+ 
303+     def  get_actor_model (self ):
304+         return  get_team_model ()
0 commit comments