Exemplo n.º 1
0
        async def run(self, **kwargs):
            options = {**self.defaults, **kwargs}

            if options["blob"] is None:
                to_sync_um = options["local_manifest"].to_remote(
                    author=options["signed_author"], timestamp=options["signed_timestamp"]
                )
                options["blob"] = to_sync_um.dump_sign_and_encrypt(
                    author_signkey=options["author_signkey"], key=options["user_manifest_key"]
                )

            await running_backend.backend.vlob.update(
                organization_id=alice.organization_id,
                author=options["backend_author"],
                encryption_revision=1,
                vlob_id=VlobID(alice.user_manifest_id.uuid),
                version=self._next_version,
                timestamp=options["backend_timestamp"],
                blob=options["blob"],
            )
            self._next_version += 1
            # This should trigger FSError
            await alice_user_fs.sync()
Exemplo n.º 2
0
 async def _assert_write_access_disallowed(encryption_revision):
     rep = await vlob_create(
         alice_backend_sock,
         realm_id=realm_id,
         vlob_id=VlobID.new(),
         blob=b"data",
         encryption_revision=encryption_revision,
         check_rep=False,
     )
     assert rep == {"status": "in_maintenance"}
     rep = await vlob_update(
         alice_backend_sock,
         vlob_id,
         version=2,
         blob=b"data",
         encryption_revision=encryption_revision,
         check_rep=False,
     )
     assert rep == {"status": "in_maintenance"}
     rep = await block_create(
         alice_backend_sock, block_id=block_id, realm_id=realm_id, block=b"data", check_rep=False
     )
     assert rep == {"status": "in_maintenance"}
Exemplo n.º 3
0
# Parsec Cloud (https://parsec.cloud) Copyright (c) AGPLv3 2016-2021 Scille SAS

import pytest
from pendulum import datetime
from unittest.mock import ANY

from parsec.api.protocol import VlobID, RealmID, RealmRole
from parsec.api.data import RealmRoleCertificateContent, UserProfile
from parsec.backend.realm import RealmGrantedRole

from tests.common import freeze_time, customize_fixtures
from tests.backend.common import realm_update_roles, realm_get_role_certificates, vlob_create

NOW = datetime(2000, 1, 1)
VLOB_ID = VlobID.from_hex("00000000000000000000000000000001")
REALM_ID = RealmID.from_hex("0000000000000000000000000000000A")


@pytest.mark.trio
async def test_get_roles_not_found(alice_backend_sock):
    rep = await realm_get_role_certificates(alice_backend_sock, REALM_ID)
    assert rep == {
        "status": "not_found",
        "reason": "Realm `0000000000000000000000000000000a` doesn't exist",
    }


async def _realm_get_clear_role_certifs(sock, realm_id):
    rep = await realm_get_role_certificates(sock, realm_id)
    assert rep["status"] == "ok"
    cooked = [
Exemplo n.º 4
0
async def test_organization_stats_data(alice_backend_sock, realm,
                                       realm_factory, alice, backend):
    stats = await organization_stats(alice_backend_sock)
    assert stats == {
        "status":
        "ok",
        "data_size":
        0,
        "metadata_size":
        ANY,
        "users":
        3,
        "active_users":
        3,
        "users_per_profile_detail": [
            {
                "profile": UserProfile.ADMIN,
                "active": 2,
                "revoked": 0
            },
            {
                "profile": UserProfile.STANDARD,
                "active": 1,
                "revoked": 0
            },
            {
                "profile": UserProfile.OUTSIDER,
                "active": 0,
                "revoked": 0
            },
        ],
        "realms":
        4,
    }
    initial_metadata_size = stats["metadata_size"]

    # Create new metadata
    await backend.vlob.create(
        organization_id=alice.organization_id,
        author=alice.device_id,
        realm_id=realm,
        encryption_revision=1,
        vlob_id=VlobID.new(),
        timestamp=pendulum.now(),
        blob=b"1234",
    )
    stats = await organization_stats(alice_backend_sock)
    assert stats == {
        "status":
        "ok",
        "data_size":
        0,
        "metadata_size":
        initial_metadata_size + 4,
        "users":
        3,
        "active_users":
        3,
        "users_per_profile_detail": [
            {
                "profile": UserProfile.ADMIN,
                "active": 2,
                "revoked": 0
            },
            {
                "profile": UserProfile.STANDARD,
                "active": 1,
                "revoked": 0
            },
            {
                "profile": UserProfile.OUTSIDER,
                "active": 0,
                "revoked": 0
            },
        ],
        "realms":
        4,
    }

    # Create new data
    await backend.block.create(
        organization_id=alice.organization_id,
        author=alice.device_id,
        block_id=BlockID.new(),
        realm_id=realm,
        block=b"1234",
    )
    stats = await organization_stats(alice_backend_sock)
    assert stats == {
        "status":
        "ok",
        "data_size":
        4,
        "metadata_size":
        initial_metadata_size + 4,
        "users":
        3,
        "active_users":
        3,
        "users_per_profile_detail": [
            {
                "profile": UserProfile.ADMIN,
                "active": 2,
                "revoked": 0
            },
            {
                "profile": UserProfile.STANDARD,
                "active": 1,
                "revoked": 0
            },
            {
                "profile": UserProfile.OUTSIDER,
                "active": 0,
                "revoked": 0
            },
        ],
        "realms":
        4,
    }

    # create new workspace
    await realm_factory(backend, alice)
    stats = await organization_stats(alice_backend_sock)
    assert stats == {
        "status":
        "ok",
        "data_size":
        4,
        "metadata_size":
        initial_metadata_size + 4,
        "users":
        3,
        "active_users":
        3,
        "users_per_profile_detail": [
            {
                "profile": UserProfile.ADMIN,
                "active": 2,
                "revoked": 0
            },
            {
                "profile": UserProfile.STANDARD,
                "active": 1,
                "revoked": 0
            },
            {
                "profile": UserProfile.OUTSIDER,
                "active": 0,
                "revoked": 0
            },
        ],
        "realms":
        5,
    }
Exemplo n.º 5
0
    async def _outbound_sync_inner(
            self, timestamp_greater_than: Optional[DateTime] = None) -> bool:
        base_um = self.get_user_manifest()
        if not base_um.need_sync:
            return True

        # Make sure the corresponding realm has been created in the backend
        if base_um.is_placeholder:
            certif = RealmRoleCertificateContent.build_realm_root_certif(
                author=self.device.device_id,
                timestamp=self.device.timestamp(),
                realm_id=RealmID(self.device.user_manifest_id.uuid),
            ).dump_and_sign(self.device.signing_key)

            try:
                rep = await self.backend_cmds.realm_create(certif)

            except BackendNotAvailable as exc:
                raise FSBackendOfflineError(str(exc)) from exc

            except BackendConnectionError as exc:
                raise FSError(
                    f"Cannot create user manifest's realm in backend: {exc}"
                ) from exc

            if rep["status"] == "already_exists":
                # It's possible a previous attempt to create this realm
                # succeeded but we didn't receive the confirmation, hence
                # we play idempotent here.
                pass
            elif rep["status"] != "ok":
                raise FSError(
                    f"Cannot create user manifest's realm in backend: {rep}")

        # Sync placeholders
        for w in base_um.workspaces:
            await self._workspace_minimal_sync(w)

        # Build vlob
        timestamp = self.device.timestamp()
        if timestamp_greater_than is not None:
            timestamp = max(
                timestamp,
                timestamp_greater_than.add(
                    microseconds=MANIFEST_STAMP_AHEAD_US))
        to_sync_um = base_um.to_remote(author=self.device.device_id,
                                       timestamp=timestamp)
        ciphered = to_sync_um.dump_sign_and_encrypt(
            author_signkey=self.device.signing_key,
            key=self.device.user_manifest_key)

        # Sync the vlob with backend
        try:
            # Note encryption_revision is always 1 given we never reencrypt
            # the user manifest's realm
            if to_sync_um.version == 1:
                rep = await self.backend_cmds.vlob_create(
                    RealmID(self.user_manifest_id.uuid),
                    1,
                    VlobID(self.user_manifest_id.uuid),
                    timestamp,
                    ciphered,
                )
            else:
                rep = await self.backend_cmds.vlob_update(
                    1, VlobID(self.user_manifest_id.uuid), to_sync_um.version,
                    timestamp, ciphered)

        except BackendNotAvailable as exc:
            raise FSBackendOfflineError(str(exc)) from exc

        except BackendConnectionError as exc:
            raise FSError(f"Cannot sync user manifest: {exc}") from exc

        if rep["status"] in ("already_exists", "bad_version"):
            # Concurrency error (handled by the caller)
            return False
        elif rep["status"] == "in_maintenance":
            raise FSWorkspaceInMaintenance(
                f"Cannot modify workspace data while it is in maintenance: {rep}"
            )
        elif rep["status"] == "require_greater_timestamp":
            return await self._outbound_sync_inner(rep["strictly_greater_than"]
                                                   )
        elif rep["status"] != "ok":
            raise FSError(f"Cannot sync user manifest: {rep}")

        # Merge back the manifest in local
        async with self._update_user_manifest_lock:
            diverged_um = self.get_user_manifest()
            # Final merge could have been achieved by a concurrent operation
            if to_sync_um.version > diverged_um.base_version:
                merged_um = merge_local_user_manifests(diverged_um, to_sync_um)
                await self.set_user_manifest(merged_um)
            self.event_bus.send(CoreEvent.FS_ENTRY_SYNCED,
                                id=self.user_manifest_id)

        return True
Exemplo n.º 6
0
# Parsec Cloud (https://parsec.cloud) Copyright (c) AGPLv3 2016-2021 Scille SAS

import pytest
from pendulum import datetime

from parsec.api.protocol import VlobID, RealmID, RealmRole, APIEvent
from parsec.backend.realm import RealmGrantedRole
from parsec.backend.backend_events import BackendEvent

from tests.backend.common import events_subscribe, events_listen_nowait

NOW = datetime(2000, 1, 3)
VLOB_ID = VlobID.from_hex("00000000000000000000000000000001")
OTHER_VLOB_ID = VlobID.from_hex("00000000000000000000000000000002")
YET_ANOTHER_VLOB_ID = VlobID.from_hex("00000000000000000000000000000003")
REALM_ID = RealmID.from_hex("0000000000000000000000000000000A")


@pytest.mark.trio
async def test_vlobs_updated_event_ok(backend, alice_backend_sock, alice,
                                      alice2, realm, other_realm):
    # Not listened events
    with backend.event_bus.listen() as spy:
        await backend.vlob.create(
            organization_id=alice.organization_id,
            author=alice.device_id,
            realm_id=realm,
            encryption_revision=1,
            vlob_id=VLOB_ID,
            timestamp=NOW,
            blob=b"v1",
Exemplo n.º 7
0
        async def _create_realm_and_first_vlob(self, device):
            manifest = initial_user_manifest_state.get_user_manifest_v1_for_backend(device)
            if manifest.author == device.device_id:
                author = device
            else:
                author = self.get_device(device.organization_id, manifest.author)
            realm_id = RealmID(author.user_manifest_id.uuid)
            vlob_id = VlobID(author.user_manifest_id.uuid)

            with self.backend.event_bus.listen() as spy:

                # The realm needs to be created srictly before the manifest timestamp
                realm_create_timestamp = manifest.timestamp.subtract(microseconds=1)

                await self.backend.realm.create(
                    organization_id=author.organization_id,
                    self_granted_role=RealmGrantedRole(
                        realm_id=realm_id,
                        user_id=author.user_id,
                        certificate=RealmRoleCertificateContent(
                            author=author.device_id,
                            timestamp=realm_create_timestamp,
                            realm_id=realm_id,
                            user_id=author.user_id,
                            role=RealmRole.OWNER,
                        ).dump_and_sign(author.signing_key),
                        role=RealmRole.OWNER,
                        granted_by=author.device_id,
                        granted_on=realm_create_timestamp,
                    ),
                )

                await self.backend.vlob.create(
                    organization_id=author.organization_id,
                    author=author.device_id,
                    realm_id=realm_id,
                    encryption_revision=1,
                    vlob_id=vlob_id,
                    timestamp=manifest.timestamp,
                    blob=manifest.dump_sign_and_encrypt(
                        author_signkey=author.signing_key, key=author.user_manifest_key
                    ),
                )

                # Avoid possible race condition in tests listening for events
                await spy.wait_multiple(
                    [
                        (
                            BackendEvent.REALM_ROLES_UPDATED,
                            {
                                "organization_id": author.organization_id,
                                "author": author.device_id,
                                "realm_id": realm_id,
                                "user": author.user_id,
                                "role": RealmRole.OWNER,
                            },
                        ),
                        (
                            BackendEvent.REALM_VLOBS_UPDATED,
                            {
                                "organization_id": author.organization_id,
                                "author": author.device_id,
                                "realm_id": realm_id,
                                "checkpoint": 1,
                                "src_id": vlob_id,
                                "src_version": 1,
                            },
                        ),
                    ]
                )
Exemplo n.º 8
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
Exemplo n.º 9
0
async def test_reconnect_with_remote_changes(frozen_clock, alice2,
                                             running_backend, server_factory,
                                             alice_core, user_fs_factory):
    wid = await alice_core.user_fs.workspace_create(EntryName("w"))
    alice_w = alice_core.user_fs.get_workspace(wid)
    await alice_w.mkdir("/foo")
    await alice_w.touch("/bar.txt")
    # Wait for sync monitor to do it job
    await frozen_clock.sleep_with_autojump(60)
    async with frozen_clock.real_clock_timeout():
        await alice_core.wait_idle_monitors()

    # Alice2 connect to the backend through a different server so that we can
    # switch alice offline while keeping alice2 connected
    async with server_factory(running_backend.backend.handle_client) as server:
        alice2 = server.correct_addr(alice2)
        async with user_fs_factory(alice2) as alice2_user_fs:

            # Switch backend offline for alice (but not alice2 !)
            with running_backend.offline():
                # Get back modifications from alice
                await alice2_user_fs.sync()
                alice2_w = alice2_user_fs.get_workspace(wid)
                await alice2_w.sync()
                # Modify the workspace while alice is offline
                await alice2_w.mkdir("/foo/spam")
                await alice2_w.write_bytes("/bar.txt", b"v2")

                foo_id = await alice2_w.path_id("/foo")
                spam_id = await alice2_w.path_id("/foo/spam")
                bar_id = await alice2_w.path_id("/bar.txt")

                with running_backend.backend.event_bus.listen() as spy:
                    await alice2_w.sync()
                    # Alice misses the vlob updated events before being back online
                    await spy.wait_multiple_with_timeout(
                        [
                            (
                                BackendEvent.REALM_VLOBS_UPDATED,
                                {
                                    "organization_id": alice2.organization_id,
                                    "author": alice2.device_id,
                                    "realm_id": RealmID(wid.uuid),
                                    "checkpoint": ANY,
                                    "src_id": VlobID(spam_id.uuid),
                                    "src_version": 1,
                                },
                            ),
                            (
                                BackendEvent.REALM_VLOBS_UPDATED,
                                {
                                    "organization_id": alice2.organization_id,
                                    "author": alice2.device_id,
                                    "realm_id": RealmID(wid.uuid),
                                    "checkpoint": ANY,
                                    "src_id": VlobID(foo_id.uuid),
                                    "src_version": 2,
                                },
                            ),
                            (
                                BackendEvent.REALM_VLOBS_UPDATED,
                                {
                                    "organization_id": alice2.organization_id,
                                    "author": alice2.device_id,
                                    "realm_id": RealmID(wid.uuid),
                                    "checkpoint": ANY,
                                    "src_id": VlobID(bar_id.uuid),
                                    "src_version": 2,
                                },
                            ),
                        ],
                        in_order=False,
                    )

            with alice_core.event_bus.listen() as spy:
                # Now alice should sync back the changes
                await frozen_clock.sleep_with_autojump(60)
                await spy.wait_multiple_with_timeout(
                    [
                        (
                            CoreEvent.BACKEND_CONNECTION_CHANGED,
                            {
                                "status": BackendConnStatus.READY,
                                "status_exc": spy.ANY
                            },
                        ),
                        (CoreEvent.FS_ENTRY_DOWNSYNCED, {
                            "workspace_id": wid,
                            "id": foo_id
                        }),
                        (CoreEvent.FS_ENTRY_DOWNSYNCED, {
                            "workspace_id": wid,
                            "id": bar_id
                        }),
                    ],
                    in_order=False,
                )
Exemplo n.º 10
0
async def test_organization_stats_data(backend_rest_send, realm, realm_factory,
                                       alice, backend):
    async def organization_stats():
        status, _, body = await backend_rest_send(
            f"/administration/organizations/{alice.organization_id}/stats")
        assert status == (200, "OK")
        return organization_stats_rep_serializer.load(body)

    rep = await organization_stats()
    assert rep == {
        "data_size":
        0,
        "metadata_size":
        ANY,
        "users":
        3,
        "active_users":
        3,
        "users_per_profile_detail": [
            {
                "profile": UserProfile.ADMIN,
                "active": 2,
                "revoked": 0
            },
            {
                "profile": UserProfile.STANDARD,
                "active": 1,
                "revoked": 0
            },
            {
                "profile": UserProfile.OUTSIDER,
                "active": 0,
                "revoked": 0
            },
        ],
        "realms":
        4,
    }
    initial_metadata_size = rep["metadata_size"]

    # Create new metadata
    await backend.vlob.create(
        organization_id=alice.organization_id,
        author=alice.device_id,
        realm_id=realm,
        encryption_revision=1,
        vlob_id=VlobID.new(),
        timestamp=pendulum.now(),
        blob=b"1234",
    )
    rep = await organization_stats()
    assert rep == {
        "data_size":
        0,
        "metadata_size":
        initial_metadata_size + 4,
        "users":
        3,
        "active_users":
        3,
        "users_per_profile_detail": [
            {
                "profile": UserProfile.ADMIN,
                "active": 2,
                "revoked": 0
            },
            {
                "profile": UserProfile.STANDARD,
                "active": 1,
                "revoked": 0
            },
            {
                "profile": UserProfile.OUTSIDER,
                "active": 0,
                "revoked": 0
            },
        ],
        "realms":
        4,
    }

    # Create new data
    await backend.block.create(
        organization_id=alice.organization_id,
        author=alice.device_id,
        block_id=BlockID.new(),
        realm_id=realm,
        block=b"1234",
    )
    rep = await organization_stats()
    assert rep == {
        "data_size":
        4,
        "metadata_size":
        initial_metadata_size + 4,
        "users":
        3,
        "active_users":
        3,
        "users_per_profile_detail": [
            {
                "profile": UserProfile.ADMIN,
                "active": 2,
                "revoked": 0
            },
            {
                "profile": UserProfile.STANDARD,
                "active": 1,
                "revoked": 0
            },
            {
                "profile": UserProfile.OUTSIDER,
                "active": 0,
                "revoked": 0
            },
        ],
        "realms":
        4,
    }

    # create new workspace
    await realm_factory(backend, alice)
    rep = await organization_stats()
    assert rep == {
        "data_size":
        4,
        "metadata_size":
        initial_metadata_size + 4,
        "users":
        3,
        "active_users":
        3,
        "users_per_profile_detail": [
            {
                "profile": UserProfile.ADMIN,
                "active": 2,
                "revoked": 0
            },
            {
                "profile": UserProfile.STANDARD,
                "active": 1,
                "revoked": 0
            },
            {
                "profile": UserProfile.OUTSIDER,
                "active": 0,
                "revoked": 0
            },
        ],
        "realms":
        5,
    }
Exemplo n.º 11
0
async def test_create_check_access_rights(backend, alice, bob,
                                          bob_backend_sock, realm,
                                          next_timestamp):
    vlob_id = VlobID.new()

    # User not part of the realm
    rep = await vlob_create(bob_backend_sock,
                            realm,
                            vlob_id,
                            b"Initial version.",
                            next_timestamp(),
                            check_rep=False)
    assert rep == {"status": "not_allowed"}

    # User part of the realm with various role
    for role, access_granted in [
        (RealmRole.READER, False),
        (RealmRole.CONTRIBUTOR, True),
        (RealmRole.MANAGER, True),
        (RealmRole.OWNER, True),
    ]:
        await backend.realm.update_roles(
            alice.organization_id,
            RealmGrantedRole(
                certificate=b"dummy",
                realm_id=realm,
                user_id=bob.user_id,
                role=role,
                granted_by=alice.device_id,
                granted_on=next_timestamp(),
            ),
        )
        vlob_id = VlobID.new()
        rep = await vlob_create(bob_backend_sock,
                                realm,
                                vlob_id,
                                b"Initial version.",
                                next_timestamp(),
                                check_rep=False)
        if access_granted:
            assert rep == {"status": "ok"}

        else:
            assert rep == {"status": "not_allowed"}

    # Ensure user that used to be part of the realm have no longer access
    await backend.realm.update_roles(
        alice.organization_id,
        RealmGrantedRole(
            certificate=b"<dummy>",
            realm_id=realm,
            user_id=bob.user_id,
            role=None,
            granted_by=alice.device_id,
            granted_on=next_timestamp(),
        ),
    )
    rep = await vlob_create(bob_backend_sock,
                            realm,
                            vlob_id,
                            b"Initial version.",
                            next_timestamp(),
                            check_rep=False)
    assert rep == {"status": "not_allowed"}
Exemplo n.º 12
0
async def test_access_during_reencryption(
    backend, alice_backend_sock, alice, realm_factory, next_timestamp
):
    # First initialize a nice realm with block and vlob
    realm_id = await realm_factory(backend, author=alice)
    vlob_id = VlobID.new()
    block_id = BlockID.new()
    await backend.vlob.create(
        organization_id=alice.organization_id,
        author=alice.device_id,
        realm_id=realm_id,
        encryption_revision=1,
        vlob_id=vlob_id,
        timestamp=next_timestamp(),
        blob=b"v1",
    )
    await backend.block.create(
        organization_id=alice.organization_id,
        author=alice.device_id,
        realm_id=realm_id,
        block_id=block_id,
        block=b"<block_data>",
    )

    async def _assert_write_access_disallowed(encryption_revision):
        rep = await vlob_create(
            alice_backend_sock,
            realm_id=realm_id,
            vlob_id=VlobID.new(),
            blob=b"data",
            encryption_revision=encryption_revision,
            check_rep=False,
        )
        assert rep == {"status": "in_maintenance"}
        rep = await vlob_update(
            alice_backend_sock,
            vlob_id,
            version=2,
            blob=b"data",
            encryption_revision=encryption_revision,
            check_rep=False,
        )
        assert rep == {"status": "in_maintenance"}
        rep = await block_create(
            alice_backend_sock, block_id=block_id, realm_id=realm_id, block=b"data", check_rep=False
        )
        assert rep == {"status": "in_maintenance"}

    async def _assert_read_access_allowed(encryption_revision, expected_blob=b"v1"):
        rep = await vlob_read(
            alice_backend_sock, vlob_id=vlob_id, version=1, encryption_revision=encryption_revision
        )
        assert rep["status"] == "ok"
        assert rep["blob"] == expected_blob

        rep = await block_read(alice_backend_sock, block_id=block_id)
        assert rep == {"status": "ok", "block": b"<block_data>"}

        # For good measure, also try those read-only commands even if they
        # are encryption-revision agnostic
        rep = await vlob_list_versions(alice_backend_sock, vlob_id=vlob_id)
        assert rep["status"] == "ok"
        rep = await vlob_poll_changes(alice_backend_sock, realm_id=realm_id, last_checkpoint=0)
        assert rep["status"] == "ok"

    async def _assert_read_access_bad_encryption_revision(encryption_revision, expected_status):
        rep = await vlob_read(
            alice_backend_sock, vlob_id=vlob_id, version=1, encryption_revision=encryption_revision
        )
        assert rep == {"status": expected_status}

    # Sanity check just to make we can access the data with initial encryption revision
    await _assert_read_access_allowed(1)

    # Now start reencryption
    await backend.realm.start_reencryption_maintenance(
        organization_id=alice.organization_id,
        author=alice.device_id,
        realm_id=realm_id,
        encryption_revision=2,
        per_participant_message={alice.user_id: b"<whatever>"},
        timestamp=pendulum_now(),
    )

    # Only read with old encryption revision is now allowed
    await _assert_read_access_allowed(1)
    await _assert_read_access_bad_encryption_revision(2, expected_status="in_maintenance")
    await _assert_write_access_disallowed(1)
    await _assert_write_access_disallowed(2)

    # Actually reencrypt the vlob data, this shouldn't affect us for the moment
    # given reencryption is not formally finished
    await backend.vlob.maintenance_save_reencryption_batch(
        organization_id=alice.organization_id,
        author=alice.device_id,
        realm_id=realm_id,
        encryption_revision=2,
        batch=[(vlob_id, 1, b"v2")],
    )

    await _assert_read_access_allowed(1)
    await _assert_read_access_bad_encryption_revision(2, expected_status="in_maintenance")
    await _assert_write_access_disallowed(1)
    await _assert_write_access_disallowed(2)

    # Finish the reencryption
    await backend.realm.finish_reencryption_maintenance(
        organization_id=alice.organization_id,
        author=alice.device_id,
        realm_id=realm_id,
        encryption_revision=2,
    )

    # Now only the new encryption revision is allowed
    await _assert_read_access_allowed(2, expected_blob=b"v2")
    await _assert_read_access_bad_encryption_revision(1, expected_status="bad_encryption_revision")