Skip to content

REST requests should mirror Alyx user group permissions #882

Open
@k1o0

Description

@k1o0

Most (but maybe not all) REST views use permissions defined in alyx.base.BestRestPublicPermission:

alyx/alyx/alyx/base.py

Lines 648 to 664 in 95c2e40

class BaseRestPublicPermission(permissions.BasePermission):
"""
The purpose is to prevent public users from interfering in any way using writable methods
"""
def has_permission(self, request, view):
if request.method == 'GET':
return True
elif request.user.is_public_user:
return False
else:
return True
def rest_permission_classes():
permission_classes = (permissions.IsAuthenticated & BaseRestPublicPermission,)
return permission_classes
, while most (but maybe not all) model admin instances use the alyx.base.BaseAdmin.has_change_permission method:

alyx/alyx/alyx/base.py

Lines 374 to 413 in 95c2e40

def has_change_permission(self, request, obj=None):
if request.user.is_public_user:
return False
if not obj:
return True
if request.user.is_superuser:
return True
# [CR 2024-03-12]
# HACK: following a request by Charu R from cortexlab, we authorize all users in the
# special Husbandry group to edit litters.
husbandry = 'husbandry' in ', '.join(_.name.lower() for _ in request.user.groups.all())
if husbandry:
if obj.__class__.__name__ in ('Litter', 'Subject', 'BreedingPair'):
return True
# Find subject associated to the object.
if hasattr(obj, 'responsible_user'):
subj = obj
elif getattr(obj, 'session', None):
subj = obj.session.subject
elif getattr(obj, 'subject', None):
subj = obj.subject
else:
return False
resp_user = getattr(subj, 'responsible_user', None)
# List of allowed users for the subject.
allowed = getattr(resp_user, 'allowed_users', None)
allowed = set(allowed.all() if allowed else [])
if resp_user:
allowed.add(resp_user)
# Add the responsible user or user(s) to the list of allowed users.
if hasattr(obj, 'responsible_user'):
allowed.add(obj.responsible_user)
if hasattr(obj, 'user'):
allowed.add(obj.user)
if hasattr(obj, 'users'):
for user in obj.users.all():
allowed.add(user)
return request.user in allowed

This means that users have far more permissions when making REST queries than via the admin interface which is extremely insecure and confusing to users. Below are some suggested improvements:

  • Ensure all REST views are using a shared base permissions set (i.e. permission_classes = rest_permission_classes() is present everywhere)
  • Ensure all model admin classes use the same basic permissions system (i.e. super class call to BaseAdmin.has_change_permission)
  • Consolidate base permissions between APIs (the above two methods can call a common base permissions function)
  • Permit test Alyx to allow all REST permissions for testing purposes - check library tests still pass after changes
  • Add method to BaseTests to check that REST API permissions are suitable. This can be a generic test for all apps.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions