Story #9614

Updated by bmbouter over 2 years ago

This is build on [the introduction of `with_perm`]( 

 ## Motivation 

 Everytime a queryset is constructed that deals with an RBAC enabled object, we need to ensure that only those objects that user has permissions to operate on are available in the queryset results. For example, if I have the `core.delete_task` permission on some objects, but not others, I can't just run `Task.objects.all().delete()`. 

 We deal with querysets in so many places, it would be great to have a safer way to be told if I've filtered each queryset at least in some way by permissions. 

 ## Proposal 

 Add an attribute on all models called `RBAC_PROTECTED = False` and have models opt-in to using this safety feature by setting it to `True` on their model definition. 

 Then modify the querset evaluation to raise an exception if that queryset never had a `with_perm` call occur. This would be an opt-in, model-by-model safety feature. 

 There are some situations when you are supposed to not need a `with_perm` call. For example if the viewset queries for all objects, and then passes the list of pks to the task in the backend to handle, the backend queryset construction already handled permissions but there is no call to `with_perm` there. 

 Let's add a queryset method called `qs.with_no_perms()`. `qs.no_perms_warning()`. With this I could call `Task.objects.with_no_perms().all()` `Task.objects.no_perms_warning().all()` and I would not receive the exception even without a call to `with_perm`. 

 ## Special considerations 

 There could be situations where a new querset is made as a new object, e.g. boolean or set operations. Let's get examples of these kinds of situations right: 

 * `qs.all() | qs.with_perm("core.task_show")` -> unsafe 
 * `qs.none() | qs.with_perm("core.task_show")` -> safe 
 * `qs.none() & qs.with_perm("core.task_show")` ?? 
 * `qs.all() & qs.with_perm("core.task_show")` -> safe