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, )
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>", }
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)
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>`")
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)
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])
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)
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")
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")
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" )), )
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" )), )
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"}
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>"}
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()
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
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", }
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"}
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)
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()
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
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)
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"] }
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")
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, )
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" )
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" )
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
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")
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)
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