Esempio n. 1
0
    async def list_versions(
            self, entry_id: EntryID) -> Dict[int, Tuple[DateTime, DeviceID]]:
        """
        Raises:
            FSError
            FSRemoteOperationError
            FSBackendOfflineError
            FSWorkspaceInMaintenance
            FSRemoteManifestNotFound
        """
        with translate_backend_cmds_errors():
            rep = await self.backend_cmds.vlob_list_versions(
                VlobID(entry_id.uuid))
        if rep["status"] == "not_allowed":
            # Seems we lost the access to the realm
            raise FSWorkspaceNoReadAccess(
                "Cannot load manifest: no read access")
        elif rep["status"] == "not_found":
            raise FSRemoteManifestNotFound(entry_id)
        elif rep["status"] == "in_maintenance":
            raise FSWorkspaceInMaintenance(
                "Cannot download vlob while the workspace is in maintenance")
        elif rep["status"] != "ok":
            raise FSError(f"Cannot fetch vlob {entry_id}: `{rep['status']}`")

        return rep["versions"]
Esempio n. 2
0
    async def load_manifest(
        self,
        entry_id: EntryID,
        version: int = None,
        timestamp: Pendulum = None,
        expected_backend_timestamp: Pendulum = None,
    ) -> RemoteManifest:
        """
        Download a manifest.

        Only one from version or timestamp parameters can be specified at the same time.
        expected_backend_timestamp enables to check a timestamp against the one returned by the
        backend.

        Raises:
            FSError
            FSBackendOfflineError
            FSWorkspaceInMaintenance
            FSRemoteManifestNotFound
            FSBadEncryptionRevision
            FSWorkspaceNoAccess
        """
        if timestamp is not None and version is not None:
            raise FSError(
                f"Supplied both version {version} and timestamp `{timestamp}` for manifest "
                f"`{entry_id}`")
        # Download the vlob
        workspace_entry = self.get_workspace_entry()
        rep = await self._backend_cmds(
            "vlob_read",
            workspace_entry.encryption_revision,
            entry_id,
            version=version,
            timestamp=timestamp if version is None else None,
        )
        if rep["status"] == "not_found":
            raise FSRemoteManifestNotFound(entry_id)
        elif rep["status"] == "not_allowed":
            # Seems we lost the access to the realm
            raise FSWorkspaceNoReadAccess(
                "Cannot load manifest: no read access")
        elif rep["status"] == "bad_version":
            raise FSRemoteManifestNotFoundBadVersion(entry_id)
        elif rep["status"] == "bad_timestamp":
            raise FSRemoteManifestNotFoundBadTimestamp(entry_id)
        elif rep["status"] == "bad_encryption_revision":
            raise FSBadEncryptionRevision(
                f"Cannot fetch vlob {entry_id}: Bad encryption revision provided"
            )
        elif rep["status"] == "in_maintenance":
            raise FSWorkspaceInMaintenance(
                f"Cannot download vlob while the workspace is in maintenance")
        elif rep["status"] != "ok":
            raise FSError(f"Cannot fetch vlob {entry_id}: `{rep['status']}`")

        expected_version = rep["version"]
        expected_author = rep["author"]
        expected_timestamp = rep["timestamp"]
        if version not in (None, expected_version):
            raise FSError(
                f"Backend returned invalid version for vlob {entry_id} (expecting {version}, "
                f"got {expected_version})")

        if expected_backend_timestamp and expected_backend_timestamp != expected_timestamp:
            raise FSError(
                f"Backend returned invalid expected timestamp for vlob {entry_id} at version "
                f"{version} (expecting {expected_backend_timestamp}, got {expected_timestamp})"
            )

        author = await self.remote_device_manager.get_device(expected_author)

        try:
            remote_manifest = RemoteManifest.decrypt_verify_and_load(
                rep["blob"],
                key=workspace_entry.key,
                author_verify_key=author.verify_key,
                expected_author=expected_author,
                expected_timestamp=expected_timestamp,
                expected_version=expected_version,
                expected_id=entry_id,
            )
        except DataError as exc:
            raise FSError(f"Cannot decrypt vlob: {exc}") from exc

        # Finally make sure author was allowed to create this manifest
        role_at_timestamp = await self._get_user_realm_role_at(
            expected_author.user_id, expected_timestamp)
        if role_at_timestamp is None:
            raise FSError(
                f"Manifest was created at {expected_timestamp} by `{expected_author}` "
                "which had no right to access the workspace at that time")
        elif role_at_timestamp == RealmRole.READER:
            raise FSError(
                f"Manifest was created at {expected_timestamp} by `{expected_author}` "
                "which had write right on the workspace at that time")

        return remote_manifest
Esempio n. 3
0
    async def load_manifest(
        self,
        entry_id: EntryID,
        version: Optional[int] = None,
        timestamp: Optional[DateTime] = None,
        expected_backend_timestamp: Optional[DateTime] = None,
        workspace_entry: Optional[WorkspaceEntry] = None,
    ) -> BaseRemoteManifest:
        """
        Download a manifest.

        Only one from version or timestamp parameters can be specified at the same time.
        expected_backend_timestamp enables to check a timestamp against the one returned by the
        backend.

        Raises:
            FSError
            FSBackendOfflineError
            FSRemoteOperationError
            FSWorkspaceInMaintenance
            FSRemoteManifestNotFound
            FSBadEncryptionRevision
            FSWorkspaceNoAccess
            FSUserNotFoundError
            FSDeviceNotFoundError
            FSInvalidTrustchainError
        """
        assert (timestamp is None or version is None
                ), "Either timestamp or version argument should be provided"
        # Get the current and requested workspace entry
        # They're usually the same, except when loading from a workspace while it's in maintenance
        current_workspace_entry = self.get_workspace_entry()
        workspace_entry = current_workspace_entry if workspace_entry is None else workspace_entry
        # Download the vlob
        with translate_backend_cmds_errors():
            rep = await self.backend_cmds.vlob_read(
                workspace_entry.encryption_revision,
                VlobID(entry_id.uuid),
                version=version,
                timestamp=timestamp if version is None else None,
            )
        # Special case for loading manifest while in maintenance.
        # This is done to allow users to fetch data from a workspace while it's being reencrypted.
        # If the workspace is in maintenance for another reason (such as garbage collection),
        # the recursive call to load manifest will simply also fail with an FSWorkspaceInMaintenance.
        if (rep["status"] == "in_maintenance"
                and workspace_entry.encryption_revision
                == current_workspace_entry.encryption_revision):
            # Getting the last workspace entry with the previous encryption revision
            # requires one or several calls to the backend, meaning the following exceptions might get raised:
            # - FSError
            # - FSBackendOfflineError
            # - FSWorkspaceInMaintenance
            # It is fine to let those exceptions bubble up as there all valid reasons for failing to load a manifest.
            previous_workspace_entry = await self.get_previous_workspace_entry(
            )
            if previous_workspace_entry is not None:
                # Make sure we don't fall into an infinite loop because of some other bug
                assert (previous_workspace_entry.encryption_revision <
                        self.get_workspace_entry().encryption_revision)
                # Recursive call to `load_manifest`, requiring an older encryption revision than the current one
                return await self.load_manifest(
                    entry_id,
                    version=version,
                    timestamp=timestamp,
                    expected_backend_timestamp=expected_backend_timestamp,
                    workspace_entry=previous_workspace_entry,
                )

        if rep["status"] == "not_found":
            raise FSRemoteManifestNotFound(entry_id)
        elif rep["status"] == "not_allowed":
            # Seems we lost the access to the realm
            raise FSWorkspaceNoReadAccess(
                "Cannot load manifest: no read access")
        elif rep["status"] == "bad_version":
            raise FSRemoteManifestNotFoundBadVersion(entry_id)
        elif rep["status"] == "bad_encryption_revision":
            raise FSBadEncryptionRevision(
                f"Cannot fetch vlob {entry_id}: Bad encryption revision provided"
            )
        elif rep["status"] == "in_maintenance":
            raise FSWorkspaceInMaintenance(
                "Cannot download vlob while the workspace is in maintenance")
        elif rep["status"] != "ok":
            raise FSError(f"Cannot fetch vlob {entry_id}: `{rep['status']}`")

        expected_version = rep["version"]
        expected_author = rep["author"]
        expected_timestamp = rep["timestamp"]
        if version not in (None, expected_version):
            raise FSError(
                f"Backend returned invalid version for vlob {entry_id} (expecting {version}, "
                f"got {expected_version})")

        if expected_backend_timestamp and expected_backend_timestamp != expected_timestamp:
            raise FSError(
                f"Backend returned invalid expected timestamp for vlob {entry_id} at version "
                f"{version} (expecting {expected_backend_timestamp}, got {expected_timestamp})"
            )

        with translate_remote_devices_manager_errors():
            author = await self.remote_devices_manager.get_device(
                expected_author)

        try:
            remote_manifest = BaseRemoteManifest.decrypt_verify_and_load(
                rep["blob"],
                key=workspace_entry.key,
                author_verify_key=author.verify_key,
                expected_author=expected_author,
                expected_timestamp=expected_timestamp,
                expected_version=expected_version,
                expected_id=entry_id,
            )
        except DataError as exc:
            raise FSError(f"Cannot decrypt vlob: {exc}") from exc

        # Get the timestamp of the last role for this particular user
        author_last_role_granted_on = rep["author_last_role_granted_on"]
        # Compatibility with older backends (best effort strategy)
        if author_last_role_granted_on is None:
            author_last_role_granted_on = self.device.timestamp()

        # Finally make sure author was allowed to create this manifest
        role_at_timestamp = await self._get_user_realm_role_at(
            expected_author.user_id, expected_timestamp,
            author_last_role_granted_on)
        if role_at_timestamp is None:
            raise FSError(
                f"Manifest was created at {expected_timestamp} by `{expected_author}` "
                "which had no right to access the workspace at that time")
        elif role_at_timestamp == RealmRole.READER:
            raise FSError(
                f"Manifest was created at {expected_timestamp} by `{expected_author}` "
                "which had no right to write on the workspace at that time")

        return remote_manifest