def new_user_and_device(self, is_admin, certifier_id, certifier_key): device_id = self.next_device_id() local_device = local_device_factory(device_id, org=coolorg) self.local_devices[device_id] = local_device user = UserCertificateContent( author=certifier_id, timestamp=pendulum_now(), user_id=local_device.user_id, public_key=local_device.public_key, is_admin=is_admin, ) self.users_content[device_id.user_id] = user self.users_certifs[device_id.user_id] = user.dump_and_sign( certifier_key) device = DeviceCertificateContent( author=certifier_id, timestamp=pendulum_now(), device_id=local_device.device_id, verify_key=local_device.verify_key, ) self.devices_content[local_device.device_id] = device self.devices_certifs[ local_device.device_id] = device.dump_and_sign(certifier_key) return device_id
def new_user_and_device(self, is_admin, certifier_id, certifier_key): device_id = self.next_device_id() local_device = local_device_factory(device_id, org=coolorg) self.local_devices[device_id] = local_device user = UserCertificateContent( author=certifier_id, timestamp=pendulum_now(), user_id=local_device.user_id, human_handle=local_device.human_handle, public_key=local_device.public_key, profile=UserProfile.ADMIN if is_admin else UserProfile.STANDARD, ) self.users_content[device_id.user_id] = user self.users_certifs[device_id.user_id] = user.dump_and_sign( certifier_key) device = DeviceCertificateContent( author=certifier_id, timestamp=pendulum_now(), device_id=local_device.device_id, device_label=local_device.device_label, verify_key=local_device.verify_key, ) self.devices_content[local_device.device_id] = device self.devices_certifs[ local_device.device_id] = device.dump_and_sign(certifier_key) return device_id
async def query_find(conn, organization_id: OrganizationID, query: str, page: int, per_page: int, omit_revoked: bool) -> Tuple[List[UserID], int]: if query: try: UserID(query) except ValueError: # Contains invalid caracters, no need to go further return ([], 0) if omit_revoked: q = _q_factory(query=True, omit_revoked=True) args = (organization_id, query, pendulum_now()) else: q = _q_factory(query=True, omit_revoked=False) args = (organization_id, query) else: if omit_revoked: q = _q_factory(query=False, omit_revoked=True) args = (organization_id, pendulum_now()) else: q = _q_factory(query=False, omit_revoked=False) args = (organization_id, ) all_results = await conn.fetch(q, *args) # TODO: should user LIMIT and OFFSET in the SQL query instead results = [ UserID(x[0]) for x in all_results[(page - 1) * per_page:page * per_page] ] return results, len(all_results)
async def test_start_bad_per_participant_message( backend, alice_backend_sock, alice, bob, adam, realm ): # Bob used to be part of the realm await backend.realm.update_roles( alice.organization_id, RealmGrantedRole( certificate=b"<dummy>", realm_id=realm, user_id=bob.user_id, role=RealmRole.READER, granted_by=alice.device_id, ), ) await backend.realm.update_roles( alice.organization_id, RealmGrantedRole( certificate=b"<dummy>", realm_id=realm, user_id=bob.user_id, role=None, granted_by=alice.device_id, ), ) # Adam is still part of the realm, but is revoked await backend.realm.update_roles( alice.organization_id, RealmGrantedRole( certificate=b"<dummy>", realm_id=realm, user_id=adam.user_id, role=RealmRole.READER, granted_by=alice.device_id, ), ) await backend.user.revoke_user( alice.organization_id, adam.user_id, revoked_user_certificate=b"<dummy>", revoked_user_certifier=alice.device_id, ) for msg in [ {}, {alice.user_id: b"ok", bob.user_id: b"bad"}, {alice.user_id: b"ok", "zack": b"bad"}, {alice.user_id: b"ok", adam.user_id: b"bad"}, ]: rep = await realm_start_reencryption_maintenance( alice_backend_sock, realm, 2, pendulum_now(), msg, check_rep=False ) assert rep == { "status": "participants_mismatch", "reason": "Realm participants and message recipients mismatch", } # Finally make sure the reencryption is possible await realm_start_reencryption_maintenance( alice_backend_sock, realm, 2, pendulum_now(), {alice.user_id: b"ok"} )
async def query_find(conn, organization_id: OrganizationID, query: str, page: int, per_page: int, omit_revoked: bool) -> Tuple[List[UserID], int]: if page > 1: offset = ((page - 1) * per_page) - 1 else: offset = 0 if query: try: UserID(query) except ValueError: # Contains invalid caracters, no need to go further return ([], 0) if omit_revoked: q = _q_factory(query=True, omit_revoked=True, limit=per_page, offset=offset) args = q(organization_id=organization_id, query="%" + query + "%", now=pendulum_now()) else: q = _q_factory(query=True, omit_revoked=False, limit=per_page, offset=offset) args = q(organization_id=organization_id, query="%" + query + "%") else: if omit_revoked: q = _q_factory(query=False, omit_revoked=True, limit=per_page, offset=offset) args = q(organization_id=organization_id, now=pendulum_now()) else: q = _q_factory(query=False, omit_revoked=False, limit=per_page, offset=offset) args = q(organization_id=organization_id) all_results = [user["user_id"] for user in await conn.fetch(*args)] q = _q_count_total_human(query, omit_revoked=omit_revoked, omit_non_human=True, in_find=True) if omit_revoked: total = await conn.fetchrow( *q(organization_id=organization_id, now=pendulum_now())) else: total = await conn.fetchrow(*q(organization_id=organization_id)) return all_results, total[0]
async def test_start_check_access_rights( backend, bob_backend_sock, alice, bob, realm, next_timestamp ): # User not part of the realm rep = await realm_start_reencryption_maintenance( bob_backend_sock, realm, 2, pendulum_now(), {alice.user_id: b"wathever"}, check_rep=False ) assert rep == {"status": "not_allowed"} # User part of the realm with various role for not_allowed_role in (RealmRole.READER, RealmRole.CONTRIBUTOR, RealmRole.MANAGER, None): await backend.realm.update_roles( alice.organization_id, RealmGrantedRole( certificate=b"<dummy>", realm_id=realm, user_id=bob.user_id, role=not_allowed_role, granted_by=alice.device_id, granted_on=next_timestamp(), ), ) rep = await realm_start_reencryption_maintenance( bob_backend_sock, realm, 2, next_timestamp(), {alice.user_id: b"foo", bob.user_id: b"bar"}, check_rep=False, ) assert rep == {"status": "not_allowed"} # Finally, just make sure owner can do it await backend.realm.update_roles( alice.organization_id, RealmGrantedRole( certificate=b"<dummy>", realm_id=realm, user_id=bob.user_id, role=RealmRole.OWNER, granted_by=alice.device_id, granted_on=next_timestamp(), ), ) rep = await realm_start_reencryption_maintenance( bob_backend_sock, realm, 2, pendulum_now(), {alice.user_id: b"foo", bob.user_id: b"bar"}, check_rep=False, ) assert rep == {"status": "ok"}
async def _do_process_authenticated_answer( backend, transport: Transport, handshake: ServerHandshake, handshake_type ) -> Tuple[Optional[BaseClientContext], bytes, Optional[Dict]]: organization_id = handshake.answer_data["organization_id"] device_id = handshake.answer_data["device_id"] expected_rvk = handshake.answer_data["rvk"] def _make_error_infos(reason): return { "reason": reason, "handshake_type": handshake_type, "organization_id": organization_id, "device_id": device_id, } try: organization = await backend.organization.get(organization_id) user, device = await backend.user.get_user_with_device( organization_id, device_id) except (OrganizationNotFoundError, UserNotFoundError, KeyError) as exc: result_req = handshake.build_bad_identity_result_req() return None, result_req, _make_error_infos(str(exc)) if organization.root_verify_key != expected_rvk: result_req = handshake.build_rvk_mismatch_result_req() return None, result_req, _make_error_infos("Bad root verify key") if organization.expiration_date is not None and organization.expiration_date <= pendulum_now( ): result_req = handshake.build_organization_expired_result_req() return None, result_req, _make_error_infos("Expired organization") if user.revoked_on and user.revoked_on <= pendulum_now(): result_req = handshake.build_revoked_device_result_req() return None, result_req, _make_error_infos("Revoked device") context = AuthenticatedClientContext( transport=transport, handshake=handshake, organization_id=organization_id, device_id=device_id, human_handle=user.human_handle, profile=user.profile, public_key=user.public_key, verify_key=device.verify_key, ) result_req = handshake.build_result_req(device.verify_key) return context, result_req, None
async def query_find( conn, organization_id: OrganizationID, page: int = 1, per_page: int = 100, omit_revoked: bool = False, query: Optional[str] = None, ) -> Tuple[List[UserID], int]: if page >= 1: offset = (page - 1) * per_page else: return ([], 0) if query: try: UserID(query) except ValueError: # Contains invalid caracters, no need to go further return ([], 0) q = _q_factory(with_query=bool(query), omit_revoked=omit_revoked) if query: if omit_revoked: args = q( organization_id=organization_id, now=pendulum_now(), query=query, offset=offset, limit=per_page, ) else: args = q(organization_id=organization_id, query=query, offset=offset, limit=per_page) else: if omit_revoked: args = q(organization_id=organization_id, now=pendulum_now(), offset=offset, limit=per_page) else: args = q(organization_id=organization_id, offset=offset, limit=per_page) raw_results = await conn.fetch(*args) total = raw_results[0]["total"] results = [UserID(x["user_id"]) for x in raw_results[1:]] return results, total
async def test_start_already_in_maintenance(alice_backend_sock, realm): await realm_start_reencryption_maintenance( alice_backend_sock, realm, 2, pendulum_now(), {"alice": b"wathever"} ) # Providing good or bad encryption revision shouldn't change anything for encryption_revision in (2, 3): rep = await realm_start_reencryption_maintenance( alice_backend_sock, realm, encryption_revision, pendulum_now(), {"alice": b"wathever"}, check_rep=False, ) assert rep == {"status": "in_maintenance"}
async def query_retrieve_active_human_by_email(conn, organization_id: OrganizationID, email: str) -> Optional[UserID]: result = await conn.fetchrow(*_q_retrieve_active_human_by_email( organization_id=organization_id, now=pendulum_now(), email=email)) if result: return UserID(result["user_id"])
async def _apiv1_process_anonymous_answer( backend, transport: Transport, handshake: ServerHandshake ) -> Tuple[Optional[BaseClientContext], bytes, Optional[Dict]]: organization_id = handshake.answer_data["organization_id"] expected_rvk = handshake.answer_data["rvk"] def _make_error_infos(reason): return { "reason": reason, "handshake_type": APIV1_HandshakeType.ANONYMOUS, "organization_id": organization_id, } try: organization = await backend.organization.get(organization_id) except OrganizationNotFoundError: result_req = handshake.build_bad_identity_result_req() return None, result_req, _make_error_infos("Bad organization") if organization.expiration_date is not None and organization.expiration_date <= pendulum_now(): result_req = handshake.build_organization_expired_result_req() return None, result_req, _make_error_infos("Expired organization") if expected_rvk and organization.root_verify_key != expected_rvk: result_req = handshake.build_rvk_mismatch_result_req() return None, result_req, _make_error_infos("Bad root verify key") context = APIV1_AnonymousClientContext(transport, handshake, organization_id=organization_id) result_req = handshake.build_result_req() return context, result_req, None
async def test_status(backend, bob_backend_sock, alice_backend_sock, alice, bob, realm): rep = await realm_status(alice_backend_sock, realm) assert rep == { "status": "ok", "in_maintenance": False, "maintenance_type": None, "maintenance_started_by": None, "maintenance_started_on": None, "encryption_revision": 1, } # Cheap test on no access rep = await realm_status(bob_backend_sock, realm) assert rep == {"status": "not_allowed"} # Also test lesser role have access await realm_update_roles( alice_backend_sock, RealmRoleCertificateContent( author=alice.device_id, timestamp=pendulum_now(), realm_id=realm, user_id=bob.user_id, role=RealmRole.READER, ).dump_and_sign(alice.signing_key), ) rep = await realm_status(bob_backend_sock, realm) assert rep == { "status": "ok", "in_maintenance": False, "maintenance_type": None, "maintenance_started_by": None, "maintenance_started_on": None, "encryption_revision": 1, }
async def new_for_user( self, organization_id: OrganizationID, greeter_user_id: UserID, claimer_email: str, created_on: Optional[DateTime] = None, ) -> UserInvitation: """ Raise: InvitationAlreadyMemberError """ created_on = created_on or pendulum_now() async with self.dbh.pool.acquire() as conn, conn.transaction(): user_id = await query_retrieve_active_human_by_email( conn, organization_id, claimer_email ) if user_id: raise InvitationAlreadyMemberError() token = await _do_new_user_invitation( conn, organization_id=organization_id, greeter_user_id=greeter_user_id, claimer_email=claimer_email, created_on=created_on, ) return UserInvitation( greeter_user_id=greeter_user_id, greeter_human_handle=None, claimer_email=claimer_email, token=token, created_on=created_on, )
def new_placeholder( cls, author: DeviceID, parent: EntryID, id: Optional[EntryID] = None, now: DateTime = None, blocksize=DEFAULT_BLOCK_SIZE, ) -> "LocalFileManifest": now = now or pendulum_now() blocks = () return cls( base=RemoteFileManifest( author=author, timestamp=now, id=id or EntryID.new(), parent=parent, version=0, created=now, updated=now, blocksize=blocksize, size=0, blocks=blocks, ), need_sync=True, updated=now, blocksize=blocksize, size=0, blocks=blocks, )
async def api_pki_enrollment_reject(self, client_ctx: AuthenticatedClientContext, msg: dict) -> dict: if client_ctx.profile != UserProfile.ADMIN: return { "status": "not_allowed", "reason": f"User `{client_ctx.device_id.user_id}` is not admin", } msg = pki_enrollment_reject_serializer.req_load(msg) try: await self.reject( organization_id=client_ctx.organization_id, enrollment_id=msg["enrollment_id"], rejected_on=pendulum_now(), ) rep = {"status": "ok"} except PkiEnrollmentNotFoundError as exc: rep = {"status": "not_found", "reason": str(exc)} except PkiEnrollmentNoLongerAvailableError as exc: rep = {"status": "no_longer_available", "reason": str(exc)} return pki_enrollment_reject_serializer.rep_dump(rep)
async def test_reencryption_events(backend, alice, alice_backend_sock, alice2_backend_sock, realm, vlobs, vlob_atoms): # Start listening events await events_subscribe(alice_backend_sock) with backend.event_bus.listen() as spy: # Start maintenance and check for events await realm_start_reencryption_maintenance(alice2_backend_sock, realm, 2, pendulum_now(), {"alice": b"foo"}) with trio.fail_after(1): # No guarantees those events occur before the commands' return await spy.wait_multiple([ BackendEvent.REALM_MAINTENANCE_STARTED, BackendEvent.MESSAGE_RECEIVED ]) rep = await events_listen_nowait(alice_backend_sock) assert rep == { "status": "ok", "event": APIEvent.REALM_MAINTENANCE_STARTED, "realm_id": realm, "encryption_revision": 2, } rep = await events_listen_nowait(alice_backend_sock) assert rep == { "status": "ok", "event": APIEvent.MESSAGE_RECEIVED, "index": 1 } # Do the reencryption rep = await vlob_maintenance_get_reencryption_batch(alice_backend_sock, realm, 2, size=100) await vlob_maintenance_save_reencryption_batch(alice_backend_sock, realm, 2, rep["batch"]) # Finish maintenance and check for events await realm_finish_reencryption_maintenance(alice2_backend_sock, realm, 2) # No guarantees those events occur before the commands' return await spy.wait_with_timeout(BackendEvent.REALM_MAINTENANCE_FINISHED) rep = await events_listen_nowait(alice_backend_sock) assert rep == { "status": "ok", "event": APIEvent.REALM_MAINTENANCE_FINISHED, "realm_id": realm, "encryption_revision": 2, } # Sanity check rep = await events_listen_nowait(alice_backend_sock) assert rep == {"status": "no_events"}
async def test_claim_device_already_deleted(aqtbot, running_backend, backend, autoclose_dialog, alice, gui): invitation = await backend.invite.new_for_device( organization_id=alice.organization_id, greeter_user_id=alice.user_id) invitation_addr = BackendInvitationAddr.build( backend_addr=alice.organization_addr.get_backend_addr(), organization_id=alice.organization_id, invitation_type=InvitationType.DEVICE, token=invitation.token, ) await backend.invite.delete( organization_id=alice.organization_id, greeter=alice.user_id, token=invitation_addr.token, on=pendulum_now(), reason=InvitationDeletedReason.CANCELLED, ) gui.add_instance(invitation_addr.to_url()) def _assert_dialogs(): assert len(autoclose_dialog.dialogs) == 1 assert autoclose_dialog.dialogs == [ ("Error", translate("TEXT_INVITATION_ALREADY_USED")) ] await aqtbot.wait_until(_assert_dialogs)
async def test_backend_close_on_user_revoke(backend, alice_backend_sock, backend_sock_factory, bob, alice): now = pendulum_now() bob_revocation = RevokedUserCertificateContent( author=alice.device_id, timestamp=now, user_id=bob.user_id).dump_and_sign(alice.signing_key) async with backend_sock_factory( backend, bob, freeze_on_transport_error=False) as bob_backend_sock: with backend.event_bus.listen() as spy: rep = await user_revoke(alice_backend_sock, revoked_user_certificate=bob_revocation) assert rep == {"status": "ok"} await spy.wait_with_timeout( BackendEvent.USER_REVOKED, { "organization_id": bob.organization_id, "user_id": bob.user_id }, ) # `user.revoked` event schedules connection cancellation, so wait # for things to settle down to make sure the cancellation is done await trio.testing.wait_all_tasks_blocked() # Bob cannot send new command with pytest.raises(TransportError): await ping(bob_backend_sock)
async def new_for_user( self, organization_id: OrganizationID, greeter_user_id: UserID, claimer_email: str, created_on: Optional[DateTime] = None, ) -> UserInvitation: """ Raise: Nothing """ created_on = created_on or pendulum_now() async with self.dbh.pool.acquire() as conn, conn.transaction(): token = await _do_new_user_invitation( conn, organization_id=organization_id, greeter_user_id=greeter_user_id, claimer_email=claimer_email, created_on=created_on, ) return UserInvitation( greeter_user_id=greeter_user_id, greeter_human_handle=None, claimer_email=claimer_email, token=token, created_on=created_on, )
async def test_start_send_message_to_participants(backend, alice, bob, alice_backend_sock, bob_backend_sock, realm): await backend.realm.update_roles( alice.organization_id, RealmGrantedRole( certificate=b"<dummy>", realm_id=realm, user_id=bob.user_id, role=RealmRole.READER, granted_by=alice.device_id, ), ) with freeze_time("2000-01-02"): await realm_start_reencryption_maintenance(alice_backend_sock, realm, 2, pendulum_now(), { "alice": b"alice msg", "bob": b"bob msg" }) # Each participant should have received a message for user, sock in ((alice, alice_backend_sock), (bob, bob_backend_sock)): rep = await message_get(sock) assert rep == { "status": "ok", "messages": [{ "count": 1, "body": f"{user.user_id} msg".encode(), "timestamp": datetime(2000, 1, 2), "sender": alice.device_id, }], }
async def _apply_migration(conn, migration: MigrationItem): async with conn.transaction(): await conn.execute(migration.sql) if migration.idx >= CREATE_MIGRATION_TABLE_ID: # The migration table is created in the second migration sql = "INSERT INTO migration (_id, name, applied) VALUES ($1, $2, $3)" await conn.execute(sql, migration.idx, migration.name, pendulum_now())
async def upload_manifest(self, entry_id: EntryID, manifest: RemoteManifest): """ Raises: FSError FSRemoteSyncError FSBackendOfflineError FSWorkspaceInMaintenance FSBadEncryptionRevision """ assert manifest.author == self.device.device_id assert timestamps_in_the_ballpark(manifest.timestamp, pendulum_now()) workspace_entry = self.get_workspace_entry() try: ciphered = manifest.dump_sign_and_encrypt( key=workspace_entry.key, author_signkey=self.device.signing_key) except DataError as exc: raise FSError(f"Cannot encrypt vlob: {exc}") from exc # Upload the vlob if manifest.version == 1: await self._vlob_create(workspace_entry.encryption_revision, entry_id, ciphered, manifest.timestamp) else: await self._vlob_update( workspace_entry.encryption_revision, entry_id, ciphered, manifest.timestamp, manifest.version, )
async def test_reencryption_provide_unknown_vlob_atom_and_duplications( backend, alice, alice_backend_sock, realm, vlob_atoms ): await realm_start_reencryption_maintenance( alice_backend_sock, realm, 2, pendulum_now(), {alice.user_id: b"foo"} ) rep = await vlob_maintenance_get_reencryption_batch(alice_backend_sock, realm, 2) assert rep["status"] == "ok" assert len(rep["batch"]) == 3 unknown_vlob_id = VlobID.new() duplicated_vlob_id = rep["batch"][0]["vlob_id"] duplicated_version = rep["batch"][0]["version"] duplicated_expected_blob = rep["batch"][0]["blob"] reencrypted_batch = [ # Reencryption as identity *rep["batch"], # Add an unknown vlob {"vlob_id": unknown_vlob_id, "version": 1, "blob": b"ignored"}, # Valid vlob ID with invalid version {"vlob_id": duplicated_vlob_id, "version": 99, "blob": b"ignored"}, # Duplicate a vlob atom, should be ignored given the reencryption has already be done for it {"vlob_id": duplicated_vlob_id, "version": duplicated_version, "blob": b"ignored"}, ] # Another level of duplication ! for i in range(2): rep = await vlob_maintenance_save_reencryption_batch( alice_backend_sock, realm, 2, reencrypted_batch ) assert rep == {"status": "ok", "total": 3, "done": 3} # Finish the reencryption await realm_finish_reencryption_maintenance(alice_backend_sock, realm, 2) # Check the vlobs with pytest.raises(VlobNotFoundError): await backend.vlob.read( organization_id=alice.organization_id, author=alice.device_id, encryption_revision=2, vlob_id=unknown_vlob_id, ) with pytest.raises(VlobVersionError): await backend.vlob.read( organization_id=alice.organization_id, author=alice.device_id, encryption_revision=2, vlob_id=duplicated_vlob_id, version=99, ) _, content, _, _, _ = await backend.vlob.read( organization_id=alice.organization_id, author=alice.device_id, encryption_revision=2, vlob_id=duplicated_vlob_id, version=duplicated_version, ) assert content == duplicated_expected_blob
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)
async def bootstrap_organization( cmds: APIV1_BackendAnonymousCmds, human_handle: Optional[HumanHandle], device_label: Optional[str], ) -> LocalDevice: root_signing_key = SigningKey.generate() root_verify_key = root_signing_key.verify_key organization_addr = BackendOrganizationAddr.build( backend_addr=cmds.addr, organization_id=cmds.addr.organization_id, root_verify_key=root_verify_key, ) device = generate_new_device( organization_addr=organization_addr, profile=UserProfile.ADMIN, human_handle=human_handle, device_label=device_label, ) now = pendulum_now() user_certificate = UserCertificateContent( author=None, timestamp=now, user_id=device.user_id, human_handle=device.human_handle, public_key=device.public_key, profile=device.profile, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent( author=None, timestamp=now, device_id=device.device_id, device_label=device.device_label, verify_key=device.verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) user_certificate = user_certificate.dump_and_sign(root_signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign( root_signing_key) device_certificate = device_certificate.dump_and_sign(root_signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( root_signing_key) rep = await cmds.organization_bootstrap( organization_id=cmds.addr.organization_id, bootstrap_token=cmds.addr.token, root_verify_key=root_verify_key, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=redacted_user_certificate, redacted_device_certificate=redacted_device_certificate, ) _check_rep(rep, step_name="organization bootstrap") return device
async def _cancel_invitation(self): await backend.invite.delete( organization_id=self.author.organization_id, greeter=self.author.user_id, token=self.invitation_addr.token, on=pendulum_now(), reason=InvitationDeletedReason.CANCELLED, )
async def get_minimal_remote_manifest( self, entry_id: EntryID) -> Optional[RemoteManifest]: manifest = await self.local_storage.get_manifest(entry_id) if not manifest.is_placeholder: return None return manifest.base.evolve(author=self.local_author, timestamp=pendulum_now(), version=1)
async def _cancel_invitation(): await backend.invite.delete( organization_id=alice.organization_id, greeter=alice.user_id, token=invitation_addr.token, on=pendulum_now(), reason=InvitationDeletedReason.CANCELLED, )
def timestamp(self) -> DateTime: """This method centralizes the production of parsec timestamps for a given device. At the moment it is simply an alias to `pendulum.now` but it has two main benefits: 1. Allowing for easier testing by patching this method in device-sepecific way 2. Allowing for other implementation in the future allowing to track, check and possibly alter the production of timestamps. """ return pendulum_now()
def get_user(self, user_id: UserID, now: DateTime = None) -> Optional[UserCertificateContent]: now = now or pendulum_now() try: cached_on, verified_user = self._users_cache[user_id] if (now - cached_on).total_seconds() < self.cache_validity: return verified_user except KeyError: pass return None