Ejemplo n.º 1
0
async def _get_user(conn, organization_id: OrganizationID,
                    user_id: UserID) -> User:
    row = await conn.fetchrow(
        *_q_get_user(organization_id=organization_id.str, user_id=user_id.str))
    if not row:
        raise UserNotFoundError(user_id)

    human_handle = None
    if row["human_email"]:
        human_handle = HumanHandle(email=row["human_email"],
                                   label=row["human_label"])

    return User(
        user_id=user_id,
        human_handle=human_handle,
        profile=UserProfile(row["profile"]),
        user_certificate=row["user_certificate"],
        redacted_user_certificate=row["redacted_user_certificate"],
        user_certifier=DeviceID(row["user_certifier"])
        if row["user_certifier"] else None,
        created_on=row["created_on"],
        revoked_on=row["revoked_on"],
        revoked_user_certificate=row["revoked_user_certificate"],
        revoked_user_certifier=DeviceID(row["revoked_user_certifier"])
        if row["revoked_user_certifier"] else None,
    )
Ejemplo n.º 2
0
async def test_user_claim_ok(monkeypatch, backend, anonymous_backend_sock,
                             coolorg, alice, mallory_invitation):
    user_invitation_retreived = trio.Event()

    vanilla_claim_user_invitation = backend.user.claim_user_invitation

    async def _mocked_claim_user_invitation(*args, **kwargs):
        ret = await vanilla_claim_user_invitation(*args, **kwargs)
        user_invitation_retreived.set()
        return ret

    monkeypatch.setattr(backend.user, "claim_user_invitation",
                        _mocked_claim_user_invitation)

    with freeze_time(mallory_invitation.created_on):
        async with user_claim(
                anonymous_backend_sock,
                invited_user_id=mallory_invitation.user_id,
                encrypted_claim=b"<foo>",
        ) as prep:

            # `backend.user.create_user` will destroy the user invitation,
            # so make sure we retreived it before
            await user_invitation_retreived.wait()

            # No the user we are waiting for
            await backend.user.create_user(
                alice.organization_id,
                User(
                    user_id=UserID("dummy"),
                    user_certificate=b"<user certif>",
                    user_certifier=alice.device_id,
                ),
                Device(
                    device_id=DeviceID("dummy@pc1"),
                    device_certificate=b"<device certif>",
                    device_certifier=alice.device_id,
                ),
            )

            await backend.user.create_user(
                alice.organization_id,
                User(
                    user_id=mallory_invitation.user_id,
                    user_certificate=b"<user certif>",
                    user_certifier=alice.device_id,
                ),
                Device(
                    device_id=DeviceID(f"{mallory_invitation.user_id}@pc1"),
                    device_certificate=b"<device certif>",
                    device_certifier=alice.device_id,
                ),
            )

    assert prep[0] == {
        "status": "ok",
        "user_certificate": b"<user certif>",
        "device_certificate": b"<device certif>",
    }
Ejemplo n.º 3
0
def test_merge_folder_manifests():
    my_device = DeviceID("b@b")
    other_device = DeviceID("a@a")
    parent = EntryID()
    v1 = LocalFolderManifest.new_placeholder(
        my_device, parent=parent).to_remote(author=other_device)

    # Initial base manifest
    m1 = LocalFolderManifest.from_remote(v1, empty_pattern)
    assert merge_manifests(my_device, empty_pattern, m1) == m1

    # Local change
    m2 = m1.evolve_children_and_mark_updated({"a": EntryID()}, empty_pattern)
    assert merge_manifests(my_device, empty_pattern, m2) == m2

    # Successful upload
    v2 = m2.to_remote(author=my_device)
    m3 = merge_manifests(my_device, empty_pattern, m2, v2)
    assert m3 == LocalFolderManifest.from_remote(v2, empty_pattern)

    # Two local changes
    m4 = m3.evolve_children_and_mark_updated({"b": EntryID()}, empty_pattern)
    assert merge_manifests(my_device, empty_pattern, m4) == m4
    m5 = m4.evolve_children_and_mark_updated({"c": EntryID()}, empty_pattern)
    assert merge_manifests(my_device, empty_pattern, m4) == m4

    # M4 has been successfully uploaded
    v3 = m4.to_remote(author=my_device)
    m6 = merge_manifests(my_device, empty_pattern, m5, v3)
    assert m6 == m5.evolve(base=v3)

    # The remote has changed
    v4 = v3.evolve(version=4,
                   children={
                       "d": EntryID(),
                       **v3.children
                   },
                   author=other_device)
    m7 = merge_manifests(my_device, empty_pattern, m6, v4)
    assert m7.base_version == 4
    assert sorted(m7.children) == ["a", "b", "c", "d"]
    assert m7.need_sync

    # Successful upload
    v5 = m7.to_remote(author=my_device)
    m8 = merge_manifests(my_device, empty_pattern, m7, v5)
    assert m8 == LocalFolderManifest.from_remote(v5, empty_pattern)

    # The remote has changed
    v6 = v5.evolve(version=6,
                   children={
                       "e": EntryID(),
                       **v5.children
                   },
                   author=other_device)
    m9 = merge_manifests(my_device, empty_pattern, m8, v6)
    assert m9 == LocalFolderManifest.from_remote(v6, empty_pattern)
Ejemplo n.º 4
0
def _unslug(val):
    parts = val.split(":")
    if len(parts) == 1:
        return (None, DeviceID(val), val)
    elif len(parts) == 2:
        raw_org, raw_device_id = parts
        return (OrganizationID(raw_org), DeviceID(raw_device_id), val)
    else:
        raise ValueError(
            "Must follow format `[<organization>:]<user_id>@<device_name>`")
Ejemplo n.º 5
0
def test_device_certificate():
    from parsec.api.data.certif import (
        _RsDeviceCertificateContent,
        DeviceCertificateContent,
        _PyDeviceCertificateContent,
    )

    assert DeviceCertificateContent is _RsDeviceCertificateContent

    def _assert_device_certificate_eq(py, rs):
        assert py.author == rs.author
        assert py.timestamp == rs.timestamp
        assert py.device_id == rs.device_id
        assert py.device_label == rs.device_label
        assert py.verify_key == rs.verify_key

    kwargs = {
        "author": DeviceID.new(),
        "timestamp": pendulum.now(),
        "device_id": DeviceID("bob@dev1"),
        "device_label": DeviceLabel("dev machine"),
        "verify_key": SigningKey.generate().verify_key,
    }

    py_dc = _PyDeviceCertificateContent(**kwargs)
    rs_dc = DeviceCertificateContent(**kwargs)
    _assert_device_certificate_eq(py_dc, rs_dc)

    kwargs = {
        "author": DeviceID.new(),
        "timestamp": pendulum.now(),
        "device_id": DeviceID("alice@dev1"),
        "device_label": None,
        "verify_key": SigningKey.generate().verify_key,
    }

    py_dc = py_dc.evolve(**kwargs)
    rs_dc = rs_dc.evolve(**kwargs)
    _assert_device_certificate_eq(py_dc, rs_dc)

    sign_key = SigningKey.generate()
    py_data = py_dc.dump_and_sign(sign_key)
    rs_data = rs_dc.dump_and_sign(sign_key)

    py_dc = _PyDeviceCertificateContent.verify_and_load(
        rs_data, sign_key.verify_key, expected_author=py_dc.author, expected_device=py_dc.device_id
    )
    rs_dc = DeviceCertificateContent.verify_and_load(
        py_data, sign_key.verify_key, expected_author=rs_dc.author, expected_device=rs_dc.device_id
    )
    _assert_device_certificate_eq(py_dc, rs_dc)

    py_dc = _PyDeviceCertificateContent.unsecure_load(rs_data)
    rs_dc = DeviceCertificateContent.unsecure_load(py_data)
    _assert_device_certificate_eq(py_dc, rs_dc)
Ejemplo n.º 6
0
async def _get_device_invitation(conn, organization_id: OrganizationID,
                                 device_id: DeviceID) -> DeviceInvitation:
    if await _device_exists(conn, organization_id, device_id):
        raise UserAlreadyExistsError(f"Device `{device_id}` already exists")

    result = await conn.fetchrow(_q_get_invitation, organization_id, device_id)
    if not result:
        raise UserNotFoundError(device_id)

    return DeviceInvitation(device_id=DeviceID(result[0]),
                            creator=DeviceID(result[1]),
                            created_on=result[2])
Ejemplo n.º 7
0
def test_folder_manifest():
    from parsec.api.data.manifest import _RsFolderManifest, FolderManifest, _PyFolderManifest

    assert FolderManifest is _RsFolderManifest

    def _assert_folder_manifest_eq(py, rs):
        assert isinstance(py, _PyFolderManifest)
        assert isinstance(rs, _RsFolderManifest)

        assert py.author == rs.author
        assert py.parent == rs.parent
        assert py.id == rs.id
        assert py.version == rs.version
        assert py.timestamp == rs.timestamp
        assert py.created == rs.created
        assert py.updated == rs.updated
        assert py.children == rs.children

    kwargs = {
        "author": DeviceID("user@device"),
        "id": EntryID.new(),
        "parent": EntryID.new(),
        "version": 42,
        "timestamp": pendulum.now(),
        "created": pendulum.now(),
        "updated": pendulum.now(),
        "children": {
            EntryName("file1.txt"): EntryID.new()
        },
    }
    py_wm = _PyFolderManifest(**kwargs)
    rs_wm = FolderManifest(**kwargs)
    _assert_folder_manifest_eq(py_wm, rs_wm)

    kwargs = {
        "author": DeviceID("a@b"),
        "id": EntryID.new(),
        "parent": EntryID.new(),
        "version": 1337,
        "timestamp": pendulum.now(),
        "created": pendulum.now(),
        "updated": pendulum.now(),
        "children": {
            EntryName("file2.mp4"): EntryID.new()
        },
    }

    py_wm = py_wm.evolve(**kwargs)
    rs_wm = rs_wm.evolve(**kwargs)
    _assert_folder_manifest_eq(py_wm, rs_wm)
Ejemplo n.º 8
0
async def test_invite_claim_3_chained_users(running_backend, backend, alice):
    # Zeta will be invited by Zoe, Zoe will be invited by Zack
    new_device_id_1 = DeviceID("zack@pc1")
    new_device_1 = None
    token_1 = generate_invitation_token()
    new_device_id_2 = DeviceID("zoe@pc2")
    new_device_2 = None
    token_2 = generate_invitation_token()
    new_device_id_3 = DeviceID("zeta@pc3")
    new_device_3 = None
    token_3 = generate_invitation_token()

    async def _invite_from_alice():
        await invite_and_create_user(alice, new_device_id_1.user_id, token=token_1, is_admin=True)

    async def _claim_from_1():
        nonlocal new_device_1
        new_device_1 = await claim_user(alice.organization_addr, new_device_id_1, token=token_1)

    async def _invite_from_1():
        await invite_and_create_user(
            new_device_1, new_device_id_2.user_id, token=token_2, is_admin=True
        )

    async def _claim_from_2():
        nonlocal new_device_2
        new_device_2 = await claim_user(alice.organization_addr, new_device_id_2, token=token_2)

    async def _invite_from_2():
        await invite_and_create_user(
            new_device_2, new_device_id_3.user_id, token=token_3, is_admin=False
        )

    async def _claim_from_3():
        nonlocal new_device_3
        new_device_3 = await claim_user(alice.organization_addr, new_device_id_3, token=token_3)

    await _invite_and_claim(running_backend, _invite_from_alice, _claim_from_1)
    await _invite_and_claim(running_backend, _invite_from_1, _claim_from_2)
    await _invite_and_claim(running_backend, _invite_from_2, _claim_from_3)

    assert new_device_1.is_admin
    assert new_device_2.is_admin
    assert not new_device_3.is_admin

    # Now connect as the last user
    async with backend_authenticated_cmds_factory(
        new_device_2.organization_addr, new_device_2.device_id, new_device_2.signing_key
    ) as cmds:
        await cmds.ping("foo")
Ejemplo n.º 9
0
async def test_invite_claim_multiple_devices_from_chained_user(running_backend, backend, alice):
    # The devices are invited from one another
    new_device_id_1 = DeviceID("zack@pc1")
    new_device_1 = None
    token_1 = generate_invitation_token()

    new_device_id_2 = DeviceID("zack@pc2")
    new_device_2 = None
    token_2 = generate_invitation_token()

    new_device_id_3 = DeviceID("zack@pc3")
    new_device_3 = None
    token_3 = generate_invitation_token()

    async def _invite_from_alice():
        await invite_and_create_user(alice, new_device_id_1.user_id, token=token_1, is_admin=True)

    async def _claim_from_1():
        nonlocal new_device_1
        new_device_1 = await claim_user(alice.organization_addr, new_device_id_1, token=token_1)

    async def _invite_from_1():
        await invite_and_create_device(new_device_1, new_device_id_2.device_name, token=token_2)

    async def _claim_from_2():
        nonlocal new_device_2
        new_device_2 = await claim_device(alice.organization_addr, new_device_id_2, token=token_2)

    async def _invite_from_2():
        await invite_and_create_device(new_device_2, new_device_id_3.device_name, token=token_3)

    async def _claim_from_3():
        nonlocal new_device_3
        new_device_3 = await claim_device(alice.organization_addr, new_device_id_3, token=token_3)

    await _invite_and_claim(running_backend, _invite_from_alice, _claim_from_1)
    await _invite_and_claim(
        running_backend, _invite_from_1, _claim_from_2, event_name="device.claimed"
    )
    await _invite_and_claim(
        running_backend, _invite_from_2, _claim_from_3, event_name="device.claimed"
    )

    # Now connect as the last device
    async with backend_authenticated_cmds_factory(
        new_device_3.organization_addr, new_device_3.device_id, new_device_3.signing_key
    ) as cmds:
        await cmds.ping("foo")
Ejemplo n.º 10
0
def generate_BOB_local_device():
    return LocalDevice(
        organization_addr=BackendOrganizationAddr.from_url(
            "parsec://bob_dev1.example.com:9999/CoolOrg?no_ssl=true&rvk=XYUXM4ZM5SGKSTXNZ4FK7VATZUKZGY7A7LOJ42CXFR32DYL5TO6Qssss"
        ),
        device_id=DeviceID("bob@dev1"),
        device_label=DeviceLabel("My dev1 machine"),
        human_handle=HumanHandle("*****@*****.**", "Boby McBobFace"),
        signing_key=SigningKey(
            unhexlify(
                "85f47472a2c0f30f01b769617db248f3ec8d96a490602a9262f95e9e43432b30"
            )),
        private_key=PrivateKey(
            unhexlify(
                "16767ec446f2611f971c36f19c2dc11614d853475ac395d6c1d70ba46d07dd49"
            )),
        profile=UserProfile.STANDARD,
        user_manifest_id=EntryID.from_hex("71568d41afcb4e2380b3d164ace4fb85"),
        user_manifest_key=SecretKey(
            unhexlify(
                "65de53d2c6cd965aa53a1ba5cc7e54b331419e6103466121996fa99a97197a48"
            )),
        local_symkey=SecretKey(
            unhexlify(
                "93f25b18491016f20b10dcf4eb7986716d914653d6ab4e778701c13435e6bdf0"
            )),
    )
Ejemplo n.º 11
0
def generate_ALICE_local_device():
    return LocalDevice(
        organization_addr=BackendOrganizationAddr.from_url(
            "parsec://alice_dev1.example.com:9999/CoolOrg?no_ssl=true&rvk=XYUXM4ZM5SGKSTXNZ4FK7VATZUKZGY7A7LOJ42CXFR32DYL5TO6Qssss"
        ),
        device_id=DeviceID("alice@dev1"),
        device_label=DeviceLabel("My dev1 machine"),
        human_handle=HumanHandle("*****@*****.**", "Alicey McAliceFace"),
        signing_key=SigningKey(
            unhexlify(
                "d544f66ece9c85d5b80275db9124b5f04bb038081622bed139c1e789c5217400"
            )),
        private_key=PrivateKey(
            unhexlify(
                "74e860967fd90d063ebd64fb1ba6824c4c010099dd37508b7f2875a5db2ef8c9"
            )),
        profile=UserProfile.ADMIN,
        user_manifest_id=EntryID.from_hex("a4031e8bcdd84df8ae12bd3d05e6e20f"),
        user_manifest_key=SecretKey(
            unhexlify(
                "26bf35a98c1e54e90215e154af92a1af2d1142cdd0dba25b990426b0b30b0f9a"
            )),
        local_symkey=SecretKey(
            unhexlify(
                "125a78618995e2e0f9a19bc8617083c809c03deb5457d5b82df5bcaec9966cd4"
            )),
    )
Ejemplo n.º 12
0
async def test_user_claim_already_exists(mock_clock, backend,
                                         anonymous_backend_sock, alice,
                                         mallory_invitation):
    await backend.user.create_user(
        alice.organization_id,
        User(
            user_id=mallory_invitation.user_id,
            user_certificate=b"<foo>",
            user_certifier=alice.device_id,
        ),
        Device(
            device_id=DeviceID(f"{mallory_invitation.user_id}@pc1"),
            device_certificate=b"<bar>",
            device_certifier=alice.device_id,
        ),
    )

    with freeze_time(mallory_invitation.created_on):
        async with user_claim(
                anonymous_backend_sock,
                invited_user_id=mallory_invitation.user_id,
                encrypted_claim=b"<foo>",
        ) as prep:

            pass

    assert prep[0] == {"status": "not_found"}
Ejemplo n.º 13
0
async def test_device_invite(monkeypatch, backend, alice_backend_sock, alice, alice_nd_id):
    dummy_device_id = DeviceID(f"{alice.user_id}@pc1")
    await backend.user.create_device_invitation(
        alice.organization_id, DeviceInvitation(dummy_device_id, alice.device_id)
    )

    device_invitation_created = trio.Event()

    vanilla_create_device_invitation = backend.user.create_device_invitation

    async def _mocked_create_device_invitation(*args, **kwargs):
        ret = await vanilla_create_device_invitation(*args, **kwargs)
        device_invitation_created.set()
        return ret

    monkeypatch.setattr(backend.user, "create_device_invitation", _mocked_create_device_invitation)

    with trio.fail_after(1):
        async with device_invite(
            alice_backend_sock, invited_device_name=alice_nd_id.device_name
        ) as prep:

            # Wait for invitation to be created before fetching it !
            await device_invitation_created.wait()

            # No the user we are waiting for
            await backend.user.claim_device_invitation(
                alice.organization_id, dummy_device_id, b"<alice@dummy encrypted_claim>"
            )

            await backend.user.claim_device_invitation(
                alice.organization_id, alice_nd_id, b"<alice@new_device encrypted_claim>"
            )

        assert prep[0] == {"status": "ok", "encrypted_claim": b"<alice@new_device encrypted_claim>"}
Ejemplo n.º 14
0
 def __init__(self) -> None:
     super().__init__()
     self.oracle = open(tmpdir / "oracle.txt", "w+b")
     self.manifest = LocalFileManifest.new_placeholder(DeviceID.new(),
                                                       parent=EntryID(),
                                                       blocksize=8)
     self.storage = Storage()
Ejemplo n.º 15
0
async def test_user_invite_claim_invalid_token(running_backend, backend, alice):
    new_device_id = DeviceID("zack@pc1")
    token = generate_invitation_token()
    bad_token = generate_invitation_token()
    invite_exception_occured = False
    claim_exception_occured = False

    async def _from_alice():
        nonlocal invite_exception_occured
        with pytest.raises(InviteClaimInvalidTokenError) as exc:
            await invite_and_create_user(alice, new_device_id.user_id, is_admin=False, token=token)
        assert (
            str(exc.value)
            == f"Invalid claim token provided by peer: `{bad_token}` (was expecting `{token}`)"
        )
        invite_exception_occured = True

    async def _from_new_device():
        nonlocal claim_exception_occured
        with pytest.raises(InviteClaimError) as exc:
            await claim_user(alice.organization_addr, new_device_id, token=bad_token)
        assert (
            str(exc.value)
            == "Cannot claim user: {'reason': 'Invitation creator rejected us.', 'status': 'denied'}"
        )
        claim_exception_occured = True

    await _invite_and_claim(running_backend, _from_alice, _from_new_device)
    assert invite_exception_occured
    assert claim_exception_occured
Ejemplo n.º 16
0
async def test_user_create_human_handle_already_exists(alice_backend_sock,
                                                       alice, bob):
    now = pendulum.now()
    bob2_device_id = DeviceID("bob2@dev1")
    user_certificate = UserCertificateContent(
        author=alice.device_id,
        timestamp=now,
        user_id=bob2_device_id.user_id,
        public_key=bob.public_key,
        is_admin=False,
        human_handle=bob.human_handle,
    ).dump_and_sign(alice.signing_key)
    device_certificate = DeviceCertificateContent(
        author=alice.device_id,
        timestamp=now,
        device_id=bob2_device_id,
        verify_key=bob.verify_key).dump_and_sign(alice.signing_key)

    rep = await user_create(alice_backend_sock,
                            user_certificate=user_certificate,
                            device_certificate=device_certificate)
    assert rep == {
        "status":
        "already_exists",
        "reason":
        f"Human handle `{bob.human_handle}` already corresponds to a non-revoked user",
    }
Ejemplo n.º 17
0
async def test_user_create_human_handle_with_revoked_previous_one(
        alice_backend_sock, alice, bob, backend_data_binder):
    # First revoke bob
    await backend_data_binder.bind_revocation(user_id=bob.user_id,
                                              certifier=alice)

    # Now recreate another user with bob's human handle
    now = pendulum.now()
    bob2_device_id = DeviceID("bob2@dev1")
    user_certificate = UserCertificateContent(
        author=alice.device_id,
        timestamp=now,
        user_id=bob2_device_id.user_id,
        public_key=bob.public_key,
        is_admin=False,
        human_handle=bob.human_handle,
    ).dump_and_sign(alice.signing_key)
    device_certificate = DeviceCertificateContent(
        author=alice.device_id,
        timestamp=now,
        device_id=bob2_device_id,
        verify_key=bob.verify_key).dump_and_sign(alice.signing_key)

    rep = await user_create(alice_backend_sock,
                            user_certificate=user_certificate,
                            device_certificate=device_certificate)
    assert rep == {"status": "ok"}
Ejemplo n.º 18
0
async def _get_user_devices(conn, organization_id: OrganizationID,
                            user_id: UserID) -> Tuple[Device, ...]:
    results = await conn.fetch(*_q_get_user_devices(
        organization_id=organization_id.str, user_id=user_id.str))

    return tuple(
        Device(
            device_id=DeviceID(row["device_id"]),
            device_label=DeviceLabel(row["device_label"]
                                     ) if row["device_label"] else None,
            device_certificate=row["device_certificate"],
            redacted_device_certificate=row["redacted_device_certificate"],
            device_certifier=DeviceID(row["device_certifier"]
                                      ) if row["device_certifier"] else None,
            created_on=row["created_on"],
        ) for row in results)
Ejemplo n.º 19
0
async def alice_invite(running_backend, backend, alice):
    device_id = DeviceID("Zack@pc1")
    # Modify address subdomain to be able to switch it offline whithout
    # disconnecting the inviter
    organization_addr = addr_with_device_subdomain(alice.organization_addr,
                                                   device_id)
    invitation = {
        "addr":
        BackendOrganizationClaimUserAddr.build(organization_addr, "Zack",
                                               "123456"),
        "token":
        "123456",
        "user_id":
        device_id.user_id,
        "device_name":
        device_id.device_name,
        "password":
        "******",
    }

    async def _invite():
        await invite_and_create_user(alice, invitation["user_id"],
                                     invitation["token"], True)

    async with trio.open_service_nursery() as nursery:
        with backend.event_bus.listen() as spy:
            nursery.start_soon(_invite)
            await spy.wait_with_timeout("event.connected",
                                        {"event_name": "user.claimed"})

            yield invitation

            nursery.cancel_scope.cancel()
Ejemplo n.º 20
0
    def _from_url_parse_and_consume_params(cls, params):
        kwargs = super()._from_url_parse_and_consume_params(params)

        value = params.pop("action", ())
        if len(value) != 1:
            raise ValueError("Missing mandatory `action` param")
        if value[0] != "claim_device":
            raise ValueError("Expected `action=claim_device` value")

        value = params.pop("device_id", ())
        if len(value) != 1:
            raise ValueError("Missing mandatory `device_id` param")
        try:
            kwargs["device_id"] = DeviceID(value[0])
        except ValueError as exc:
            raise ValueError("Invalid `device_id` param value") from exc

        value = params.pop("token", ())
        if len(value) > 0:
            try:
                kwargs["token"] = value[0]
            except ValueError:
                raise ValueError("Invalid `token` param value")

        return kwargs
Ejemplo n.º 21
0
async def test_path_info_remote_loader_exceptions(monkeypatch, alice_workspace,
                                                  alice):
    manifest, _ = await alice_workspace.transactions._get_manifest_from_path(
        FsPath("/foo/bar"))
    async with alice_workspace.local_storage.lock_entry_id(manifest.id):
        await alice_workspace.local_storage.clear_manifest(manifest.id)

    vanilla_file_manifest_deserialize = BaseRemoteManifest._deserialize

    def mocked_file_manifest_deserialize(*args, **kwargs):
        return vanilla_file_manifest_deserialize(
            *args, **kwargs).evolve(**manifest_modifiers)

    monkeypatch.setattr(BaseRemoteManifest, "_deserialize",
                        mocked_file_manifest_deserialize)

    manifest_modifiers = {"id": EntryID.new()}
    with pytest.raises(FSError) as exc:
        await alice_workspace.path_info(FsPath("/foo/bar"))
    assert f"Invalid entry ID: expected `{manifest.id}`, got `{manifest_modifiers['id']}`" in str(
        exc.value)

    manifest_modifiers = {"version": 4}
    with pytest.raises(FSError) as exc:
        await alice_workspace.path_info(FsPath("/foo/bar"))
    assert "Invalid version: expected `1`, got `4`" in str(exc.value)

    manifest_modifiers = {"author": DeviceID("mallory@pc1")}
    with pytest.raises(FSError) as exc:
        await alice_workspace.path_info(FsPath("/foo/bar"))
    assert "Invalid author: expected `alice@dev1`, got `mallory@pc1`" in str(
        exc.value)
Ejemplo n.º 22
0
    async def _api_device_invite(self, client_ctx, msg):
        invited_device_id = DeviceID(
            f"{client_ctx.device_id.user_id}@{msg['invited_device_name']}")
        invitation = DeviceInvitation(invited_device_id, client_ctx.device_id)

        def _filter_on_device_claimed(event, organization_id, device_id,
                                      encrypted_claim):
            return organization_id == client_ctx.organization_id and device_id == invited_device_id

        with self._event_bus.waiter_on(
                "device.claimed", filter=_filter_on_device_claimed) as waiter:

            try:
                await self.create_device_invitation(client_ctx.organization_id,
                                                    invitation)

            except UserAlreadyExistsError as exc:
                return {"status": "already_exists", "reason": str(exc)}

            # Wait for invited user to send `user_claim`
            _, event_data = await waiter.wait()

        return {
            "status": "ok",
            "encrypted_claim": event_data["encrypted_claim"]
        }
Ejemplo n.º 23
0
async def test_invite_claim_admin_user(running_backend, backend, alice):
    new_device_id = DeviceID("zack@pc1")
    new_device = None
    token = generate_invitation_token()

    async def _from_alice():
        await invite_and_create_user(alice,
                                     new_device_id.user_id,
                                     token=token,
                                     is_admin=True)

    async def _from_new_device():
        nonlocal new_device
        new_device = await claim_user(alice.organization_addr,
                                      new_device_id,
                                      token=token)

    await _invite_and_claim(running_backend, _from_alice, _from_new_device)

    assert new_device.is_admin

    # Now connect as the new user
    async with backend_authenticated_cmds_factory(
        new_device.organization_addr, new_device.device_id,
        new_device.signing_key) as cmds:
        await cmds.ping("foo")
Ejemplo n.º 24
0
def _create_new_user_certificates(
    author: LocalDevice,
    device_label: Optional[DeviceLabel],
    human_handle: Optional[HumanHandle],
    profile: UserProfile,
    public_key: PublicKey,
    verify_key: VerifyKey,
) -> Tuple[bytes, bytes, bytes, bytes, InviteUserConfirmation]:
    """Helper to prepare the creation of a new user."""
    device_id = DeviceID.new()
    try:
        timestamp = author.timestamp()

        user_certificate = UserCertificateContent(
            author=author.device_id,
            timestamp=timestamp,
            user_id=device_id.user_id,
            human_handle=human_handle,
            public_key=public_key,
            profile=profile,
        )
        redacted_user_certificate = user_certificate.evolve(human_handle=None)

        device_certificate = DeviceCertificateContent(
            author=author.device_id,
            timestamp=timestamp,
            device_id=device_id,
            device_label=device_label,
            verify_key=verify_key,
        )
        redacted_device_certificate = device_certificate.evolve(
            device_label=None)

        user_certificate = user_certificate.dump_and_sign(author.signing_key)
        redacted_user_certificate = redacted_user_certificate.dump_and_sign(
            author.signing_key)
        device_certificate = device_certificate.dump_and_sign(
            author.signing_key)
        redacted_device_certificate = redacted_device_certificate.dump_and_sign(
            author.signing_key)

    except DataError as exc:
        raise InviteError(
            f"Cannot generate device certificate: {exc}") from exc

    invite_user_confirmation = InviteUserConfirmation(
        device_id=device_id,
        device_label=device_label,
        human_handle=human_handle,
        profile=profile,
        root_verify_key=author.root_verify_key,
    )

    return (
        user_certificate,
        redacted_user_certificate,
        device_certificate,
        redacted_device_certificate,
        invite_user_confirmation,
    )
Ejemplo n.º 25
0
async def test_device_invite_claim_cancel_invitation(running_backend, backend,
                                                     alice):
    new_device_id = DeviceID(f"{alice.user_id}@NewDevice")
    token = generate_invitation_token()

    invite_and_claim_cancel_scope = None

    async def _from_alice():
        nonlocal invite_and_claim_cancel_scope
        with trio.CancelScope() as invite_and_claim_cancel_scope:
            await invite_and_create_device(alice,
                                           new_device_id.device_name,
                                           token=token)

    async def _cancel_invite_and_claim():
        invite_and_claim_cancel_scope.cancel()

    await _invite_and_claim(running_backend,
                            _from_alice,
                            _cancel_invite_and_claim,
                            event_name="device.claimed")

    # Now make sure the invitation cannot be used
    with trio.fail_after(1):
        with pytest.raises(InviteClaimError) as exc:
            await claim_device(alice.organization_addr,
                               new_device_id,
                               token=token)
    assert (
        str(exc.value) ==
        "Cannot retrieve invitation creator: User `alice@NewDevice` doesn't exist in backend"
    )
Ejemplo n.º 26
0
async def test_user_invite_claim_cancel_invitation(monitor, running_backend,
                                                   backend, alice):
    new_device_id = DeviceID("zack@pc1")
    token = generate_invitation_token()

    invite_and_claim_cancel_scope = None

    async def _from_alice():
        nonlocal invite_and_claim_cancel_scope
        with trio.CancelScope() as invite_and_claim_cancel_scope:
            await invite_and_create_user(alice,
                                         new_device_id.user_id,
                                         is_admin=False,
                                         token=token)

    async def _cancel_invite_and_claim():
        invite_and_claim_cancel_scope.cancel()

    await _invite_and_claim(running_backend, _from_alice,
                            _cancel_invite_and_claim)

    # Now make sure the invitation cannot be used
    with trio.fail_after(1):
        with pytest.raises(InviteClaimError) as exc:
            await claim_user(alice.organization_addr,
                             new_device_id,
                             token=token)
        assert (
            str(exc.value) ==
            "Cannot retrieve invitation creator: User `zack` doesn't exist in backend"
        )
Ejemplo n.º 27
0
    async def _do_test():
        nonlocal device_count
        device_count += 1
        new_device_id = DeviceID(f"{alice.user_id}@newdev{device_count}")
        token = generate_invitation_token()
        exception_occured = False

        async def _from_alice():
            await invite_and_create_device(alice,
                                           new_device_id.device_name,
                                           token=token)

        async def _from_new_device():
            nonlocal exception_occured
            with pytest.raises(InviteClaimCryptoError):
                await claim_device(alice.organization_addr,
                                   new_device_id,
                                   token=token)
            exception_occured = True

        await _invite_and_claim(running_backend,
                                _from_alice,
                                _from_new_device,
                                event_name="device.claimed")
        assert exception_occured
Ejemplo n.º 28
0
async def test_invite_claim_device(running_backend, backend, alice):
    new_device_id = DeviceID(f"{alice.user_id}@NewDevice")
    new_device = None
    token = generate_invitation_token()

    async def _from_alice():
        await invite_and_create_device(alice,
                                       new_device_id.device_name,
                                       token=token)

    async def _from_new_device():
        nonlocal new_device
        new_device = await claim_device(alice.organization_addr,
                                        new_device_id,
                                        token=token)

    await _invite_and_claim(running_backend,
                            _from_alice,
                            _from_new_device,
                            event_name="device.claimed")

    # Now connect as the new device
    async with backend_authenticated_cmds_factory(
        new_device.organization_addr, new_device.device_id,
        new_device.signing_key) as cmds:
        await cmds.ping("foo")
Ejemplo n.º 29
0
    async def do_create_new_device(self, author: LocalDevice,
                                   device_label: Optional[str]) -> None:
        device_id = DeviceID(f"{author.user_id}@{DeviceName.new()}")
        try:
            now = pendulum_now()

            device_certificate = DeviceCertificateContent(
                author=author.device_id,
                timestamp=now,
                device_id=device_id,
                verify_key=self._verify_key,
                device_label=device_label,
            )
            redacted_device_certificate = device_certificate.evolve(
                device_label=None)

            device_certificate = device_certificate.dump_and_sign(
                author.signing_key)
            redacted_device_certificate = redacted_device_certificate.dump_and_sign(
                author.signing_key)

        except DataError as exc:
            raise InviteError(
                f"Cannot generate device certificate: {exc}") from exc

        rep = await self._cmds.device_create(
            device_certificate=device_certificate,
            redacted_device_certificate=redacted_device_certificate,
        )
        if rep["status"] != "ok":
            raise InviteError(f"Cannot create device: {rep}")

        try:
            payload = InviteDeviceConfirmation(
                device_id=device_id,
                device_label=device_label,
                human_handle=author.human_handle,
                profile=author.profile,
                private_key=author.private_key,
                user_manifest_id=author.user_manifest_id,
                user_manifest_key=author.user_manifest_key,
                root_verify_key=author.root_verify_key,
            ).dump_and_encrypt(key=self._shared_secret_key)
        except DataError as exc:
            raise InviteError(
                "Cannot generate InviteUserConfirmation payload") from exc

        rep = await self._cmds.invite_4_greeter_communicate(token=self.token,
                                                            payload=payload)
        if rep["status"] in ("not_found", "already_deleted"):
            raise InviteNotAvailableError()
        elif rep["status"] == "invalid_state":
            raise InvitePeerResetError()
        elif rep["status"] != "ok":
            raise InviteError(
                f"Backend error during step 4 (confirmation exchange): {rep}")

        await self._cmds.invite_delete(token=self.token,
                                       reason=InvitationDeletedReason.FINISHED)
Ejemplo n.º 30
0
 def validate(self, string, pos):
     try:
         if len(string) == 0:
             return QValidator.Intermediate, string, pos
         DeviceID(string)
         return QValidator.Acceptable, string, pos
     except ValueError:
         return QValidator.Invalid, string, pos