Example #1
0
async def test_new_sharing_trigger_event(alice_core, bob_core,
                                         running_backend):
    # First, create a folder and sync it on backend
    with freeze_time("2000-01-01"):
        wid = await alice_core.user_fs.workspace_create("foo")
    workspace = alice_core.user_fs.get_workspace(wid)
    with freeze_time("2000-01-02"):
        await workspace.sync()

    # Now we can share this workspace with Bob
    with bob_core.event_bus.listen() as spy:
        with freeze_time("2000-01-03"):
            await alice_core.user_fs.workspace_share(
                wid, recipient="bob", role=WorkspaceRole.MANAGER)

        # Bob should get a notification
        await spy.wait_with_timeout(
            "sharing.updated",
            {
                "new_entry":
                WorkspaceEntry(
                    name="foo (shared by alice)",
                    id=wid,
                    key=ANY,
                    encryption_revision=1,
                    encrypted_on=Pendulum(2000, 1, 1),
                    role_cached_on=ANY,
                    role=WorkspaceRole.MANAGER,
                ),
                "previous_entry":
                None,
            },
        )
Example #2
0
async def test_share_workspace_then_rename_it(
    running_backend, alice_user_fs, bob_user_fs, alice, bob
):
    # Share a workspace between Alice and Bob
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w")
    await alice_user_fs.workspace_share(wid, bob.user_id, WorkspaceRole.MANAGER)
    with freeze_time("2000-01-03"):
        await bob_user_fs.process_last_messages()

    # Now Bob and alice both rename the workpsace for there own taste
    await bob_user_fs.workspace_rename(wid, "from_alice")
    await alice_user_fs.workspace_rename(wid, "to_bob")

    await bob_user_fs.sync()
    await alice_user_fs.sync()

    # This should have not changed the workspace in any way
    bw = bob_user_fs.get_workspace(wid)
    aw = alice_user_fs.get_workspace(wid)
    await bw.touch("/ping_bob.txt")
    await aw.mkdir("/ping_alice")

    await bw.sync()
    await aw.sync()
    await bw.sync()

    aw_stat = await aw.path_info("/")
    bw_stat = await bw.path_info("/")
    assert aw_stat == bw_stat
    assert aw_stat["id"] == wid
async def test_sync_then_clean_start(autojump_clock, running_backend,
                                     client_factory, alice, alice2):
    # Given the clients are initialized while the backend is online, we are
    # guaranteed they are connected
    async with client_factory() as alice_client:
        await alice_client.login(alice)

        async with wait_for_entries_synced(alice_client, ("/", "/foo.txt")):

            with freeze_time("2000-01-02"):
                await alice_client.user_fs.touch("/foo.txt")

            with freeze_time("2000-01-03"):
                await alice_client.user_fs.file_write("/foo.txt", b"v1")

            await alice_client.user_fs.sync("/foo.txt")

        async with client_factory() as alice2_client2, wait_for_entries_synced(
                alice2_client2, ["/"]):
            await alice2_client2.login(alice2)

            for path in ("/", "/foo.txt"):
                stat = await alice_client.user_fs.stat(path)
                stat2 = await alice2_client2.user_fs.stat(path)
                assert stat2 == stat
Example #4
0
async def test_create_already_existing_block(running_backend, alice_user_fs,
                                             alice2_user_fs):
    # First create&sync an empty file

    with freeze_time("2000-01-02"):
        wid = await create_shared_workspace("w", alice_user_fs, alice2_user_fs)
        workspace = alice_user_fs.get_workspace(wid)
        workspace2 = alice2_user_fs.get_workspace(wid)
        await workspace.touch("/foo.txt")
        foo_id = await workspace.path_id("/foo.txt")
        await workspace.sync_by_id(foo_id)

    path_info = await workspace.path_info("/foo.txt")
    assert path_info["base_version"] == 1

    # Now hack a bit the fs to simulate poor connection with backend

    vanilla_backend_block_create = alice_user_fs._syncer._backend_block_create

    async def mocked_backend_block_create(*args, **kwargs):
        await vanilla_backend_block_create(*args, **kwargs)
        raise BackendNotAvailable()

    alice_user_fs._syncer._backend_block_create = mocked_backend_block_create

    # Write into the file locally and try to sync this.
    # We should end up with a block synced in the backend but still considered
    # as a dirty block in the fs.

    with freeze_time("2000-01-03"):
        await workspace.write_bytes("/foo.txt", b"data")
    with pytest.raises(BackendNotAvailable):
        await workspace.sync_by_id(foo_id)

    # Now retry the sync with a good connection, we should be able to reach
    # eventual consistency.

    alice_user_fs._syncer._backend_block_create = vanilla_backend_block_create
    await workspace.sync_by_id(foo_id)

    # Finally test this so-called consistency ;-)

    data = await workspace.read_bytes("/foo.txt")
    assert data == b"data"
    path_info = await workspace.path_info("/foo.txt")
    assert path_info == {
        "type": "file",
        "id": ANY,
        "is_placeholder": False,
        "need_sync": False,
        "created": datetime(2000, 1, 2),
        "updated": datetime(2000, 1, 3),
        "base_version": 2,
        "size": 4,
        "confinement_point": None,
    }

    await workspace2.sync()
    data2 = await workspace2.read_bytes("/foo.txt")
    assert data2 == b"data"
Example #5
0
async def testbed(running_backend, alice_user_fs, alice, bob):
    with freeze_time("2000-01-01"):
        wid = await alice_user_fs.workspace_create(EntryName("w1"))
        workspace = alice_user_fs.get_workspace(wid)
        await workspace.sync()
        local_manifest = await workspace.local_storage.get_manifest(wid)
    with freeze_time("2000-01-03"):
        await alice_user_fs.workspace_share(wid, bob.user_id,
                                            WorkspaceRole.MANAGER)

    class TestBed:
        def __init__(self):
            self._next_version = 2
            self.defaults = {
                "local_manifest": local_manifest,
                "blob": None,
                "signed_author": alice.device_id,
                "backend_author": alice.device_id,
                "signed_timestamp": datetime(2000, 1, 2),
                "backend_timestamp": datetime(2000, 1, 2),
                "author_signkey": alice.signing_key,
                "key": workspace.get_workspace_entry().key,
            }

        async def run(self, exc_msg, **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["key"])

            await running_backend.backend.vlob.update(
                organization_id=alice.organization_id,
                author=options["backend_author"],
                encryption_revision=1,
                vlob_id=VlobID(wid.uuid),
                version=self._next_version,
                timestamp=options["backend_timestamp"],
                blob=options["blob"],
            )
            self._next_version += 1

            # This should trigger FSError
            with pytest.raises(FSError) as exc:
                await workspace.sync()
            assert str(exc.value) == exc_msg

            # Also test timestamped workspace
            # Note: oxidation doesn't implement WorkspaceStorageTimestamped
            if not IS_OXIDIZED:
                with pytest.raises(FSError) as exc:
                    await workspace.to_timestamped(options["backend_timestamp"]
                                                   )
                assert str(exc.value) == exc_msg

    return TestBed()
Example #6
0
async def test_retrieve_user(running_backend, alice_remote_devices_manager, bob):
    remote_devices_manager = alice_remote_devices_manager
    d1 = datetime(2000, 1, 1)
    with freeze_time(d1):
        # Offline with no cache
        with pytest.raises(RemoteDevicesManagerBackendOfflineError):
            with running_backend.offline():
                await remote_devices_manager.get_user(bob.user_id)

        # Online
        user, revoked_user = await remote_devices_manager.get_user(bob.user_id)
        assert user.user_id == bob.user_id
        assert user.public_key == bob.public_key
        assert revoked_user is None

        # Offline with cache
        with running_backend.offline():
            user2, revoked_user2 = await remote_devices_manager.get_user(bob.user_id)
            assert user2 is user
            assert revoked_user2 is None

    d2 = d1.add(remote_devices_manager.cache_validity + 1)
    with freeze_time(d2):
        # Offline with cache expired
        with pytest.raises(RemoteDevicesManagerBackendOfflineError):
            with running_backend.offline():
                await remote_devices_manager.get_user(bob.user_id)

        # Online with cache expired
        user, revoked_user = await remote_devices_manager.get_user(bob.user_id)
        assert user.user_id == bob.user_id
        assert user.public_key == bob.public_key
        assert revoked_user is None
Example #7
0
async def test_retrieve_device(running_backend, alice_remote_devices_manager, bob):
    remote_devices_manager = alice_remote_devices_manager
    d1 = datetime(2000, 1, 1)
    with freeze_time(d1):
        # Offline with no cache
        with pytest.raises(RemoteDevicesManagerBackendOfflineError):
            with running_backend.offline():
                await remote_devices_manager.get_device(bob.device_id)

        # Online
        device = await remote_devices_manager.get_device(bob.device_id)
        assert device.device_id == bob.device_id
        assert device.verify_key == bob.verify_key

        # Offline with cache
        with running_backend.offline():
            device2 = await remote_devices_manager.get_device(bob.device_id)
            assert device2 is device

    d2 = d1.add(remote_devices_manager.cache_validity + 1)
    with freeze_time(d2):
        # Offline with cache expired
        with pytest.raises(RemoteDevicesManagerBackendOfflineError):
            with running_backend.offline():
                await remote_devices_manager.get_device(bob.device_id)

        # Online with cache expired
        device = await remote_devices_manager.get_device(bob.device_id)
        assert device.device_id == bob.device_id
        assert device.verify_key == bob.verify_key
Example #8
0
async def test_sharing_event_on_sync_if_same_role(
    running_backend, alice_user_fs, alice2_user_fs, bob_user_fs, alice, bob
):
    # Share a workspace, alice2 knows about it
    with freeze_time("2000-01-02"):
        wid = await create_shared_workspace("w", bob_user_fs, alice_user_fs, alice2_user_fs)
    expected_entry_v1 = WorkspaceEntry(
        name="w (shared by bob)",
        id=wid,
        key=ANY,
        encryption_revision=1,
        encrypted_on=Pendulum(2000, 1, 2),
        role_cached_on=Pendulum(2000, 1, 2),
        role=WorkspaceRole.MANAGER,
    )

    # Then change alice's role...
    await bob_user_fs.workspace_share(wid, alice.user_id, WorkspaceRole.OWNER)
    with freeze_time("2000-01-03"):
        await alice_user_fs.process_last_messages()
    await alice_user_fs.sync()

    # ...and give back alice the same role
    await bob_user_fs.workspace_share(wid, alice.user_id, WorkspaceRole.MANAGER)
    with freeze_time("2000-01-04"):
        await alice_user_fs.process_last_messages()
    expected_entry_v3 = expected_entry_v1.evolve(role_cached_on=Pendulum(2000, 1, 4))
    await alice_user_fs.sync()

    # A single sharing event should be triggered
    with alice2_user_fs.event_bus.listen() as spy:
        await alice2_user_fs.sync()
    spy.assert_event_occured(
        "sharing.updated", {"new_entry": expected_entry_v3, "previous_entry": expected_entry_v1}
    )
Example #9
0
async def test_sync(running_backend, alice2_user_fs, alice2):
    with freeze_time("2000-01-02"):
        wid = await alice2_user_fs.workspace_create("w1")

    with freeze_time("2000-01-03"):
        await alice2_user_fs.sync()

    um = alice2_user_fs.get_user_manifest()
    expected_base_um = UserManifest(
        author=alice2.device_id,
        timestamp=datetime(2000, 1, 3),
        id=alice2.user_manifest_id,
        version=2,
        created=datetime(2000, 1, 1),
        updated=datetime(2000, 1, 2),
        last_processed_message=0,
        workspaces=(WorkspaceEntry(
            name="w1",
            id=wid,
            key=ANY,
            encryption_revision=1,
            encrypted_on=datetime(2000, 1, 2),
            role_cached_on=datetime(2000, 1, 2),
            role=WorkspaceRole.OWNER,
        ), ),
    )
    expected_um = LocalUserManifest.from_remote(expected_base_um)
    assert um == expected_um
Example #10
0
async def test_get_role_certificates_multiple(
    backend,
    alice,
    bob,
    adam,
    bob_backend_sock,
    realm,
    backend_realm_generate_certif_and_update_roles,
):
    # Realm is created on 2000-01-02

    with freeze_time("2000-01-03"):
        c3 = await backend_realm_generate_certif_and_update_roles(
            backend, alice, realm, bob.user_id, RealmRole.OWNER)

    with freeze_time("2000-01-04"):
        c4 = await backend_realm_generate_certif_and_update_roles(
            backend, bob, realm, adam.user_id, RealmRole.MANAGER)

    with freeze_time("2000-01-05"):
        c5 = await backend_realm_generate_certif_and_update_roles(
            backend, bob, realm, alice.user_id, RealmRole.READER)

    with freeze_time("2000-01-06"):
        c6 = await backend_realm_generate_certif_and_update_roles(
            backend, bob, realm, alice.user_id, None)

    rep = await realm_get_role_certificates(bob_backend_sock, realm)
    assert rep == {"status": "ok", "certificates": [ANY, c3, c4, c5, c6]}
Example #11
0
async def test_update_invalid_timestamp(running_backend, alice_user_fs,
                                        alice2_user_fs):
    with freeze_time("2000-01-01"):
        wid = await create_shared_workspace(EntryName("w"), alice_user_fs,
                                            alice2_user_fs)
    workspace = alice_user_fs.get_workspace(wid)
    await workspace.touch("/foo.txt")
    with freeze_time("2000-01-02") as t2:
        await workspace.sync()
    await workspace.write_bytes("/foo.txt", b"ok")
    with freeze_time("2000-01-03") as t3:
        await workspace.sync()
    await workspace.write_bytes("/foo.txt", b"ko")
    with freeze_time(t2):
        with pytest.raises(FSRemoteOperationError) as context:
            await workspace.sync()
        cause = context.value.__cause__
        assert isinstance(cause, BackendOutOfBallparkError)
        rep, = cause.args
        assert rep == {
            "status": "bad_timestamp",
            "client_timestamp": t3.add(microseconds=MANIFEST_STAMP_AHEAD_US),
            "backend_timestamp": t2,
            "ballpark_client_early_offset": BALLPARK_CLIENT_EARLY_OFFSET,
            "ballpark_client_late_offset": BALLPARK_CLIENT_LATE_OFFSET,
        }
async def test_online_sync2(autojump_clock, running_backend, client_factory,
                            alice, alice2):
    # Given the clients are initialized while the backend is online, we are
    # guaranteed they are connected
    async with client_factory() as alice_client, client_factory(
    ) as alice2_client2:
        await alice_client.login(alice)
        await alice2_client2.login(alice2)

        # FS does a full sync at startup, wait for it to finish
        await alice_client.user_fs.wait_not_syncing()
        await alice2_client2.user_fs.wait_not_syncing()

        async with wait_for_entries_synced(alice2_client2,
                                           ["/"]), wait_for_entries_synced(
                                               alice_client,
                                               ("/", "/foo.txt")):

            with freeze_time("2000-01-02"):
                await alice_client.user_fs.touch("/foo.txt")

            with freeze_time("2000-01-03"):
                await alice_client.user_fs.file_write("/foo.txt",
                                                      b"hello world !")

            await alice_client.user_fs.sync("/foo.txt")

        stat = await alice_client.user_fs.stat("/foo.txt")
        stat2 = await alice2_client2.user_fs.stat("/foo.txt")
        assert stat2 == stat
Example #13
0
async def test_rename_workspace(initial_user_manifest_state, alice_user_fs,
                                alice):
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")

    with freeze_time("2000-01-03"):
        await alice_user_fs.workspace_rename(wid, "w2")

    um = alice_user_fs.get_user_manifest()
    expected_base_um = initial_user_manifest_state.get_user_manifest_v1_for_backend(
        alice)
    expected_um = LocalUserManifest(
        base=expected_base_um,
        need_sync=True,
        updated=datetime(2000, 1, 3),
        last_processed_message=expected_base_um.last_processed_message,
        workspaces=(WorkspaceEntry(
            name="w2",
            id=wid,
            key=ANY,
            encryption_revision=1,
            encrypted_on=datetime(2000, 1, 2),
            role_cached_on=datetime(2000, 1, 2),
            role=WorkspaceRole.OWNER,
        ), ),
    )
    assert um == expected_um
Example #14
0
async def test_reshare_workspace(running_backend, alice_user_fs, bob_user_fs,
                                 alice, bob):
    # Share a workspace...
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")
    await alice_user_fs.workspace_share(wid, bob.user_id,
                                        WorkspaceRole.MANAGER)
    with freeze_time("2000-01-03"):
        await bob_user_fs.process_last_messages()

    # ...and unshare it...
    await alice_user_fs.workspace_share(wid, bob.user_id, None)
    with freeze_time("2000-01-04"):
        await bob_user_fs.process_last_messages()

    # ...and re-share it !
    await alice_user_fs.workspace_share(wid, bob.user_id,
                                        WorkspaceRole.MANAGER)
    with bob_user_fs.event_bus.listen() as spy:
        with freeze_time("2000-01-05"):
            await bob_user_fs.process_last_messages()
    spy.assert_event_occured(
        CoreEvent.SHARING_UPDATED,
        {
            "new_entry":
            WorkspaceEntry(
                name="w1",
                id=wid,
                key=ANY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 2),
                role_cached_on=datetime(2000, 1, 5),
                role=WorkspaceRole.MANAGER,
            ),
            "previous_entry":
            WorkspaceEntry(
                name="w1",
                id=wid,
                key=ANY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 2),
                role_cached_on=datetime(2000, 1, 4),
                role=None,
            ),
        },
    )

    # Check access
    aum = alice_user_fs.get_user_manifest()
    bum = bob_user_fs.get_user_manifest()
    assert len(aum.workspaces) == 1
    assert len(bum.workspaces) == 1
    aw = aum.workspaces[0]
    bw = bum.workspaces[0]

    assert bw.name == "w1"
    assert bw.id == aw.id
    assert bw.role == WorkspaceRole.MANAGER
async def test_fast_forward_on_offline_during_sync(autojump_clock,
                                                   server_factory, backend,
                                                   client_factory, alice,
                                                   alice2):
    # Create two servers to be able to turn offline a single one
    async with server_factory(
            backend.handle_client) as server1, server_factory(
                backend.handle_client) as server2:

        # Given the clients are initialized while the backend is online, we are
        # guaranteed they are connected
        async with client_factory(
                config={"backend_addr": server1.addr}
        ) as alice_client, client_factory(
                config={"backend_addr": server2.addr}) as alice2_client2:
            await alice_client.login(alice)
            await alice2_client2.login(alice2)

            # TODO: shouldn't need this...
            await trio.testing.wait_all_tasks_blocked(cushion=0.1)

            async with wait_for_entries_synced(alice2_client2,
                                               ["/"]), wait_for_entries_synced(
                                                   alice_client,
                                                   ("/", "/foo.txt")):
                with freeze_time("2000-01-02"):
                    await alice_client.user_fs.touch("/foo.txt")

                with freeze_time("2000-01-03"):
                    await alice_client.user_fs.file_write("/foo.txt", b"v1")

                # Sync should be done in the background by the sync monitor
                ########### shouldn't need to do that... #######
                await alice_client.user_fs.sync("/foo.txt")

            # TODO: shouldn't need this...
            await trio.testing.wait_all_tasks_blocked(cushion=0.1)

            # client goes offline, other client2 is still connected to backend
            async with wait_for_entries_synced(alice_client,
                                               ("/", "/foo.txt")):
                stat2 = await alice2_client2.user_fs.stat("/foo.txt")
                with offline(server1.addr):

                    with freeze_time("2000-01-04"):
                        await alice2_client2.user_fs.file_write(
                            "/foo.txt", b"v2")
                        await alice2_client2.user_fs.folder_create("/bar")

                    async with wait_for_entries_synced(
                            alice2_client2, ("/", "/bar", "/foo.txt")):
                        await alice2_client2.user_fs.sync()

            for path in ("/", "/bar", "/foo.txt"):
                stat = await alice_client.user_fs.stat(path)
                stat2 = await alice2_client2.user_fs.stat(path)
                assert stat2 == stat
Example #16
0
async def test_create_certif_too_old(alice, alice_backend_sock):
    now = pendulum.now()

    # Generate a certificate

    realm_id = RealmID.from_hex("C0000000000000000000000000000000")
    certif = RealmRoleCertificateContent.build_realm_root_certif(
        author=alice.device_id, timestamp=now,
        realm_id=realm_id).dump_and_sign(alice.signing_key)

    # Create a realm a tiny bit too late

    later = now.add(seconds=BALLPARK_CLIENT_LATE_OFFSET)
    with freeze_time(later):
        rep = await realm_create(alice_backend_sock, certif)
    assert rep == {
        "status": "bad_timestamp",
        "backend_timestamp": later,
        "ballpark_client_early_offset": BALLPARK_CLIENT_EARLY_OFFSET,
        "ballpark_client_late_offset": BALLPARK_CLIENT_LATE_OFFSET,
        "client_timestamp": now,
    }

    #  Create a realm late but right before the deadline

    later = now.add(seconds=BALLPARK_CLIENT_LATE_OFFSET, microseconds=-1)
    with freeze_time(later):
        rep = await realm_create(alice_backend_sock, certif)
    assert rep["status"] == "ok"

    # Generate a new certificate

    realm_id = RealmID.from_hex("C0000000000000000000000000000001")
    certif = RealmRoleCertificateContent.build_realm_root_certif(
        author=alice.device_id, timestamp=now,
        realm_id=realm_id).dump_and_sign(alice.signing_key)

    # Create a realm a tiny bit too soon

    sooner = now.subtract(seconds=BALLPARK_CLIENT_EARLY_OFFSET)
    with freeze_time(sooner):
        rep = await realm_create(alice_backend_sock, certif)
    assert rep == {
        "status": "bad_timestamp",
        "backend_timestamp": sooner,
        "ballpark_client_early_offset": BALLPARK_CLIENT_EARLY_OFFSET,
        "ballpark_client_late_offset": BALLPARK_CLIENT_LATE_OFFSET,
        "client_timestamp": now,
    }

    # Create a realm soon but after the limit

    sooner = now.subtract(seconds=BALLPARK_CLIENT_EARLY_OFFSET,
                          microseconds=-1)
    with freeze_time(sooner):
        rep = await realm_create(alice_backend_sock, certif)
    assert rep["status"] == "ok"
Example #17
0
async def test_remove_role_idempotent(
    alice,
    bob,
    alice_backend_sock,
    realm,
    start_with_existing_role,
    realm_generate_certif_and_update_roles_or_fail,
):
    if start_with_existing_role:
        with freeze_time("2000-01-03"):
            rep = await realm_generate_certif_and_update_roles_or_fail(
                alice_backend_sock, alice, realm, bob.user_id,
                RealmRole.MANAGER)
            assert rep == {"status": "ok"}

    with freeze_time("2000-01-04"):
        rep = await realm_generate_certif_and_update_roles_or_fail(
            alice_backend_sock, alice, realm, bob.user_id, None)
        if start_with_existing_role:
            assert rep == {"status": "ok"}
        else:
            assert rep == {"status": "already_granted"}

    with freeze_time("2000-01-05"):
        rep = await realm_generate_certif_and_update_roles_or_fail(
            alice_backend_sock, alice, realm, bob.user_id, None)
        assert rep == {"status": "already_granted"}

    certifs = await _realm_get_clear_role_certifs(alice_backend_sock, realm)
    expected_certifs = [
        RealmRoleCertificateContent(
            author=alice.device_id,
            timestamp=datetime(2000, 1, 2),
            realm_id=realm,
            user_id=alice.user_id,
            role=RealmRole.OWNER,
        )
    ]
    if start_with_existing_role:
        expected_certifs += [
            RealmRoleCertificateContent(
                author=alice.device_id,
                timestamp=datetime(2000, 1, 3),
                realm_id=realm,
                user_id=bob.user_id,
                role=RealmRole.MANAGER,
            ),
            RealmRoleCertificateContent(
                author=alice.device_id,
                timestamp=datetime(2000, 1, 4),
                realm_id=realm,
                user_id=bob.user_id,
                role=None,
            ),
        ]
    assert certifs == expected_certifs
Example #18
0
async def test_sync_under_concurrency(running_backend, alice_user_fs,
                                      alice2_user_fs, alice, alice2):
    with freeze_time("2000-01-02"):
        waid = await alice_user_fs.workspace_create(EntryName("wa"))

    with freeze_time("2000-01-03"):
        wa2id = await alice2_user_fs.workspace_create(EntryName("wa2"))

    with freeze_time("2000-01-04"):
        await alice_user_fs.sync()
    with freeze_time("2000-01-05"):
        await alice2_user_fs.sync()
    # Fetch back alice2's changes
    with freeze_time("2000-01-06"):
        await alice_user_fs.sync()

    um = alice_user_fs.get_user_manifest()
    um2 = alice2_user_fs.get_user_manifest()

    expected_base_um = UserManifest(
        author=alice2.device_id,
        timestamp=datetime(2000, 1, 5),
        id=alice2.user_manifest_id,
        version=3,
        created=datetime(2000, 1, 1),
        updated=datetime(2000, 1, 3),
        last_processed_message=0,
        workspaces=(
            WorkspaceEntry(
                name=EntryName("wa"),
                id=waid,
                key=KEY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 2),
                role_cached_on=datetime(2000, 1, 2),
                role=WorkspaceRole.OWNER,
            ),
            WorkspaceEntry(
                name=EntryName("wa2"),
                id=wa2id,
                key=KEY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 3),
                role_cached_on=datetime(2000, 1, 3),
                role=WorkspaceRole.OWNER,
            ),
        ),
    )
    expected_um = LocalUserManifest.from_remote(expected_base_um)

    um = _update_user_manifest_key(um)
    um2 = _update_user_manifest_key(um2)

    assert um == expected_um
    assert um2 == expected_um
Example #19
0
async def test_sync_placeholder(
    running_backend, backend_data_binder, local_device_factory, user_fs_factory, with_workspace
):
    device = local_device_factory()
    await backend_data_binder.bind_device(device, initial_user_manifest_in_v0=True)

    async with user_fs_factory(device, initialize_in_v0=True) as user_fs:
        um_v0 = user_fs.get_user_manifest()

        expected_um = LocalUserManifest.new_placeholder(
            id=device.user_manifest_id, now=um_v0.created
        )
        assert um_v0 == expected_um

        if with_workspace:
            with freeze_time("2000-01-02"):
                wid = await user_fs.workspace_create("w1")
            um = user_fs.get_user_manifest()
            expected_um = um_v0.evolve(
                updated=Pendulum(2000, 1, 2),
                workspaces=(
                    WorkspaceEntry(
                        name="w1",
                        id=wid,
                        key=ANY,
                        encryption_revision=1,
                        encrypted_on=Pendulum(2000, 1, 2),
                        role_cached_on=Pendulum(2000, 1, 2),
                        role=WorkspaceRole.OWNER,
                    ),
                ),
            )
            assert um == expected_um

        with freeze_time("2000-01-02"):
            await user_fs.sync()
        um = user_fs.get_user_manifest()
        expected_base_um = UserManifest(
            author=device.device_id,
            timestamp=Pendulum(2000, 1, 2),
            id=device.user_manifest_id,
            version=1,
            created=expected_um.created,
            updated=expected_um.updated,
            last_processed_message=0,
            workspaces=expected_um.workspaces,
        )
        expected_um = LocalUserManifest(
            base=expected_base_um,
            need_sync=False,
            updated=expected_um.updated,
            last_processed_message=0,
            workspaces=expected_base_um.workspaces,
        )
        assert um == expected_um
Example #20
0
async def test_new_empty_entry(type, running_backend, alice_user_fs, alice2_user_fs):
    with freeze_time("2000-01-01"):
        wid = await create_shared_workspace("w", alice_user_fs, alice2_user_fs)
    workspace = alice_user_fs.get_workspace(wid)
    with freeze_time("2000-01-02"):
        if type == "file":
            await workspace.touch("/foo")
        else:
            await workspace.mkdir("/foo")

    info = await workspace.path_info("/foo")
    fid = info["id"]
    with alice_user_fs.event_bus.listen() as spy:
        with freeze_time("2000-01-03"):
            await workspace.sync()

    if type == "file":  # TODO: file and folder should generate the same events after the migration
        expected_events = [
            ("fs.entry.synced", {"workspace_id": wid, "id": wid}, Pendulum(2000, 1, 3))
        ]
    else:
        expected_events = [
            ("fs.entry.synced", {"workspace_id": wid, "id": fid}, Pendulum(2000, 1, 3)),
            ("fs.entry.synced", {"workspace_id": wid, "id": wid}, Pendulum(2000, 1, 3)),
        ]
    spy.assert_events_occured(expected_events)

    workspace2 = alice2_user_fs.get_workspace(wid)
    await workspace2.sync()

    info = await workspace.path_info("/foo")
    if type == "file":
        assert info == {
            "type": "file",
            "id": ANY,
            "is_placeholder": False,
            "need_sync": False,
            "base_version": 1,
            "created": Pendulum(2000, 1, 2),
            "updated": Pendulum(2000, 1, 2),
            "size": 0,
        }
    else:
        assert info == {
            "type": "folder",
            "id": ANY,
            "is_placeholder": False,
            "need_sync": False,
            "base_version": 1,
            "created": Pendulum(2000, 1, 2),
            "updated": Pendulum(2000, 1, 2),
            "children": [],
        }
    info2 = await workspace2.path_info("/foo")
    assert info == info2
Example #21
0
async def test_reloading_v0_user_manifest(running_backend, backend_data_binder,
                                          local_storage_factory,
                                          user_fs_factory, coolorg, alice):
    # Initialize backend and local storage
    with freeze_time("2000-01-01"):
        await backend_data_binder.bind_organization(
            coolorg, alice, initial_user_manifest_in_v0=True)
    local_storage = await local_storage_factory(alice,
                                                user_manifest_in_v0=True)

    # Create a workspace without syncronizing
    async with user_fs_factory(alice, local_storage) as user_fs:
        with freeze_time("2000-01-02"):
            wid = await user_fs.workspace_create("foo")
            workspace = user_fs.get_workspace(wid)

    await local_storage.clear_memory_cache()

    # Reload version 0 manifest
    async with user_fs_factory(alice, local_storage) as user_fs:
        with freeze_time("2000-01-02"):
            path_info = await workspace.path_info("/")

        assert path_info == {
            "type": "root",
            "id": alice.user_manifest_id,
            "created": datetime(2000, 1, 2),
            "updated": datetime(2000, 1, 2),
            "base_version": 0,
            "is_folder": True,
            "is_placeholder": True,
            "need_sync": True,
            "children": ["foo"],
        }

    await local_storage.clear_memory_cache()

    # Syncronize version 0 manifest
    async with user_fs_factory(alice, local_storage) as user_fs:
        with freeze_time("2000-01-03"):
            await user_fs.sync()

        path_info = await workspace.path_info("/")
        assert path_info == {
            "type": "root",
            "id": ANY,
            "created": datetime(2000, 1, 2),
            "updated": datetime(2000, 1, 2),
            "base_version": 1,
            "is_folder": True,
            "is_placeholder": False,
            "need_sync": False,
            "children": ["foo"],
        }
Example #22
0
async def test_create_workspace_same_name(alice_user_fs):
    with freeze_time("2000-01-02"):
        w1id = await alice_user_fs.workspace_create("w")

    with freeze_time("2000-01-03"):
        w2id = await alice_user_fs.workspace_create("w")

    um = alice_user_fs.get_user_manifest()
    assert um.updated == Pendulum(2000, 1, 3)
    assert len(um.workspaces) == 2
    assert [(x.id, x.name) for x in um.workspaces] == [(w1id, "w"), (w2id, "w")]
Example #23
0
async def test_unshare_ok(running_backend, alice_user_fs, bob_user_fs, alice,
                          bob):
    # Share a workspace...
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create(EntryName("w1"))
    await alice_user_fs.workspace_share(wid, bob.user_id, WorkspaceRole.OWNER)
    await bob_user_fs.process_last_messages()

    # ...and unshare it
    await bob_user_fs.workspace_share(wid, alice.user_id, None)
    with alice_user_fs.event_bus.listen() as spy:
        with freeze_time("2000-01-03"):
            await alice_user_fs.process_last_messages()

    new_events = []
    for event in spy.events:
        if event.event == CoreEvent.SHARING_UPDATED:
            event.kwargs["new_entry"] = event.kwargs["new_entry"].evolve(
                key=KEY)
            event.kwargs["previous_entry"] = event.kwargs[
                "previous_entry"].evolve(key=KEY)
        new_events.append(event)
    spy.events = new_events

    spy.assert_event_occured(
        CoreEvent.SHARING_UPDATED,
        {
            "new_entry":
            WorkspaceEntry(
                name=EntryName("w1"),
                id=wid,
                key=KEY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 2),
                role_cached_on=datetime(2000, 1, 3),
                role=None,
            ),
            "previous_entry":
            WorkspaceEntry(
                name=EntryName("w1"),
                id=wid,
                key=KEY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 2),
                role_cached_on=datetime(2000, 1, 2),
                role=WorkspaceRole.OWNER,
            ),
        },
    )

    aum = alice_user_fs.get_user_manifest()
    aw = aum.workspaces[0]
    assert not aw.role
Example #24
0
async def test_sharing_event_on_sync_if_same_role(running_backend,
                                                  alice_user_fs,
                                                  alice2_user_fs, bob_user_fs,
                                                  alice, bob):
    # Share a workspace, alice2 knows about it
    with freeze_time("2000-01-02"):
        wid = await create_shared_workspace(EntryName("w"), bob_user_fs,
                                            alice_user_fs, alice2_user_fs)
    expected_entry_v1 = WorkspaceEntry(
        name=EntryName("w"),
        id=wid,
        key=KEY,
        encryption_revision=1,
        encrypted_on=datetime(2000, 1, 2),
        role_cached_on=datetime(2000, 1, 2),
        role=WorkspaceRole.MANAGER,
    )

    # Then change alice's role...
    await bob_user_fs.workspace_share(wid, alice.user_id, WorkspaceRole.OWNER)
    with freeze_time("2000-01-03"):
        await alice_user_fs.process_last_messages()
    await alice_user_fs.sync()

    # ...and give back alice the same role
    await bob_user_fs.workspace_share(wid, alice.user_id,
                                      WorkspaceRole.MANAGER)
    with freeze_time("2000-01-04"):
        await alice_user_fs.process_last_messages()
    expected_entry_v3 = expected_entry_v1.evolve(
        role_cached_on=datetime(2000, 1, 4))
    await alice_user_fs.sync()

    # A single sharing event should be triggered
    with alice2_user_fs.event_bus.listen() as spy:
        await alice2_user_fs.sync()

    new_events = []
    for event in spy.events:
        if event.event == CoreEvent.SHARING_UPDATED:
            event.kwargs["new_entry"] = event.kwargs["new_entry"].evolve(
                key=KEY)
            event.kwargs["previous_entry"] = event.kwargs[
                "previous_entry"].evolve(key=KEY)
        new_events.append(event)
    spy.events = new_events
    spy.assert_event_occured(
        CoreEvent.SHARING_UPDATED,
        {
            "new_entry": expected_entry_v3,
            "previous_entry": expected_entry_v1
        },
    )
Example #25
0
async def test_create_workspace_same_name(alice_user_fs):
    with freeze_time("2000-01-02"):
        w1id = await alice_user_fs.workspace_create(EntryName("w"))

    with freeze_time("2000-01-03"):
        w2id = await alice_user_fs.workspace_create(EntryName("w"))

    um = alice_user_fs.get_user_manifest()
    assert um.updated == datetime(2000, 1, 3)
    assert len(um.workspaces) == 2
    assert sorted((x.id, x.name)
                  for x in um.workspaces) == sorted([(w1id, EntryName("w")),
                                                     (w2id, EntryName("w"))])
Example #26
0
async def test_user_manifest_access_while_speculative(user_fs_factory, alice):
    with freeze_time("2000-01-01"):
        async with user_fs_factory(alice) as user_fs:
            with freeze_time("2000-01-02"):
                user_manifest = user_fs.get_user_manifest()

    assert user_manifest.to_stats() == {
        "id": alice.user_manifest_id,
        "base_version": 0,
        "created": datetime(2000, 1, 1),
        "updated": datetime(2000, 1, 1),
        "is_placeholder": True,
        "need_sync": True,
    }
Example #27
0
async def test_update_invalid_timestamp(running_backend, alice_user_fs, alice2_user_fs):
    with freeze_time("2000-01-01"):
        wid = await create_shared_workspace("w", alice_user_fs, alice2_user_fs)
    workspace = alice_user_fs.get_workspace(wid)
    await workspace.touch("/foo.txt")
    with freeze_time("2000-01-01"):
        await workspace.sync()
    await workspace.write_bytes("/foo.txt", b"ok")
    with freeze_time("2000-01-03"):
        await workspace.sync()
    await workspace.write_bytes("/foo.txt", b"ko")
    with freeze_time("2000-01-02"):
        with pytest.raises(FSBackendOfflineError):
            await workspace.sync()
Example #28
0
async def test_concurrent_devices_agree_on_workspace_manifest(
        running_backend, user_fs_factory, data_base_dir,
        initialize_local_user_manifest, alice, alice2):
    await initialize_local_user_manifest(data_base_dir,
                                         alice,
                                         initial_user_manifest="v1")
    await initialize_local_user_manifest(data_base_dir,
                                         alice2,
                                         initial_user_manifest="v1")

    async with user_fs_factory(alice) as alice_user_fs:
        async with user_fs_factory(alice2) as alice2_user_fs:
            with freeze_time("2000-01-01"):
                wksp_id = await alice_user_fs.workspace_create(
                    EntryName("wksp"))
            # Sync user manifest (containing the workspace entry), but
            # not the corresponding workspace manifest !
            with freeze_time("2000-01-02"):
                await alice_user_fs.sync()

            # Retrieve the user manifest but not the workpace manifest, Alice2 hence has a speculative workspace manifest
            with freeze_time("2000-01-03"):
                await alice2_user_fs.sync()

            # Now workspace diverge between devices
            alice_wksp = alice_user_fs.get_workspace(wksp_id)
            alice2_wksp = alice2_user_fs.get_workspace(wksp_id)
            with freeze_time("2000-01-04"):
                await alice_wksp.mkdir("/from_alice")
            with freeze_time("2000-01-05"):
                await alice2_wksp.mkdir("/from_alice2")

            # Sync user_fs2 first to ensure created_on field is
            # kept even if further syncs have an earlier value
            with freeze_time("2000-01-06"):
                await alice2_wksp.sync()
            with freeze_time("2000-01-07"):
                await alice_wksp.sync()
            with freeze_time("2000-01-08"):
                await alice2_wksp.sync()

            # Now, both user fs should have the same view on workspace
            expected_alice_wksp_stat = {
                "id": wksp_id,
                "base_version": 3,
                "created": datetime(2000, 1, 1),
                "updated": datetime(2000, 1, 7),
                "is_placeholder": False,
                "need_sync": False,
                "type": "folder",
                "children":
                [EntryName("from_alice"),
                 EntryName("from_alice2")],
                "confinement_point": None,
            }
            alice_wksp_stat = await alice_wksp.path_info("/")
            alice2_wksp_stat = await alice2_wksp.path_info("/")
            assert alice_wksp_stat == expected_alice_wksp_stat
            assert alice2_wksp_stat == expected_alice_wksp_stat
Example #29
0
async def test_get_role_certificates_no_longer_allowed(
        backend, alice, bob, alice_backend_sock, realm,
        backend_realm_generate_certif_and_update_roles):
    # Realm is created on 2000-01-02

    with freeze_time("2000-01-03"):
        await backend_realm_generate_certif_and_update_roles(
            backend, alice, realm, bob.user_id, RealmRole.OWNER)

    with freeze_time("2000-01-04"):
        await backend_realm_generate_certif_and_update_roles(
            backend, bob, realm, alice.user_id, None)

    rep = await realm_get_role_certificates(alice_backend_sock, realm)
    assert rep == {"status": "not_allowed"}
Example #30
0
async def test_share_ok(running_backend, alice_user_fs, bob_user_fs, alice,
                        bob, presynced):
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")

    if presynced:
        await alice_user_fs.sync()

    await alice_user_fs.workspace_share(wid, bob.user_id,
                                        WorkspaceRole.MANAGER)

    with bob_user_fs.event_bus.listen() as spy:
        with freeze_time("2000-01-03"):
            await bob_user_fs.process_last_messages()
    spy.assert_event_occured(
        CoreEvent.SHARING_UPDATED,
        {
            "new_entry":
            WorkspaceEntry(
                name="w1",
                id=wid,
                key=ANY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 2),
                role_cached_on=datetime(2000, 1, 3),
                role=WorkspaceRole.MANAGER,
            ),
            "previous_entry":
            None,
        },
    )

    aum = alice_user_fs.get_user_manifest()
    bum = bob_user_fs.get_user_manifest()
    assert len(aum.workspaces) == 1
    assert len(bum.workspaces) == 1
    awe = aum.get_workspace_entry(wid)
    bwe = bum.get_workspace_entry(wid)

    assert bwe.name == "w1"
    assert bwe.id == awe.id
    assert bwe.role == WorkspaceRole.MANAGER

    aw = alice_user_fs.get_workspace(wid)
    bw = bob_user_fs.get_workspace(wid)
    aw_stat = await aw.path_info("/")
    bw_stat = await bw.path_info("/")
    assert aw_stat == bw_stat