"""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)