Browse Source

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.
Marta Marczykowska-Górecka 3 years ago
parent
commit
39129bd804

+ 16 - 7
qubesmanager/backup.py

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

+ 7 - 2
qubesmanager/backup_utils.py

@@ -43,10 +43,10 @@ def fill_appvms_list(dialog):
     dialog.appvm_combobox.setCurrentIndex(0)  # current selected is null ""
 
     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
 
-        if vm.is_running() and vm.qid != 0:
+        if utils.is_running(vm, False) and vm.qid != 0:
             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("Backup path can only contain the following "
                               "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:
         if not read_only:

+ 20 - 6
qubesmanager/bootfromdevice.py

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

+ 14 - 11
qubesmanager/create_new_vm.py

@@ -105,22 +105,27 @@ class NewVmDlg(QtWidgets.QDialog, Ui_NewVMDlg):
             choices=[(vm.name, vm) for vm in self.app.domains
                      if not utils.is_internal(vm) and vm.klass == 'TemplateVM'],
             mark_existing_as_default=True,
-            default_value=self.app.default_template)
+            default_value=getattr(self.app, 'default_template', None))
 
         utils.initialize_widget_with_default(
             widget=self.netvm,
             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_qubes_default=True,
-            default_value=self.app.default_netvm)
+            default_value=getattr(self.app, 'default_netvm', None))
 
-        utils.initialize_widget_with_default(
-            widget=self.storage_pool,
-            choices=[(str(pool), pool) for pool in self.app.pools.values()],
-            add_qubes_default=True,
-            mark_existing_as_default=True,
-            default_value=self.app.default_pool)
+        try:
+            utils.initialize_widget_with_default(
+                widget=self.storage_pool,
+                choices=[(str(pool), pool) for pool in self.app.pools.values()],
+                add_qubes_default=True,
+                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(
             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('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 = [
             (self.tr("Qube based on a template (AppVM)"), 'AppVM'),
             (self.tr("Standalone qube copied from a template"),

+ 8 - 1
qubesmanager/template_manager.py

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

+ 14 - 2
qubesmanager/utils.py

@@ -51,8 +51,20 @@ from PyQt5 import QtWidgets, QtCore, QtGui  # pylint: disable=import-error
 
 def is_internal(vm):
     """checks if the VM is either an AdminVM or has the 'internal' features"""
-    return (vm.klass == 'AdminVM'
-            or vm.features.get('internal', False))
+    try:
+        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):