Misc qubesmanager tools modified to be more resilient to insufficient permissions

Create New Qubes, Backup and Restore, Boot from Device and Template Manager
are now more resilient to insufficient permissions.
This commit is contained in:
Marta Marczykowska-Górecka 2020-08-04 22:50:20 +02:00
parent 7cbc7d9db1
commit 39129bd804
No known key found for this signature in database
GPG Key ID: 9A752C30B26FD04B
6 changed files with 79 additions and 29 deletions

View File

@ -49,6 +49,11 @@ class BackupThread(QtCore.QThread):
try: try:
if not self.vm.is_running(): if not self.vm.is_running():
self.vm.start() self.vm.start()
except exc.QubesException:
# we may have insufficient exceptions to ensure the qube is running
pass
try:
self.vm.app.qubesd_call( self.vm.app.qubesd_call(
'dom0', 'admin.backup.Execute', 'dom0', 'admin.backup.Execute',
backup_utils.get_profile_name(True)) backup_utils.get_profile_name(True))
@ -103,8 +108,8 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, QtWidgets.QWizard):
qubes_app=self.qubes_app, qubes_app=self.qubes_app,
filter_function=(lambda vm: filter_function=(lambda vm:
vm.klass != 'TemplateVM' vm.klass != 'TemplateVM'
and vm.is_running() and utils.is_running(vm, False)
and not vm.features.get('internal', False)), and not utils.get_feature(vm, 'internal', False)),
allow_internal=True, allow_internal=True,
) )
self.appvm_combobox.setCurrentIndex( self.appvm_combobox.setCurrentIndex(
@ -215,7 +220,7 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, QtWidgets.QWizard):
def __fill_vms_list__(self, selected=None): def __fill_vms_list__(self, selected=None):
for vm in self.qubes_app.domains: for vm in self.qubes_app.domains:
if vm.features.get('internal', False): if utils.get_feature(vm, 'internal', False):
continue continue
item = BackupVMsWindow.VmListItem(vm) item = BackupVMsWindow.VmListItem(vm)
@ -298,13 +303,17 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, QtWidgets.QWizard):
if self.currentPage() is self.confirm_page: if self.currentPage() is self.confirm_page:
self.save_settings(use_temp=True) self.save_settings(use_temp=True)
backup_summary = self.qubes_app.qubesd_call( try:
'dom0', 'admin.backup.Info', backup_summary = self.qubes_app.qubesd_call(
backup_utils.get_profile_name(True)) 'dom0', 'admin.backup.Info',
backup_utils.get_profile_name(True)).decode()
except exc.QubesDaemonCommunicationError:
backup_summary = "Failed to get backup summary: " \
"insufficient permissions"
self.textEdit.setReadOnly(True) self.textEdit.setReadOnly(True)
self.textEdit.setFontFamily("Monospace") self.textEdit.setFontFamily("Monospace")
self.textEdit.setText(backup_summary.decode()) self.textEdit.setText(backup_summary)
elif self.currentPage() is self.commit_page: elif self.currentPage() is self.commit_page:

View File

@ -43,10 +43,10 @@ def fill_appvms_list(dialog):
dialog.appvm_combobox.setCurrentIndex(0) # current selected is null "" dialog.appvm_combobox.setCurrentIndex(0) # current selected is null ""
for vm in dialog.qubes_app.domains: for vm in dialog.qubes_app.domains:
if vm.features.get('internal', False) or vm.klass == 'TemplateVM': if utils.get_feature(vm, 'internal', False) or vm.klass == 'TemplateVM':
continue continue
if vm.is_running() and vm.qid != 0: if utils.is_running(vm, False) and vm.qid != 0:
dialog.appvm_combobox.addItem(vm.name) dialog.appvm_combobox.addItem(vm.name)
@ -101,6 +101,11 @@ def select_path_button_clicked(dialog, select_file=False, read_only=False):
dialog.tr("Unexpected characters in path!"), dialog.tr("Unexpected characters in path!"),
dialog.tr("Backup path can only contain the following " dialog.tr("Backup path can only contain the following "
"special characters: /:.,_+=() -")) "special characters: /:.,_+=() -"))
except Exception as ex:
QtWidgets.QMessageBox.warning(
dialog,
dialog.tr("Failed to select path!"),
dialog.tr("Error {} occurred.".format(str(ex))))
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
if not read_only: if not read_only:

View File

@ -24,6 +24,7 @@ from . import ui_bootfromdevice # pylint: disable=no-name-in-module
from PyQt5 import QtWidgets, QtGui # pylint: disable=import-error from PyQt5 import QtWidgets, QtGui # pylint: disable=import-error
from qubesadmin import tools from qubesadmin import tools
from qubesadmin.tools import qvm_start from qubesadmin.tools import qvm_start
from qubesadmin import exc
class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog,
@ -75,13 +76,20 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog,
self.done(0) self.done(0)
def __warn_if_running__(self): def __warn_if_running__(self):
if self.vm.is_running(): try:
if self.vm.is_running():
QtWidgets.QMessageBox.warning(
self,
self.tr("Warning!"),
self.tr("Qube must be turned off before booting it from "
"device. Please turn off the qube."))
except exc.QubesPropertyAccessError:
QtWidgets.QMessageBox.warning( QtWidgets.QMessageBox.warning(
self, self,
self.tr("Warning!"), self.tr("Warning!"),
self.tr("Qube must be turned off before booting it from " self.tr("Insufficient permissions to determine if qube is "
"device. Please turn off the qube.") "running. It must be turned off before booting it from "
) "device."))
def __init_buttons__(self): def __init_buttons__(self):
self.fileVM.setEnabled(False) self.fileVM.setEnabled(False)
@ -98,8 +106,14 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog,
allow_internal=True allow_internal=True
) )
device_choice = [(str(device), device) for domain in self.vm.app.domains device_choice = []
for device in domain.devices["block"]] for domain in self.vm.app.domains:
try:
for device in domain.devices["block"]:
device_choice.append((str(device), device))
except exc.QubesException:
# insufficient permissions
pass
utils.initialize_widget( utils.initialize_widget(
widget=self.blockDeviceComboBox, widget=self.blockDeviceComboBox,

View File

@ -105,22 +105,27 @@ class NewVmDlg(QtWidgets.QDialog, Ui_NewVMDlg):
choices=[(vm.name, vm) for vm in self.app.domains choices=[(vm.name, vm) for vm in self.app.domains
if not utils.is_internal(vm) and vm.klass == 'TemplateVM'], if not utils.is_internal(vm) and vm.klass == 'TemplateVM'],
mark_existing_as_default=True, mark_existing_as_default=True,
default_value=self.app.default_template) default_value=getattr(self.app, 'default_template', None))
utils.initialize_widget_with_default( utils.initialize_widget_with_default(
widget=self.netvm, widget=self.netvm,
choices=[(vm.name, vm) for vm in self.app.domains choices=[(vm.name, vm) for vm in self.app.domains
if not utils.is_internal(vm) and vm.provides_network], if not utils.is_internal(vm) and
getattr(vm, 'provides_network', False)],
add_none=True, add_none=True,
add_qubes_default=True, add_qubes_default=True,
default_value=self.app.default_netvm) default_value=getattr(self.app, 'default_netvm', None))
utils.initialize_widget_with_default( try:
widget=self.storage_pool, utils.initialize_widget_with_default(
choices=[(str(pool), pool) for pool in self.app.pools.values()], widget=self.storage_pool,
add_qubes_default=True, choices=[(str(pool), pool) for pool in self.app.pools.values()],
mark_existing_as_default=True, add_qubes_default=True,
default_value=self.app.default_pool) mark_existing_as_default=True,
default_value=self.app.default_pool)
except qubesadmin.exc.QubesPropertyAccessError:
self.storage_pool.clear()
self.storage_pool.addItem("(default)", qubesadmin.DEFAULT)
self.name.setValidator(QtGui.QRegExpValidator( self.name.setValidator(QtGui.QRegExpValidator(
QtCore.QRegExp("[a-zA-Z0-9_-]*", QtCore.Qt.CaseInsensitive), None)) QtCore.QRegExp("[a-zA-Z0-9_-]*", QtCore.Qt.CaseInsensitive), None))
@ -133,8 +138,6 @@ class NewVmDlg(QtWidgets.QDialog, Ui_NewVMDlg):
self.tr('No template available!'), self.tr('No template available!'),
self.tr('Cannot create a qube when no template exists.')) self.tr('Cannot create a qube when no template exists.'))
# Order of types is important and used elsewhere; if it's changed
# check for changes needed in self.type_change
type_list = [ type_list = [
(self.tr("Qube based on a template (AppVM)"), 'AppVM'), (self.tr("Qube based on a template (AppVM)"), 'AppVM'),
(self.tr("Standalone qube copied from a template"), (self.tr("Standalone qube copied from a template"),

View File

@ -345,7 +345,7 @@ class VMRow:
table_widget.setItem(row_no, columns.index('New template'), table_widget.setItem(row_no, columns.index('New template'),
self.dummy_new_item) self.dummy_new_item)
self.vm_state_change(self.vm.is_running(), row_no) self.vm_state_change(is_vm_running(self.vm), row_no)
def vm_state_change(self, is_running, row=None): def vm_state_change(self, is_running, row=None):
self.state_item.set_state(is_running) self.state_item.set_state(is_running)
@ -385,6 +385,13 @@ class VMRow:
self.checkbox = None self.checkbox = None
def is_vm_running(vm):
try:
return vm.is_running()
except exc.QubesPropertyAccessError:
return False
def main(): def main():
utils.run_asynchronous(TemplateManagerWindow) utils.run_asynchronous(TemplateManagerWindow)

View File

@ -51,8 +51,20 @@ from PyQt5 import QtWidgets, QtCore, QtGui # pylint: disable=import-error
def is_internal(vm): def is_internal(vm):
"""checks if the VM is either an AdminVM or has the 'internal' features""" """checks if the VM is either an AdminVM or has the 'internal' features"""
return (vm.klass == 'AdminVM' try:
or vm.features.get('internal', False)) return (vm.klass == 'AdminVM'
or vm.features.get('internal', False))
except exc.QubesDaemonCommunicationError:
return False
def is_running(vm, default_state):
"""Checks if the VM is running, returns default_state if we have
insufficient permissions to deteremine that."""
try:
return vm.is_running()
except exc.QubesPropertyAccessError:
return default_state
def translate(string): def translate(string):