def wrapper(**kwargs): config = kwargs["config"] password = kwargs["password"] organization_id, device_id, slugname = kwargs["device"] devices = [ (o, d, t, kf) for o, d, t, kf in list_available_devices(config.config_dir) if (not organization_id or o == organization_id) and d == device_id ] if not devices: raise SystemExit(f"Device `{slugname}` not found") elif len(devices) > 1: found = "\n".join([str(kf.parent) for *_, kf in devices]) raise SystemExit( f"Multiple devices found for `{slugname}`:\n{found}") else: _, _, cipher, key_file = devices[0] try: if cipher != "password": raise SystemExit( f"Device {slugname} is ciphered with {cipher}.") if password is None: password = click.prompt("password", hide_input=True) device = load_device_with_password(key_file, password) except LocalDeviceError as exc: raise SystemExit(f"Cannot load device {slugname}: {exc}") kwargs["device"] = device return fn(**kwargs)
def wrapper(**kwargs): config = kwargs["config"] password = kwargs["password"] device_slughash = kwargs.pop("device") all_available_devices = list_available_devices(config.config_dir) devices = [] for device in all_available_devices: if device.slughash.startswith(device_slughash): devices.append(device) if not devices: raise SystemExit( f"Device `{device_slughash}` not found, available devices:\n" f"{format_available_devices(all_available_devices)}") elif len(devices) > 1: raise SystemExit( f"Multiple devices found for `{device_slughash}`:\n" f"{format_available_devices(devices)}") try: if password is None: password = click.prompt("password", hide_input=True) device = load_device_with_password(devices[0].key_file_path, password) except LocalDeviceError as exc: raise SystemExit(f"Cannot load device {device_slughash}: {exc}") kwargs["device"] = device return fn(**kwargs)
def showMaximized(self, skip_dialogs=False): super().showMaximized() QCoreApplication.processEvents() # Used with the --diagnose option if skip_dialogs: return # At the very first launch if self.config.gui_first_launch: r = ask_question( self, _("TEXT_ERROR_REPORTING_TITLE"), _("TEXT_ERROR_REPORTING_INSTRUCTIONS"), [_("ACTION_ERROR_REPORTING_ACCEPT"), _("ACTION_NO")], ) # Acknowledge the changes self.event_bus.send( "gui.config.changed", gui_first_launch=False, gui_last_version=PARSEC_VERSION, telemetry_enabled=r == _("ACTION_ERROR_REPORTING_ACCEPT"), ) # For each parsec update if self.config.gui_last_version and self.config.gui_last_version != PARSEC_VERSION: # Update from parsec `<1.14` to `>=1.14` if LooseVersion(self.config.gui_last_version) < "1.14": # Revert the acrobat reader workaround if ( platform.system() == "Windows" and win_registry.is_acrobat_reader_dc_present() and not win_registry.get_acrobat_app_container_enabled() ): win_registry.del_acrobat_app_container_enabled() # Acknowledge the changes self.event_bus.send("gui.config.changed", gui_last_version=PARSEC_VERSION) telemetry.init(self.config) devices = list_available_devices(self.config.config_dir) if not len(devices): r = ask_question( self, _("TEXT_KICKSTART_PARSEC_WHAT_TO_DO_TITLE"), _("TEXT_KICKSTART_PARSEC_WHAT_TO_DO_INSTRUCTIONS"), [ _("ACTION_NO_DEVICE_CREATE_ORGANIZATION"), _("ACTION_NO_DEVICE_JOIN_ORGANIZATION"), ], radio_mode=True, ) if r == _("ACTION_NO_DEVICE_JOIN_ORGANIZATION"): self._on_join_org_clicked() elif r == _("ACTION_NO_DEVICE_CREATE_ORGANIZATION"): self._on_create_org_clicked()
def _retrieve_device_slughash(device_id): availables = list_available_devices(Path(config_dir)) for available in availables: if available.device_id == device_id: return available.slughash else: assert False, f"`{device_id}` not among {availables}"
async def test_login_no_available_devices(aqtbot, gui_factory, autoclose_dialog, core_config, alice): password = "******" save_device_with_password_in_config(core_config.config_dir, alice, password) device = list_available_devices(core_config.config_dir)[0] gui = await gui_factory() ParsecApp.add_connected_device(device.organization_id, device.device_id) lw = gui.test_get_login_widget() lw.reload_devices() def _devices_listed(): assert lw.widget.layout().count() > 0 await aqtbot.wait_until(_devices_listed) no_device_w = lw.widget.layout().itemAt(0).widget() assert isinstance(no_device_w, LoginNoDevicesWidget) # 0 is spacer, 1 is label assert no_device_w.layout().itemAt( 2).widget().text() == "Create an organization" assert no_device_w.layout().itemAt( 3).widget().text() == "Join an organization" assert no_device_w.layout().itemAt(4).widget().text() == "Recover a device"
def reload_devices(self): while self.combo_username.count(): self.combo_username.removeItem(0) # Display devices in `<organization>:<user>@<device>` format self.devices = {} for available_device in list_available_devices(self.config.config_dir): if not ParsecApp.is_device_connected( available_device.organization_id, available_device.device_id): name = f"{available_device.organization_id}: {available_device.user_display} @ {available_device.device_display}" self.combo_username.addItem(name) self.devices[name] = available_device last_device = self.config.gui_last_device if last_device and last_device in self.devices: self.combo_username.setCurrentText(last_device) if len(self.devices): self.widget_no_device.hide() self.widget_login.show() else: self.widget_no_device.show() self.widget_login.hide() if ParsecApp.connected_devices: self.label_no_device.setText( _("TEXT_LOGIN_NO_AVAILABLE_DEVICE")) else: self.label_no_device.setText( _("TEXT_LOGIN_NO_DEVICE_ON_MACHINE"))
def test_multiple_files_same_device(config_dir, alice): path = save_device_with_password(config_dir, alice, "test") # File names contain the slughash assert path.stem == alice.slughash # .. but are no longer meaningful (path.parent / "testing.keys").write_bytes(path.read_bytes()) # Make sure we don't list duplicates devices = list_available_devices(config_dir) assert len(devices) == 1 # Remove orignal file path.unlink() devices = list_available_devices(config_dir) assert len(devices) == 1
def list_devices(config_dir): config_dir = Path(config_dir) if config_dir else get_default_config_dir( os.environ) devices = list_available_devices(config_dir) num_devices_display = click.style(str(len(devices)), fg="green") config_dir_display = click.style(str(config_dir), fg="yellow") click.echo( f"Found {num_devices_display} device(s) in {config_dir_display}:") click.echo(format_available_devices(devices))
def test_list_devices_support_key_file(config_dir, type): if type == "password": data_extra = {"type": "password", "salt": b"12345"} available_device_extra = {"type": DeviceFileType.PASSWORD} elif type == "smartcard": data_extra = { "type": "smartcard", "encrypted_key": b"12345", "certificate_id": "42", "certificate_sha1": b"12345", } available_device_extra = {"type": DeviceFileType.SMARTCARD} # Device information user_id = uuid4().hex device_name = uuid4().hex organization_id = "Org" rvk_hash = (uuid4().hex)[:10] device_id = f"{user_id}@{device_name}" slug = f"{rvk_hash}#{organization_id}#{device_id}" human_label = "Billy Mc BillFace" human_email = "*****@*****.**" device_label = "My device" # Craft file data key_file_data = packb({ "ciphertext": b"whatever", "human_handle": (human_email.encode(), human_label.encode()), "device_label": device_label, "device_id": device_id, "organization_id": organization_id, "slug": slug, **data_extra, }) key_file_path = get_devices_dir(config_dir) / "device.keys" key_file_path.parent.mkdir(parents=True) key_file_path.write_bytes(key_file_data) devices = list_available_devices(config_dir) expected_device = AvailableDevice( key_file_path=key_file_path, organization_id=OrganizationID(organization_id), device_id=DeviceID(device_id), human_handle=HumanHandle(human_email, human_label), device_label=DeviceLabel(device_label), slug=slug, **available_device_extra, ) assert devices == [expected_device] assert get_key_file(config_dir, expected_device) == key_file_path
def test_run_testenv(run_testenv): available_devices = list_available_devices(run_testenv.config_dir) devices = [(d.human_handle.label, str(d.device_label)) for d in available_devices] assert sorted(devices) == [ ("Alice", "laptop"), ("Alice", "pc"), ("Bob", "laptop"), ("Toto", "laptop"), ]
def list_devices(config_dir: Path, debug: bool, **kwargs) -> None: with cli_exception_handler(debug): config_dir = Path( config_dir) if config_dir else get_default_config_dir(os.environ) devices = list_available_devices(config_dir) num_devices_display = click.style(str(len(devices)), fg="green") config_dir_display = click.style(str(config_dir), fg="yellow") click.echo( f"Found {num_devices_display} device(s) in {config_dir_display}:") click.echo(format_available_devices(devices))
def list_devices(config_dir): config_dir = Path(config_dir) if config_dir else get_default_config_dir( os.environ) devices = list_available_devices(config_dir) num_devices_display = click.style(str(len(devices)), fg="green") config_dir_display = click.style(str(config_dir), fg="yellow") click.echo( f"Found {num_devices_display} device(s) in {config_dir_display}:") for org, device, cipher, _ in devices: device_display = click.style(f"{org}:{device}", fg="yellow") click.echo(f"{device_display} (cipher: {cipher})")
def reload_devices(self): while self.combo_username.count(): self.combo_username.removeItem(0) devices = list_available_devices(self.config.config_dir) # Display devices in `<organization>:<device_id>` format self.devices = {} for o, d, t, kf in devices: self.combo_username.addItem(f"{o}:{d}") self.devices[f"{o}:{d}"] = (o, d, t, kf) last_device = self.config.gui_last_device if last_device and last_device in self.devices: self.combo_username.setCurrentText(last_device)
def test_password_save_already_existing(config_dir, alice, alice2, otheralice): save_device_with_password_in_config(config_dir, alice, "S3Cr37") # Different devices should not overwrite each other save_device_with_password_in_config(config_dir, otheralice, "S3Cr37") save_device_with_password_in_config(config_dir, alice2, "S3Cr37") # Overwritting self is allowed save_device_with_password_in_config(config_dir, alice, "S3Cr37") devices = list_available_devices(config_dir) assert len(devices) == 3
def reload_devices(self): layout = self.scroll_content.layout() for _ in range(layout.count()): item = self.scroll_content.layout().takeAt(0) layout.removeItem(item) w = item.widget() if w is not None: w.setParent(None) devices = list_available_devices(self.config.config_dir) for device in devices: w = KeyWidget(device, parent=self) w.export_clicked.connect(self._on_export_key) self.scroll_content.layout().insertWidget(self.scroll_content.layout().count() - 1, w)
def _find_device_from_addr(self, action_addr, display_error=False): device = None for available_device in list_available_devices(self.config.config_dir): if available_device.organization_id == action_addr.organization_id: device = available_device break if device is None: show_error( self, _("TEXT_FILE_LINK_NOT_IN_ORG_organization").format( organization=action_addr.organization_id), ) return device
def _on_manage_keys(self) -> None: devices = [device for device in list_available_devices(self.config.config_dir)] options = [_("ACTION_CANCEL"), _("ACTION_RECOVER_DEVICE")] if len(devices): options.append(_("ACTION_CREATE_RECOVERY_DEVICE")) result = ask_question( self, _("TEXT_DEVICE_RECOVERY_TITLE"), _("TEXT_DEVICE_RECOVERY_QUESTION"), options ) if result == _("ACTION_RECOVER_DEVICE"): DeviceRecoveryImportWidget.show_modal( self.config, self.jobs_ctx, parent=self, on_finished=self.reload_login_devices ) elif result == _("ACTION_CREATE_RECOVERY_DEVICE"): DeviceRecoveryExportWidget.show_modal(self.config, self.jobs_ctx, devices, parent=self)
def switch_to_login_tab( self, file_link_addr: Optional[BackendOrganizationFileLinkAddr] = None ) -> None: # Retrieve the login tab idx = self._get_login_tab_index() if idx != -1: self.switch_to_tab(idx) else: # No loging tab, create one tab = self.add_new_tab() tab.show_login_widget() self.on_tab_state_changed(tab, "login") idx = self.tab_center.count() - 1 self.switch_to_tab(idx) if not file_link_addr: # We're done here return # Find the device corresponding to the organization in the link for available_device in list_available_devices(self.config.config_dir): if available_device.organization_id == file_link_addr.organization_id: break else: # Cannot reach this organization with our available devices show_error( self, _("TEXT_FILE_LINK_NOT_IN_ORG_organization").format( organization=file_link_addr.organization_id ), ) return # Pre-select the corresponding device login_w = self.tab_center.widget(idx).get_login_widget() login_w._on_account_clicked(available_device) # Set the path instance_widget = self.tab_center.widget(idx) instance_widget.set_workspace_path(file_link_addr) # Prompt the user for the need to log in first show_info( self, _("TEXT_FILE_LINK_PLEASE_LOG_IN_organization").format( organization=file_link_addr.organization_id ), )
def test_list_devices_support_legacy_file_with_meaningful_name(config_dir): # Legacy path might exceed the 256 characters limit in some cases (see issue #1356) # So we use the `\\?\` workaround: https://stackoverflow.com/a/57502760/2846140 if sys.platform == "win32": config_dir = Path("\\\\?\\" + str(config_dir.resolve())) # Device information user_id = uuid4().hex device_name = uuid4().hex organization_id = "Org" rvk_hash = (uuid4().hex)[:10] device_id = f"{user_id}@{device_name}" slug = f"{rvk_hash}#{organization_id}#{device_id}" human_label = "Billy Mc BillFace" human_email = "*****@*****.**" device_label = "My device" # Craft file data without the user_id, organization_id and slug fields key_file_data = packb({ "type": "password", "salt": b"12345", "ciphertext": b"whatever", "human_handle": (human_email.encode(), human_label.encode()), "device_label": device_label, }) key_file_path = get_devices_dir(config_dir) / slug / f"{slug}.keys" key_file_path.parent.mkdir(parents=True) key_file_path.write_bytes(key_file_data) devices = list_available_devices(config_dir) expected_device = AvailableDevice( key_file_path=key_file_path, organization_id=OrganizationID(organization_id), device_id=DeviceID(device_id), human_handle=HumanHandle(human_email, human_label), device_label=DeviceLabel(device_label), slug=slug, type=DeviceFileType.PASSWORD, ) assert devices == [expected_device] assert get_key_file(config_dir, expected_device) == key_file_path
def test_list_devices(organization_factory, local_device_factory, config_dir): org1 = organization_factory("org1") org2 = organization_factory("org2") o1d11 = local_device_factory("d1@1", org1) o1d12 = local_device_factory("d1@2", org1) o1d21 = local_device_factory("d2@1", org1) o2d11 = local_device_factory("d1@1", org2, has_human_handle=False) o2d12 = local_device_factory("d1@2", org2, has_device_label=False) o2d21 = local_device_factory("d2@1", org2, has_human_handle=False, has_device_label=False) for device in [o1d11, o1d12, o1d21]: save_device_with_password_in_config(config_dir, device, "S3Cr37") for device in [o2d11, o2d12, o2d21]: save_device_with_password_in_config(config_dir, device, "secret") # Also add dummy stuff that should be ignored device_dir = config_dir / "devices" (device_dir / "bad1").touch() (device_dir / "373955f566#corp#bob@laptop").mkdir() dummy_slug = "a54ed6df3a#corp#alice@laptop" (device_dir / dummy_slug).mkdir() (device_dir / dummy_slug / f"{dummy_slug}.keys").write_bytes(b"dummy") devices = list_available_devices(config_dir) expected_devices = { AvailableDevice( key_file_path=get_key_file(config_dir, d), organization_id=d.organization_id, device_id=d.device_id, human_handle=d.human_handle, device_label=d.device_label, slug=d.slug, type=DeviceFileType.PASSWORD, ) for d in [o1d11, o1d12, o1d21, o2d11, o2d12, o2d21] } assert set(devices) == expected_devices
async def main(): # Config config_dir = get_default_config_dir(os.environ) config = load_config(config_dir) devices = list_available_devices(config_dir) key_file = next(key_file for _, device_id, _, key_file in devices if device_id == DEVICE_ID) device = load_device_with_password(key_file, PASSWORD) # Log in async with logged_core_factory(config, device) as core: # Get workspace user_manifest = core.user_fs.get_user_manifest() workspace_entry = user_manifest.workspaces[0] workspace = core.user_fs.get_workspace(workspace_entry.id) # await make_workspace_dir_inconsistent(device, workspace, "/bar") await make_workspace_dir_simple_versions(device, workspace, "/foo")
def test_list_devices(organization_factory, local_device_factory, config_dir): org1 = organization_factory("org1") org2 = organization_factory("org2") o1d11 = local_device_factory("d1@1", org1) o1d12 = local_device_factory("d1@2", org1) o1d21 = local_device_factory("d2@1", org1) o2d11 = local_device_factory("d1@1", org2) o2d12 = local_device_factory("d1@2", org2) o2d21 = local_device_factory("d2@1", org2) for device in [o1d11, o1d12, o1d21]: save_device_with_password(config_dir, device, "S3Cr37") for device in [o2d11, o2d12, o2d21]: save_device_with_password(config_dir, device, "secret") # Also add dummy stuff that should be ignored (config_dir / "bad1").touch() (config_dir / "373955f566#corp#bob@laptop").mkdir() dummy_slug = "a54ed6df3a#corp#alice@laptop" (config_dir / dummy_slug).mkdir() (config_dir / dummy_slug / f"{dummy_slug}.keys").write_bytes(b"dummy") devices = list_available_devices(config_dir) assert set(devices) == { (o1d11.organization_id, o1d11.device_id, "password", get_key_file(config_dir, o1d11)), (o1d12.organization_id, o1d12.device_id, "password", get_key_file(config_dir, o1d12)), (o1d21.organization_id, o1d21.device_id, "password", get_key_file(config_dir, o1d21)), (o2d11.organization_id, o2d11.device_id, "password", get_key_file(config_dir, o2d11)), (o2d12.organization_id, o2d12.device_id, "password", get_key_file(config_dir, o2d12)), (o2d21.organization_id, o2d21.device_id, "password", get_key_file(config_dir, o2d21)), }
def test_list_devices_support_legacy_file_without_labels(config_dir): # Craft file data without the labels fields key_file_data = packb({ "type": "password", "salt": b"12345", "ciphertext": b"whatever" }) slug = "9d84fbd57a#Org#Zack@PC1" key_file_path = get_devices_dir(config_dir) / slug / f"{slug}.keys" key_file_path.parent.mkdir(parents=True) key_file_path.write_bytes(key_file_data) devices = list_available_devices(config_dir) expected_device = AvailableDevice( key_file_path=key_file_path, organization_id=OrganizationID("Org"), device_id=DeviceID("Zack@PC1"), human_handle=None, device_label=None, ) assert devices == [expected_device]
async def test_login_no_available_devices(aqtbot, gui_factory, autoclose_dialog, core_config, alice, qt_thread_gateway): password = "******" save_device_with_password(core_config.config_dir, alice, password) device = list_available_devices(core_config.config_dir)[0] gui = await gui_factory() ParsecApp.add_connected_device(device.organization_id, device.device_id) lw = gui.test_get_login_widget() def _reload_devices(): lw.reload_devices() await qt_thread_gateway.send_action(_reload_devices) no_device_w = lw.widget.layout().itemAt(0).widget() assert isinstance(no_device_w, LoginNoDevicesWidget)
def list_devices_and_enrollments(self): pendings = PkiEnrollmentSubmitterSubmittedCtx.list_from_disk( config_dir=self.config.config_dir) devices = [ device for device in list_available_devices(self.config.config_dir) if not ParsecApp.is_device_connected(device.organization_id, device.device_id) ] if not len(devices) and not len(pendings): no_device_widget = LoginNoDevicesWidget() no_device_widget.create_organization_clicked.connect( self.create_organization_clicked.emit) no_device_widget.join_organization_clicked.connect( self.join_organization_clicked.emit) no_device_widget.recover_device_clicked.connect( self.recover_device_clicked.emit) self.widget.layout().addWidget(no_device_widget) no_device_widget.setFocus() elif len(devices) == 1 and not len(pendings): self._on_account_clicked(devices[0], hide_back=True) else: # If the GUI has a last used device, we look for it in our devices list # and insert it to the front, so it will be shown first if self.config.gui_last_device: last_used = next( (d for d in devices if d.device_id.str == self.config.gui_last_device), None) if last_used: devices.remove(last_used) devices.insert(0, last_used) accounts_widget = LoginAccountsWidget(self.config, self.jobs_ctx, devices, pendings) accounts_widget.account_clicked.connect(self._on_account_clicked) accounts_widget.pending_finalize_clicked.connect( self._on_pending_finalize_clicked) accounts_widget.pending_clear_clicked.connect( self._on_pending_clear_clicked) self.widget.layout().addWidget(accounts_widget) accounts_widget.setFocus()
def reload_devices(self): self._clear_widget() devices = [ device for device in list_available_devices(self.config.config_dir) if not ParsecApp.is_device_connected(device.organization_id, device.device_id) ] if not len(devices): no_device_widget = LoginNoDevicesWidget() no_device_widget.create_organization_clicked.connect( self.create_organization_clicked.emit) no_device_widget.join_organization_clicked.connect( self.join_organization_clicked.emit) self.widget.layout().addWidget(no_device_widget) no_device_widget.setFocus() elif len(devices) == 1: self._on_account_clicked(devices[0], hide_back=True) else: accounts_widget = LoginAccountsWidget(devices) accounts_widget.account_clicked.connect(self._on_account_clicked) self.widget.layout().addWidget(accounts_widget) accounts_widget.setFocus()
def reload_devices(self): while self.combo_username.count(): self.combo_username.removeItem(0) devices = list_available_devices(self.config.config_dir) # Display devices in `<organization>:<device_id>` format self.devices = {} for o, d, t, kf in devices: if not ParsecApp.is_device_connected(o, d): self.combo_username.addItem(f"{o}:{d}") self.devices[f"{o}:{d}"] = (o, d, t, kf) last_device = self.config.gui_last_device if last_device and last_device in self.devices: self.combo_username.setCurrentText(last_device) if len(self.devices): self.widget_no_device.hide() self.widget_login.show() else: self.widget_no_device.show() self.widget_login.hide() if ParsecApp.connected_devices: self.label_no_device.setText(_("TEXT_LOGIN_NO_AVAILABLE_DEVICE")) else: self.label_no_device.setText(_("TEXT_LOGIN_NO_DEVICE_ON_MACHINE"))
def test_list_no_devices(path_exists, config_dir): config_dir = config_dir if path_exists else config_dir / "dummy" devices = list_available_devices(config_dir) assert not devices
def test_run_testenv(run_testenv): devices = list_available_devices(run_testenv.config_dir) _, devices, _, _ = zip(*devices) assert sorted(devices) == ["alice@laptop", "alice@pc", "bob@laptop"]
def show_window( self, skip_dialogs: bool = False, invitation_link: Optional[str] = None ) -> None: try: if not self.restoreGeometry(self.config.gui_geometry): self.showMaximized() except TypeError: self.showMaximized() QCoreApplication.processEvents() # Used with the --diagnose option if skip_dialogs: return # At the very first launch if self.config.gui_first_launch: r = ask_question( self, _("TEXT_ENABLE_TELEMETRY_TITLE"), _("TEXT_ENABLE_TELEMETRY_INSTRUCTIONS"), [_("ACTION_ENABLE_TELEMETRY_ACCEPT"), _("ACTION_ENABLE_TELEMETRY_REFUSE")], oriented_question=True, ) # Acknowledge the changes self.event_bus.send( CoreEvent.GUI_CONFIG_CHANGED, gui_first_launch=False, gui_last_version=PARSEC_VERSION, telemetry_enabled=r == _("ACTION_ENABLE_TELEMETRY_ACCEPT"), ) # For each parsec update if self.config.gui_last_version and self.config.gui_last_version != PARSEC_VERSION: # Update from parsec `<1.14` to `>=1.14` if LooseVersion(self.config.gui_last_version) < "1.14": # Revert the acrobat reader workaround if ( sys.platform == "win32" and win_registry.is_acrobat_reader_dc_present() and not win_registry.get_acrobat_app_container_enabled() ): win_registry.del_acrobat_app_container_enabled() # Acknowledge the changes self.event_bus.send(CoreEvent.GUI_CONFIG_CHANGED, gui_last_version=PARSEC_VERSION) telemetry.init(self.config) devices = list_available_devices(self.config.config_dir) if not len(devices) and not invitation_link: r = ask_question( self, _("TEXT_KICKSTART_PARSEC_WHAT_TO_DO_TITLE"), _("TEXT_KICKSTART_PARSEC_WHAT_TO_DO_INSTRUCTIONS"), [ _("ACTION_NO_DEVICE_CREATE_ORGANIZATION"), _("ACTION_NO_DEVICE_JOIN_ORGANIZATION"), ], radio_mode=True, ) if r == _("ACTION_NO_DEVICE_JOIN_ORGANIZATION"): self._on_join_org_clicked() elif r == _("ACTION_NO_DEVICE_CREATE_ORGANIZATION"): self._on_create_org_clicked()