Make Qube Manager resistant to missing permissions

It should no longer crash if policy denies it access to some information.
The only required information is vm name, qid and class.

references QubesOS/qubes-issues#5811
This commit is contained in:
Marta Marczykowska-Górecka 2020-08-03 23:25:20 +02:00
parent 7fc8c2e9e0
commit 1f933b775a
No known key found for this signature in database
GPG Key ID: 9A752C30B26FD04B

View File

@ -199,23 +199,21 @@ class VmInfo():
self.vm = vm self.vm = vm
self.qid = vm.qid self.qid = vm.qid
self.name = self.vm.name self.name = self.vm.name
self.label = self.vm.label
self.klass = self.vm.klass self.label = getattr(self.vm, 'label', None)
self.klass = getattr(self.vm, 'klass', None)
self.state = {'power': "", 'outdated': ""} self.state = {'power': "", 'outdated': ""}
self.updateable = getattr(vm, 'updateable', False) self.updateable = getattr(vm, 'updateable', False)
self.update(True) self.update(True)
def update(self, update_size_on_disk=False, event=None): def update_power_state(self):
"""
Update VmInfo
:param update_size_on_disk: should disk utilization be updated?
:param event: name of the event that caused the update, to avoid
updating unnecessary properties; if event is none, update everything
:return: None
"""
try: try:
self.state['power'] = self.vm.get_power_state() self.state['power'] = self.vm.get_power_state()
except exc.QubesPropertyAccessError:
self.state['power'] = ""
self.state['outdated'] = ""
try:
if self.vm.is_running(): if self.vm.is_running():
if hasattr(self.vm, 'template') and \ if hasattr(self.vm, 'template') and \
self.vm.template.is_running(): self.vm.template.is_running():
@ -225,64 +223,89 @@ class VmInfo():
if vol.is_outdated(): if vol.is_outdated():
self.state['outdated'] = "outdated" self.state['outdated'] = "outdated"
break break
else:
self.state['outdated'] = ""
if self.vm.klass in {'TemplateVM', 'StandaloneVM'} and \ if self.vm.klass in {'TemplateVM', 'StandaloneVM'} and \
self.vm.features.get('updates-available', False): self.vm.features.get('updates-available', False):
self.state['outdated'] = 'update' self.state['outdated'] = 'update'
except exc.QubesPropertyAccessError:
pass
if not event or event.endswith(':label'): def update(self, update_size_on_disk=False, event=None):
self.label = self.vm.label """
if not event or event.endswith(':template'): Update VmInfo
try: :param update_size_on_disk: should disk utilization be updated?
self.template = self.vm.template.name :param event: name of the event that caused the update, to avoid
except AttributeError: updating unnecessary properties; if event is none, update everything
self.template = None :return: None
if not event or event.endswith(':netvm'): """
self.netvm = getattr(self.vm, 'netvm', None) self.update_power_state()
if self.netvm:
self.netvm = self.netvm.name if not event or event.endswith(':label'):
else: self.label = getattr(self.vm, 'label', None)
self.netvm = "n/a"
if self.qid != 0 and self.vm.property_is_default("netvm"): if not event or event.endswith(':template'):
try:
self.template = self.vm.template.name
except AttributeError:
self.template = None
if not event or event.endswith(':netvm'):
self.netvm = getattr(self.vm, 'netvm', None)
if self.netvm:
self.netvm = str(self.netvm)
else:
self.netvm = "n/a"
try:
if hasattr(self.vm, 'netvm') \
and self.vm.property_is_default("netvm"):
self.netvm = "default (" + self.netvm + ")" self.netvm = "default (" + self.netvm + ")"
if not event or event.endswith(':internal'): except exc.QubesPropertyAccessError:
# this is a feature, not a property; TODO: fix event handling pass
if not event or event.endswith(':internal'):
try:
self.internal = self.vm.features.get('internal', False) self.internal = self.vm.features.get('internal', False)
if not event or event.endswith(':ip'): except exc.QubesPropertyAccessError:
self.ip = getattr(self.vm, 'ip', "n/a") self.internal = False
if not event or event.endswith(':include_in_backups'):
self.inc_backup = getattr(self.vm, 'include_in_backups', None) if not event or event.endswith(':ip'):
if not event or event.endswith(':backup_timestamp'): self.ip = getattr(self.vm, 'ip', "n/a")
self.last_backup = getattr(self.vm, 'backup_timestamp', None)
if self.last_backup: if not event or event.endswith(':include_in_backups'):
self.last_backup = str(datetime.fromtimestamp( self.inc_backup = getattr(self.vm, 'include_in_backups', None)
self.last_backup))
if not event or event.endswith(':default_dispvm'): if not event or event.endswith(':backup_timestamp'):
self.dvm = getattr(self.vm, 'default_dispvm', None) self.last_backup = getattr(self.vm, 'backup_timestamp', None)
if self.last_backup:
self.last_backup = str(datetime.fromtimestamp(self.last_backup))
if not event or event.endswith(':default_dispvm'):
self.dvm = getattr(self.vm, 'default_dispvm', None)
try:
if self.vm.property_is_default("default_dispvm"): if self.vm.property_is_default("default_dispvm"):
self.dvm = "default (" + str(self.dvm) + ")" self.dvm = "default (" + str(self.dvm) + ")"
elif self.dvm is not None: elif self.dvm is not None:
self.dvm = self.dvm.name self.dvm = str(self.dvm)
if not event or event.endswith(':template_for_dispvms'): except exc.QubesPropertyAccessError:
self.dvm_template = getattr(self.vm, 'template_for_dispvms', self.dvm = None
None)
if self.qid != 0 and update_size_on_disk: if not event or event.endswith(':template_for_dispvms'):
self.dvm_template = getattr(self.vm, 'template_for_dispvms', None)
if self.vm.klass != 'AdminVM' and update_size_on_disk:
try:
self.disk_float = float(self.vm.get_disk_utilization()) self.disk_float = float(self.vm.get_disk_utilization())
self.disk = str(round(self.disk_float/(1024*1024), 2)) + " MiB" self.disk = str(round(self.disk_float/(1024*1024), 2)) + " MiB"
except exc.QubesPropertyAccessError:
self.disk_float = None
self.disk = None
if self.vm.klass != 'AdminVM':
self.virt_mode = getattr(self.vm, 'virt_mode', None)
else:
self.virt_mode = None
self.disk = "n/a"
if self.qid != 0:
self.virt_mode = self.vm.virt_mode
else:
self.virt_mode = None
self.disk = "n/a"
except exc.QubesPropertyAccessError:
pass
except exc.QubesDaemonNoResponseError:
# TODO: this will be fixed by a rewrite moving the event system to
# AdminAPI
pass
class QubesCache(QAbstractTableModel): class QubesCache(QAbstractTableModel):
def __init__(self, qubes_app): def __init__(self, qubes_app):
@ -314,6 +337,7 @@ class QubesCache(QAbstractTableModel):
def __iter__(self): def __iter__(self):
return iter(self._info_list) return iter(self._info_list)
class QubesTableModel(QAbstractTableModel): class QubesTableModel(QAbstractTableModel):
def __init__(self, qubes_cache): def __init__(self, qubes_cache):
QAbstractTableModel.__init__(self) QAbstractTableModel.__init__(self)
@ -398,6 +422,8 @@ class QubesTableModel(QAbstractTableModel):
pixmap.load(icon_name) pixmap.load(icon_name)
self.klass_pixmap[vm.klass] = pixmap.scaled(icon_size) self.klass_pixmap[vm.klass] = pixmap.scaled(icon_size)
return self.klass_pixmap[vm.klass] return self.klass_pixmap[vm.klass]
except exc.QubesPropertyAccessError:
return None
if col_name == "Label": if col_name == "Label":
try: try:
@ -406,6 +432,8 @@ class QubesTableModel(QAbstractTableModel):
icon = QIcon.fromTheme(vm.label.icon) icon = QIcon.fromTheme(vm.label.icon)
self.label_pixmap[vm.label] = icon.pixmap(icon_size) self.label_pixmap[vm.label] = icon.pixmap(icon_size)
return self.label_pixmap[vm.label] return self.label_pixmap[vm.label]
except exc.QubesPropertyAccessError:
return None
if role == Qt.FontRole: if role == Qt.FontRole:
if col_name == "Template": if col_name == "Template":
@ -425,12 +453,12 @@ class QubesTableModel(QAbstractTableModel):
# Used for sorting # Used for sorting
if role == Qt.UserRole + 1: if role == Qt.UserRole + 1:
if vm.qid == 0: if vm.klass != 'AdminVM':
return "" return ""
if col_name == "Type": if col_name == "Type":
return vm.klass return vm.klass
if col_name == "Label": if col_name == "Label":
return vm.label.name return str(vm.label)
if col_name == "State": if col_name == "State":
return str(vm.state) return str(vm.state)
if col_name == "Disk Usage": if col_name == "Disk Usage":
@ -447,7 +475,6 @@ class QubesTableModel(QAbstractTableModel):
return None return None
vm_shutdown_timeout = 20000 # in msec vm_shutdown_timeout = 20000 # in msec
vm_restart_check_timeout = 1000 # in msec vm_restart_check_timeout = 1000 # in msec
@ -545,7 +572,7 @@ class StartVMThread(common_threads.QubesThread):
class UpdateVMThread(common_threads.QubesThread): class UpdateVMThread(common_threads.QubesThread):
def run(self): def run(self):
try: try:
if self.vm.qid == 0: if self.vm.klass != 'AdminVM':
subprocess.check_call( subprocess.check_call(
["/usr/bin/qubes-dom0-update", "--clean", "--gui"]) ["/usr/bin/qubes-dom0-update", "--clean", "--gui"])
else: else:
@ -587,6 +614,7 @@ class RunCommandThread(common_threads.QubesThread):
except (ChildProcessError, exc.QubesException) as ex: except (ChildProcessError, exc.QubesException) as ex:
self.msg = (self.tr("Error while running command!"), str(ex)) self.msg = (self.tr("Error while running command!"), str(ex))
class QubesProxyModel(QSortFilterProxyModel): class QubesProxyModel(QSortFilterProxyModel):
def lessThan(self, left, right): def lessThan(self, left, right):
if left.data(self.sortRole()) != right.data(self.sortRole()): if left.data(self.sortRole()) != right.data(self.sortRole()):
@ -597,6 +625,7 @@ class QubesProxyModel(QSortFilterProxyModel):
return left_vm.name.lower() < right_vm.name.lower() return left_vm.name.lower() < right_vm.name.lower()
class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
# suppress saving settings while initializing widgets # suppress saving settings while initializing widgets
settings_loaded = False settings_loaded = False
@ -733,6 +762,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.on_domain_changed) self.on_domain_changed)
dispatcher.add_handler('property-load', dispatcher.add_handler('property-load',
self.on_domain_changed) self.on_domain_changed)
dispatcher.add_handler('domain-feature-set:internal',
self.on_domain_changed)
dispatcher.add_handler('domain-feature-delete:internal',
self.on_domain_changed)
dispatcher.add_handler('domain-feature-set:updates-available', dispatcher.add_handler('domain-feature-set:updates-available',
self.on_domain_updates_available) self.on_domain_updates_available)
@ -813,9 +846,12 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.check_updates(info_iter) self.check_updates(info_iter)
return return
if info.vm.klass in {'TemplateVM', 'StandaloneVM'} and \ try:
info.vm.features.get('updates-available', False): if info.vm.klass in {'TemplateVM', 'StandaloneVM'} and \
info.state['outdated'] = 'update' info.vm.features.get('updates-available', False):
info.state['outdated'] = 'update'
except exc.QubesPropertyAccessError:
return
def on_domain_added(self, _submitter, _event, vm, **_kwargs): def on_domain_added(self, _submitter, _event, vm, **_kwargs):
try: try:
@ -974,7 +1010,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
if vm.vm.features.get('internal', False): if vm.vm.features.get('internal', False):
self.action_appmenus.setEnabled(False) self.action_appmenus.setEnabled(False)
if not vm.updateable and vm.qid != 0: if not vm.updateable and vm.klass != 'AdminVM':
self.action_updatevm.setEnabled(False) self.action_updatevm.setEnabled(False)
self.update_logs_menu() self.update_logs_menu()
@ -1363,7 +1399,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
if len(vm_info) == 1: if len(vm_info) == 1:
vm = vm_info[0].vm vm = vm_info[0].vm
if vm.qid == 0: if vm.klass == 'AdminVM':
logfiles = ["/var/log/xen/console/hypervisor.log"] logfiles = ["/var/log/xen/console/hypervisor.log"]
else: else:
logfiles = [ logfiles = [