def test_backend_organization_bootstrap_addr_bad_value(url, exc_msg): with pytest.raises(ValueError) as exc: BackendOrganizationBootstrapAddr.from_url(url) assert str(exc.value) == exc_msg
async def organization_bootstrap_addr(running_backend): org_id = OrganizationID("AnomalousMaterials") org_token = "123456" await running_backend.backend.organization.create(org_id, org_token) return BackendOrganizationBootstrapAddr.build(running_backend.addr, org_id, org_token)
async def initialize_test_organization( config_dir: Path, backend_address: BackendAddr, password: str, administration_token: str, force: bool, ) -> Tuple[LocalDevice, LocalDevice, LocalDevice]: configure_logging("WARNING") organization_id = OrganizationID("Org") # Create organization async with apiv1_backend_administration_cmds_factory( backend_address, administration_token ) as administration_cmds: rep = await administration_cmds.organization_create(organization_id) assert rep["status"] == "ok" bootstrap_token = rep["bootstrap_token"] organization_bootstrap_addr = BackendOrganizationBootstrapAddr.build( backend_address, organization_id, bootstrap_token ) # Bootstrap organization and Alice user async with apiv1_backend_anonymous_cmds_factory(organization_bootstrap_addr) as anonymous_cmds: alice_device = await bootstrap_organization( cmds=anonymous_cmds, human_handle=HumanHandle(label="Alice", email="*****@*****.**"), device_label="laptop", ) save_device_with_password(config_dir, alice_device, password, force=force) # Create a workspace for Alice config = load_config(config_dir, debug="DEBUG" in os.environ) async with logged_core_factory(config, alice_device) as core: alice_ws_id = await core.user_fs.workspace_create("alice_workspace") await core.user_fs.sync() # Register a new device for Alice other_alice_device = None async with backend_authenticated_cmds_factory( addr=alice_device.organization_addr, device_id=alice_device.device_id, signing_key=alice_device.signing_key, ) as alice_cmds: rep = await alice_cmds.invite_new(type=InvitationType.DEVICE) assert rep["status"] == "ok" invitation_addr = BackendInvitationAddr.build( backend_addr=alice_device.organization_addr, organization_id=alice_device.organization_id, invitation_type=InvitationType.DEVICE, token=rep["token"], ) async with backend_invited_cmds_factory(addr=invitation_addr) as invited_cmds: async def invite_task(): initial_ctx = DeviceGreetInitialCtx(cmds=alice_cmds, token=invitation_addr.token) in_progress_ctx = await initial_ctx.do_wait_peer() in_progress_ctx = await in_progress_ctx.do_wait_peer_trust() in_progress_ctx = await in_progress_ctx.do_signify_trust() in_progress_ctx = await in_progress_ctx.do_get_claim_requests() await in_progress_ctx.do_create_new_device( author=alice_device, device_label=in_progress_ctx.requested_device_label ) async def claim_task(): nonlocal other_alice_device initial_ctx = await claimer_retrieve_info(cmds=invited_cmds) in_progress_ctx = await initial_ctx.do_wait_peer() in_progress_ctx = await in_progress_ctx.do_signify_trust() in_progress_ctx = await in_progress_ctx.do_wait_peer_trust() other_alice_device = await in_progress_ctx.do_claim_device( requested_device_label="pc" ) async with trio.open_service_nursery() as nursery: nursery.start_soon(invite_task) nursery.start_soon(claim_task) save_device_with_password(config_dir, other_alice_device, password, force=force) # Invite Bob in bob_device = None rep = await alice_cmds.invite_new(type=InvitationType.USER, claimer_email="*****@*****.**") assert rep["status"] == "ok" invitation_addr = BackendInvitationAddr.build( backend_addr=alice_device.organization_addr, organization_id=alice_device.organization_id, invitation_type=InvitationType.USER, token=rep["token"], ) async with backend_invited_cmds_factory(addr=invitation_addr) as invited_cmds: async def invite_task(): initial_ctx = UserGreetInitialCtx(cmds=alice_cmds, token=invitation_addr.token) in_progress_ctx = await initial_ctx.do_wait_peer() in_progress_ctx = await in_progress_ctx.do_wait_peer_trust() in_progress_ctx = await in_progress_ctx.do_signify_trust() in_progress_ctx = await in_progress_ctx.do_get_claim_requests() await in_progress_ctx.do_create_new_user( author=alice_device, human_handle=in_progress_ctx.requested_human_handle, device_label=in_progress_ctx.requested_device_label, profile=UserProfile.STANDARD, ) async def claim_task(): nonlocal bob_device initial_ctx = await claimer_retrieve_info(cmds=invited_cmds) in_progress_ctx = await initial_ctx.do_wait_peer() in_progress_ctx = await in_progress_ctx.do_signify_trust() in_progress_ctx = await in_progress_ctx.do_wait_peer_trust() bob_device = await in_progress_ctx.do_claim_user( requested_human_handle=HumanHandle(label="Bob", email="*****@*****.**"), requested_device_label="laptop", ) async with trio.open_service_nursery() as nursery: nursery.start_soon(invite_task) nursery.start_soon(claim_task) save_device_with_password(config_dir, bob_device, password, force=force) # Create bob workspace and share with Alice async with logged_core_factory(config, bob_device) as core: bob_ws_id = await core.user_fs.workspace_create("bob_workspace") await core.user_fs.workspace_share(bob_ws_id, alice_device.user_id, WorkspaceRole.MANAGER) # Share Alice workspace with bob async with logged_core_factory(config, alice_device) as core: await core.user_fs.workspace_share(alice_ws_id, bob_device.user_id, WorkspaceRole.MANAGER) # Synchronize every device for device in (alice_device, other_alice_device, bob_device): async with logged_core_factory(config, device) as core: await core.user_fs.process_last_messages() await core.user_fs.sync() return (alice_device, other_alice_device, bob_device)
async def _do_bootstrap_organization( config_dir, password: str, password_check: str, user_id: str, device_name: str, bootstrap_addr: BackendOrganizationBootstrapAddr, ): if password != password_check: raise JobResultError("password-mismatch") if len(password) < 8: raise JobResultError("password-size") try: device_id = DeviceID(f"{user_id}@{device_name}") except ValueError as exc: raise JobResultError("bad-device_name") from exc root_signing_key = SigningKey.generate() root_verify_key = root_signing_key.verify_key organization_addr = bootstrap_addr.generate_organization_addr( root_verify_key) try: device = generate_new_device(device_id, organization_addr, is_admin=True) save_device_with_password(config_dir, device, password) except LocalDeviceAlreadyExistsError as exc: raise JobResultError("user-exists") from exc now = pendulum.now() user_certificate = UserCertificateContent( author=None, timestamp=now, user_id=device.user_id, public_key=device.public_key, is_admin=device.is_admin, ).dump_and_sign(root_signing_key) device_certificate = DeviceCertificateContent( author=None, timestamp=now, device_id=device_id, verify_key=device.verify_key).dump_and_sign(root_signing_key) try: async with backend_anonymous_cmds_factory(bootstrap_addr) as cmds: rep = await cmds.organization_bootstrap( bootstrap_addr.organization_id, bootstrap_addr.token, root_verify_key, user_certificate, device_certificate, ) if rep["status"] == "already_bootstrapped": raise JobResultError("already-bootstrapped", info=str(rep)) elif rep["status"] == "not_found": raise JobResultError("invalid-url", info=str(rep)) elif rep["status"] != "ok": raise JobResultError("refused-by-backend", info=str(rep)) return device, password except BackendConnectionRefused as exc: raise JobResultError("invalid-url", info=str(exc)) from exc except BackendNotAvailable as exc: raise JobResultError("backend-offline", info=str(exc)) from exc except BackendConnectionError as exc: raise JobResultError("refused-by-backend", info=str(exc)) from exc
async def test_create_organization_already_bootstrapped( aqtbot, running_backend, catch_create_org_widget, autoclose_dialog, gui, monkeypatch, organization_factory, local_device_factory, alice, ): org = organization_factory() backend_user, backend_first_device = local_device_to_backend_user(alice, org) bootstrap_token = "123456" await running_backend.backend.organization.create(org.organization_id, bootstrap_token, None) await running_backend.backend.organization.bootstrap( org.organization_id, backend_user, backend_first_device, bootstrap_token, org.root_verify_key, ) org_bs_addr = BackendOrganizationBootstrapAddr.build( running_backend.addr, org.organization_id, bootstrap_token ) monkeypatch.setattr( "parsec.core.gui.main_window.get_text_input", lambda *args, **kwargs: (str(org_bs_addr)) ) # The org bootstrap window is usually opened using a sub-menu. # Sub-menus can be a bit challenging to open in tests so we cheat # using the keyboard shortcut Ctrl+O that has the same effect. await aqtbot.key_click(gui, "o", QtCore.Qt.ControlModifier, 200) co_w = await catch_create_org_widget() await aqtbot.wait_until(co_w.user_widget.isVisible) await aqtbot.key_clicks(co_w.user_widget.line_edit_user_full_name, "Gordon Freeman") await aqtbot.key_clicks(co_w.user_widget.line_edit_user_email, "*****@*****.**") def _user_widget_ready(): assert co_w.user_widget.line_edit_org_name.text() == org.organization_id assert co_w.user_widget.line_edit_org_name.isReadOnly() is True await aqtbot.wait_until(_user_widget_ready) await aqtbot.mouse_click(co_w.user_widget.check_accept_contract, QtCore.Qt.LeftButton) await aqtbot.mouse_click(co_w.button_validate, QtCore.Qt.LeftButton) await aqtbot.wait_until(co_w.device_widget.isVisible) await aqtbot.key_clicks(co_w.device_widget.line_edit_device, "HEV") await aqtbot.key_clicks(co_w.device_widget.widget_password.line_edit_password, "nihilanth") await aqtbot.key_clicks( co_w.device_widget.widget_password.line_edit_password_check, "nihilanth" ) await aqtbot.mouse_click(co_w.button_validate, QtCore.Qt.LeftButton) def _modal_shown(): assert autoclose_dialog.dialogs == [("Error", "This bootstrap link was already used.")] await aqtbot.wait_until(_modal_shown)
async def _on_next_clicked(self): if isinstance(self.current_widget, CreateOrgUserInfoWidget): backend_addr = None org_id = None device_label = None human_handle = None if self.start_addr: backend_addr = self.start_addr else: try: org_id = OrganizationID( self.current_widget.line_edit_org_name.text()) except ValueError as exc: show_error(self, _("TEXT_ORG_WIZARD_INVALID_ORGANIZATION_ID"), exception=exc) return try: backend_addr = BackendOrganizationBootstrapAddr.build( backend_addr=self.current_widget.backend_addr if self.current_widget.radio_use_custom.isChecked() else self.config.preferred_org_creation_backend_addr, organization_id=org_id, ) except ValueError as exc: show_error(self, _("TEXT_ORG_WIZARD_INVALID_BACKEND_ADDR"), exception=exc) return try: user_name = validators.trim_user_name( self.current_widget.line_edit_user_full_name.text()) human_handle = HumanHandle( self.current_widget.line_edit_user_email.text(), user_name) except ValueError as exc: show_error(self, _("TEXT_ORG_WIZARD_INVALID_HUMAN_HANDLE"), exception=exc) return # No try/except given `self.current_widget.line_edit_device` has already been validated against `DeviceLabel` try: device_label = DeviceLabel( self.current_widget.line_edit_device.text()) except ValueError as exc: show_error(self, _("TEXT_ORG_WIZARD_INVALID_DEVICE_LABEL"), exception=exc) return # TODO: call `await _do_create_org` directly since the context is now async self.create_job = self.jobs_ctx.submit_job( self.req_success, self.req_error, _do_create_org, config=self.config, human_handle=human_handle, device_label=device_label, backend_addr=backend_addr, ) self.dialog.button_close.setVisible(False) self.button_validate.setEnabled(False) else: auth_method = self.current_widget.get_auth_method() try: if auth_method == DeviceFileType.PASSWORD: save_device_with_password_in_config( self.config.config_dir, self.new_device, self.current_widget.get_auth()) elif auth_method == DeviceFileType.SMARTCARD: await save_device_with_smartcard_in_config( self.config.config_dir, self.new_device) self.status = (self.new_device, auth_method, self.current_widget.get_auth()) if self.dialog: self.dialog.accept() elif QApplication.activeModalWidget(): QApplication.activeModalWidget().accept() else: logger.warning("Cannot close dialog when org wizard") except LocalDeviceCryptoError as exc: if auth_method == DeviceFileType.SMARTCARD: show_error(self, _("TEXT_INVALID_SMARTCARD"), exception=exc) except LocalDeviceNotFoundError as exc: if auth_method == DeviceFileType.PASSWORD: show_error(self, _("TEXT_CANNOT_SAVE_DEVICE"), exception=exc) except LocalDeviceError as exc: show_error(self, _("TEXT_CANNOT_SAVE_DEVICE"), exception=exc)
async def initialize_test_organization( config_dir: Path, backend_address: BackendAddr, password: str, administration_token: str, additional_users_number: int, additional_devices_number: int, ) -> Tuple[LocalDevice, LocalDevice, LocalDevice]: organization_id = OrganizationID("Org") config = load_config(config_dir, debug="DEBUG" in os.environ) # Create organization bootstrap_token = await create_organization_req( organization_id, backend_address, administration_token ) organization_bootstrap_addr = BackendOrganizationBootstrapAddr.build( backend_address, organization_id, bootstrap_token ) # Bootstrap organization and Alice user and create device "laptop" for Alice alice_device = await bootstrap_organization( organization_bootstrap_addr, human_handle=HumanHandle(label="Alice", email="*****@*****.**"), device_label=DeviceLabel("laptop"), ) await user_storage_non_speculative_init(data_base_dir=config.data_base_dir, device=alice_device) save_device_with_password_in_config( config_dir=config_dir, device=alice_device, password=password ) # Create context manager, alice_core will be needed for the rest of the script async with logged_core_factory(config, alice_device) as alice_core: async with backend_authenticated_cmds_factory( addr=alice_device.organization_addr, device_id=alice_device.device_id, signing_key=alice_device.signing_key, ) as alice_cmds: # Create new device "pc" for Alice other_alice_device = await _register_new_device( cmds=alice_cmds, author=alice_device, device_label=DeviceLabel("pc") ) save_device_with_password_in_config( config_dir=config_dir, device=other_alice_device, password=password ) # Invite Bob in organization bob_device = await _register_new_user( cmds=alice_cmds, author=alice_device, device_label=DeviceLabel("laptop"), human_handle=HumanHandle(email="*****@*****.**", label="Bob"), profile=UserProfile.STANDARD, ) await user_storage_non_speculative_init( data_base_dir=config.data_base_dir, device=bob_device ) save_device_with_password_in_config( config_dir=config_dir, device=bob_device, password=password ) # Invite Toto in organization toto_device = await _register_new_user( cmds=alice_cmds, author=alice_device, device_label=DeviceLabel("laptop"), human_handle=HumanHandle(email="*****@*****.**", label="Toto"), profile=UserProfile.OUTSIDER, ) await user_storage_non_speculative_init( data_base_dir=config.data_base_dir, device=toto_device ) save_device_with_password_in_config( config_dir=config_dir, device=toto_device, password=password ) # Create Alice workspace alice_ws_id = await alice_core.user_fs.workspace_create(EntryName("alice_workspace")) # Create context manager async with logged_core_factory(config, bob_device) as bob_core: # Create Bob workspace bob_ws_id = await bob_core.user_fs.workspace_create(EntryName("bob_workspace")) # Bob share workspace with Alice await bob_core.user_fs.workspace_share( bob_ws_id, alice_device.user_id, WorkspaceRole.MANAGER ) # Alice share workspace with Bob await alice_core.user_fs.workspace_share( alice_ws_id, bob_device.user_id, WorkspaceRole.MANAGER ) # Add additional random users await _add_random_users( cmds=alice_cmds, author=alice_device, alice_core=alice_core, bob_core=bob_core, alice_ws_id=alice_ws_id, bob_ws_id=bob_ws_id, additional_users_number=additional_users_number, ) # Add additional random device for alice await _add_random_device( cmds=alice_cmds, device=alice_device, additional_devices_number=additional_devices_number, ) # Synchronize every device for device in (alice_device, other_alice_device, bob_device): async with logged_core_factory(config, device) as core: await core.user_fs.process_last_messages() await core.user_fs.sync() return (alice_device, other_alice_device, bob_device)
async def initialize_test_organization( backend_address, organization_id, alice_device_id, bob_device_id, other_device_name, alice_workspace, bob_workspace, password, administration_token, force, ): configure_logging("WARNING") config_dir = get_default_config_dir(os.environ) alice_slugid = f"{organization_id}:{alice_device_id}" bob_slugid = f"{organization_id}:{bob_device_id}" # Create organization async with backend_administration_cmds_factory( backend_address, administration_token) as cmds: rep = await cmds.organization_create(organization_id) assert rep["status"] == "ok" bootstrap_token = rep["bootstrap_token"] organization_bootstrap_addr = BackendOrganizationBootstrapAddr.build( backend_address, organization_id, bootstrap_token) # Bootstrap organization and Alice user async with backend_anonymous_cmds_factory( organization_bootstrap_addr) as cmds: root_signing_key = SigningKey.generate() root_verify_key = root_signing_key.verify_key organization_addr = organization_bootstrap_addr.generate_organization_addr( root_verify_key) alice_device = generate_new_device(alice_device_id, organization_addr, True) save_device_with_password(config_dir, alice_device, password, force=force) now = pendulum.now() user_certificate = UserCertificateContent( author=None, timestamp=now, user_id=alice_device.user_id, public_key=alice_device.public_key, is_admin=True, ).dump_and_sign(author_signkey=root_signing_key) device_certificate = DeviceCertificateContent( author=None, timestamp=now, device_id=alice_device.device_id, verify_key=alice_device.verify_key, ).dump_and_sign(author_signkey=root_signing_key) rep = await cmds.organization_bootstrap( organization_bootstrap_addr.organization_id, organization_bootstrap_addr.token, root_verify_key, user_certificate, device_certificate, ) assert rep["status"] == "ok" # Create a workspace for Alice config = load_config(config_dir, debug="DEBUG" in os.environ) async with logged_core_factory(config, alice_device) as core: alice_ws_id = await core.user_fs.workspace_create(f"{alice_workspace}") await core.user_fs.sync() # Register a new device for Alice token = generate_invitation_token() other_alice_device_id = DeviceID( f"{alice_device.user_id}@{other_device_name}") other_alice_slugid = f"{organization_id}:{other_alice_device_id}" async def invite_task(): await invite_and_create_device(alice_device, other_device_name, token) other_alice_device = None async def claim_task(): nonlocal other_alice_device other_alice_device = await retry_claim(claim_device, alice_device.organization_addr, other_alice_device_id, token) save_device_with_password(config_dir, other_alice_device, password, force=force) async with trio.open_service_nursery() as nursery: nursery.start_soon(invite_task) nursery.start_soon(claim_task) # Invite Bob in token = generate_invitation_token() bob_device = None async def invite_task(): await invite_and_create_user(alice_device, bob_device_id.user_id, token, is_admin=False) async def claim_task(): nonlocal bob_device bob_device = await retry_claim(claim_user, alice_device.organization_addr, bob_device_id, token) save_device_with_password(config_dir, bob_device, password, force=force) async with trio.open_service_nursery() as nursery: nursery.start_soon(invite_task) nursery.start_soon(claim_task) # Create bob workspace and share with Alice async with logged_core_factory(config, bob_device) as core: bob_ws_id = await core.user_fs.workspace_create(f"{bob_workspace}") await core.user_fs.workspace_share(bob_ws_id, alice_device_id.user_id, WorkspaceRole.MANAGER) # Share Alice workspace with bob async with logged_core_factory(config, alice_device) as core: await core.user_fs.workspace_share(alice_ws_id, bob_device_id.user_id, WorkspaceRole.MANAGER) # Synchronize every device for device in (alice_device, other_alice_device, bob_device): async with logged_core_factory(config, device) as core: await core.user_fs.process_last_messages() await core.user_fs.sync() return alice_slugid, other_alice_slugid, bob_slugid
async def test_create_organization_with_boostrap_token( gui, aqtbot, running_backend, catch_create_org_widget, autoclose_dialog ): # Firt create the organization bootstrap_token = "T0k3n" organization_id = OrganizationID("AnomalousMaterials") await running_backend.backend.organization.create( id=organization_id, bootstrap_token=bootstrap_token ) good_bootstrap_addr = BackendOrganizationBootstrapAddr.build( running_backend.addr, organization_id, bootstrap_token ) bad_bootstrap_addr = BackendOrganizationBootstrapAddr.build( running_backend.addr, organization_id, "B@dT0k3n" ) # Now try to use the wrong bootstrap link for bootstrap_addr in (bad_bootstrap_addr, good_bootstrap_addr): autoclose_dialog.reset() await aqtbot.run(gui.add_instance, bootstrap_addr.to_url()) co_w = await catch_create_org_widget() assert co_w def _user_widget_ready(): assert co_w.user_widget.isVisible() assert not co_w.device_widget.isVisible() assert not co_w.button_previous.isVisible() assert not co_w.button_validate.isEnabled() # Organization name and server address should be already provided assert co_w.user_widget.line_edit_org_name.isReadOnly() assert not co_w.user_widget.radio_use_custom.isEnabled() await aqtbot.wait_until(_user_widget_ready) await aqtbot.key_clicks(co_w.user_widget.line_edit_user_full_name, "Gordon Freeman") await aqtbot.key_clicks( co_w.user_widget.line_edit_user_email, "*****@*****.**" ) await aqtbot.mouse_click(co_w.user_widget.check_accept_contract, QtCore.Qt.LeftButton) def _user_widget_button_validate_ready(): assert co_w.button_validate.isEnabled() await aqtbot.wait_until(_user_widget_button_validate_ready) await aqtbot.mouse_click(co_w.button_validate, QtCore.Qt.LeftButton) def _device_widget_ready(): assert not co_w.user_widget.isVisible() assert co_w.device_widget.isVisible() assert co_w.button_previous.isVisible() assert not co_w.button_validate.isEnabled() await aqtbot.wait_until(_device_widget_ready) await aqtbot.key_clicks(co_w.device_widget.line_edit_device, "HEV") await aqtbot.key_clicks(co_w.device_widget.widget_password.line_edit_password, "nihilanth") await aqtbot.key_clicks( co_w.device_widget.widget_password.line_edit_password_check, "nihilanth" ) def _device_widget_button_validate_ready(): assert co_w.button_validate.isEnabled() await aqtbot.wait_until(_device_widget_button_validate_ready) await aqtbot.mouse_click(co_w.button_validate, QtCore.Qt.LeftButton) if bootstrap_addr is bad_bootstrap_addr: def _error_modal_shown(): assert autoclose_dialog.dialogs == [("Error", "This bootstrap link is invalid.")] await aqtbot.wait_until(_error_modal_shown) else: def _modal_shown(): assert autoclose_dialog.dialogs == [ ( "", "You organization <b>AnomalousMaterials</b> has been created!<br />\n<br />\n" "You will now be automatically logged in.<br />\n<br />\n" "To help you start with PARSEC, you can read the " '<a href="https://docs.parsec.cloud/en/stable/" title="User guide">user guide</a>.', ) ] await aqtbot.wait_until(_modal_shown)