85 lines
3.3 KiB
Python
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)
|