128 lines
5.0 KiB
Python
128 lines
5.0 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')
|
|
# A file/folder share never delegates space administration or deletion.
|
|
ITEM_SHARE_RIGHTS = ('view', 'download', 'modify', 'add')
|
|
|
|
|
|
def available_item_share_rights(item):
|
|
return tuple(right for right in ITEM_SHARE_RIGHTS if right != 'add' or item.is_folder)
|
|
|
|
|
|
def normalized_item_share(value, item):
|
|
rights = normalized(value)
|
|
if not set(rights).issubset(available_item_share_rights(item)):
|
|
raise ValueError('Ce droit ne peut pas être attribué sur ce partage.')
|
|
return rights
|
|
|
|
|
|
def assert_item_delegation(issuer_rights, desired, item):
|
|
rights = normalized_item_share(desired, item)
|
|
if not set(rights).issubset(set(issuer_rights)):
|
|
raise ValueError('Tu ne peux pas attribuer des permissions supérieures aux tiennes.')
|
|
return rights
|
|
|
|
|
|
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):
|
|
return set(RIGHTS)
|
|
# A grant can also explicitly limit an assigned owner or manager, whose
|
|
# historical roles otherwise implied full access.
|
|
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.managed_by_id == user.pk or space.user_id == user.pk:
|
|
return set(RIGHTS)
|
|
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 can_change_space_quota(user, space):
|
|
"""The allocated quota is decided by the administrator, not the invitee.
|
|
|
|
A non-admin may size only a shared subspace that they themselves created,
|
|
provided they still have the space-management permission. Having
|
|
``manage_space`` on an assigned/shared space must never allow increasing
|
|
the storage allocation set by its creator/administrator.
|
|
"""
|
|
if space is None or space.private_owner_id or not user.is_authenticated or not user.is_active:
|
|
return False
|
|
if _is_admin(user):
|
|
return True
|
|
return (space.parent_id is not None
|
|
and space.user_id is None
|
|
and space.managed_by_id == user.pk
|
|
and not SpaceGrant.objects.filter(user=user, space=space).exists()
|
|
and has_space_right(user, space, 'manage_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'])) & set(available_item_share_rights(current))
|
|
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)
|