Skip to content

Commit 4d637ac

Browse files
committed
refactor: _has_bulk_permission
1 parent 69b10ac commit 4d637ac

1 file changed

Lines changed: 34 additions & 10 deletions

File tree

openedx_authz/rest_api/v1/permissions.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -203,16 +203,46 @@ def _get_permission_instance(self, request) -> BaseScopePermission:
203203
perm_class = PermissionMeta.get_permission_class(scope_namespace)
204204
return perm_class()
205205

206+
def _has_bulk_permission(self, request, view, scopes_list: list[str]) -> bool:
207+
"""Check permissions for a bulk request carrying multiple scopes.
208+
209+
Bulk operations are only supported for endpoints decorated with
210+
``@authz_permissions``. A handler that does not use the decorator (i.e. does
211+
not mix in ``MethodPermissionMixin``) has no declared permissions to evaluate
212+
per-scope, so bulk access is denied outright.
213+
214+
Every scope in ``scopes_list`` must pass at least one of the required
215+
permissions declared by the decorator (OR logic per permission, AND logic
216+
across scopes).
217+
218+
Args:
219+
request: The Django REST framework request object.
220+
view: The view being accessed.
221+
scopes_list: The list of scope values from ``request.data["scopes"]``.
222+
223+
Returns:
224+
bool: True only if every scope passes at least one required permission.
225+
"""
226+
perm_instance = self._get_permission_instance(request) # namespace resolved from scopes[0]
227+
# Bulk without @authz_permissions decorator is not supported: there are no
228+
# per-method permissions to iterate over, so we cannot safely grant access.
229+
if not isinstance(perm_instance, MethodPermissionMixin):
230+
return False
231+
required = perm_instance.get_required_permissions(request, view)
232+
if not required:
233+
return False
234+
return all(perm_instance.validate_permissions(request, required, sv) for sv in scopes_list)
235+
206236
def has_permission(self, request, view) -> bool:
207237
"""Delegate permission check to the appropriate scope-specific permission class.
208238
209239
Superusers and staff members are automatically granted permission. For other
210240
users, the permission check is delegated to the permission class registered
211241
for the request's scope namespace.
212242
213-
For bulk PUT requests that carry a ``scopes`` list, every scope in the list
214-
must pass at least one of the required permissions (OR logic per permission,
215-
AND logic across scopes).
243+
For bulk requests that carry a ``scopes`` list, delegates to
244+
``_has_bulk_permission``: every scope must pass at least one of the required
245+
permissions (OR logic per permission, AND logic across scopes).
216246
217247
Examples:
218248
>>> # Regular user gets scope-specific check
@@ -223,13 +253,7 @@ def has_permission(self, request, view) -> bool:
223253
return True
224254
scopes_list = request.data.get("scopes")
225255
if scopes_list and isinstance(scopes_list, list):
226-
perm_instance = self._get_permission_instance(request) # namespace resolved from scopes[0]
227-
if not isinstance(perm_instance, MethodPermissionMixin):
228-
return False
229-
required = perm_instance.get_required_permissions(request, view)
230-
if not required:
231-
return False
232-
return all(perm_instance.validate_permissions(request, required, sv) for sv in scopes_list)
256+
return self._has_bulk_permission(request, view, scopes_list)
233257
return self._get_permission_instance(request).has_permission(request, view)
234258

235259
def has_object_permission(self, request, view, obj) -> bool:

0 commit comments

Comments
 (0)