GooDrive/backend/drive/permissions.py
Louis Mazin 90004d4540 nickel
2026-10-14 17:27:37 +02:00

85 lines
3.3 KiB
Python

"""Granular, non-escalating rights for spaces and individual shares."""
from .models import ItemGrant, SpaceGrant
RIGHTS = ('view', 'download', 'add', 'modify', 'delete', 'share', 'manage_access', 'create_spaces', 'manage_space')
PRESETS = {
'reader': ('view', 'download'),
'contributor': ('view', 'download', 'add', 'modify'),
'manager': RIGHTS,
}
def legacy_rights(read=True, write=False, invite=False):
result = ['view', 'download'] if read else []
if write:
result += ['add', 'modify', 'delete', 'share']
if invite:
result += ['manage_access', 'create_spaces']
return result
def normalized(value):
if not isinstance(value, list) or len(value) > len(RIGHTS) or any(not isinstance(k, str) or k not in RIGHTS for k in value):
raise ValueError('Liste des permissions invalide.')
rights = set(value)
if 'view' not in rights:
raise ValueError('La permission Consulter est obligatoire.')
return sorted(rights, key=RIGHTS.index)
def _is_admin(user):
return user.is_authenticated and user.is_active and (user.is_staff or user.is_superuser)
def space_rights(user, space):
if not user.is_authenticated or not user.is_active or space is None:
return set()
if space.private_owner_id:
# Never expose someone else's private contents, including to admins.
return set(RIGHTS) if space.private_owner_id == user.pk else set()
if _is_admin(user) or space.managed_by_id == user.pk:
return set(RIGHTS)
if space.user_id == user.pk:
return set(RIGHTS)
grant = SpaceGrant.objects.filter(space=space, user=user).first()
if grant:
return set(grant.permissions) if grant.permissions is not None else set(legacy_rights(write=grant.can_write, invite=grant.can_invite))
if space.parent_id and space.can_view_parent and space.user_id == user.pk:
return set(legacy_rights(write=space.can_write_parent, invite=space.can_invite))
# Historical assigned-space access (parent) for legacy sub-users.
owned = getattr(user, 'drive_space', None)
if owned and owned.parent_id == space.pk and owned.can_view_parent:
return set(owned.parent_permissions) if owned.parent_permissions is not None else set(legacy_rights(write=owned.can_write_parent, invite=owned.can_invite))
return set()
def has_space_right(user, space, right):
return right in space_rights(user, space)
def item_rights(user, item):
rights = space_rights(user, item.space)
if rights:
return rights
if not user.is_authenticated or not user.is_active:
return set()
# Share on a parent folder propagates to its descendants.
seen = set()
current = item
while current is not None:
if current.pk in seen:
return set()
seen.add(current.pk)
grant = ItemGrant.objects.filter(user=user, item=current).first()
if grant:
return set(grant.permissions) if grant.permissions is not None else set(PRESETS['reader'])
current = current.parent
return set()
def assert_delegation(issuer_rights, desired):
clean = set(normalized(desired))
if not clean.issubset(set(issuer_rights)):
raise ValueError('Tu ne peux pas attribuer des permissions supérieures aux tiennes.')
return normalized(desired)