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
def update(self, update_size_on_disk=False, event=None):
"""
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
"""
self.update_power_state()
if not event or event.endswith(':label'): if not event or event.endswith(':label'):
self.label = self.vm.label self.label = getattr(self.vm, 'label', None)
if not event or event.endswith(':template'): if not event or event.endswith(':template'):
try: try:
self.template = self.vm.template.name self.template = self.vm.template.name
except AttributeError: except AttributeError:
self.template = None self.template = None
if not event or event.endswith(':netvm'): if not event or event.endswith(':netvm'):
self.netvm = getattr(self.vm, 'netvm', None) self.netvm = getattr(self.vm, 'netvm', None)
if self.netvm: if self.netvm:
self.netvm = self.netvm.name self.netvm = str(self.netvm)
else: else:
self.netvm = "n/a" self.netvm = "n/a"
if self.qid != 0 and self.vm.property_is_default("netvm"): try:
if hasattr(self.vm, 'netvm') \
and self.vm.property_is_default("netvm"):
self.netvm = "default (" + self.netvm + ")" self.netvm = "default (" + self.netvm + ")"
except exc.QubesPropertyAccessError:
pass
if not event or event.endswith(':internal'): if not event or event.endswith(':internal'):
# this is a feature, not a property; TODO: fix event handling try:
self.internal = self.vm.features.get('internal', False) self.internal = self.vm.features.get('internal', False)
except exc.QubesPropertyAccessError:
self.internal = False
if not event or event.endswith(':ip'): if not event or event.endswith(':ip'):
self.ip = getattr(self.vm, 'ip', "n/a") self.ip = getattr(self.vm, 'ip', "n/a")
if not event or event.endswith(':include_in_backups'): if not event or event.endswith(':include_in_backups'):
self.inc_backup = getattr(self.vm, 'include_in_backups', None) self.inc_backup = getattr(self.vm, 'include_in_backups', None)
if not event or event.endswith(':backup_timestamp'): if not event or event.endswith(':backup_timestamp'):
self.last_backup = getattr(self.vm, 'backup_timestamp', None) self.last_backup = getattr(self.vm, 'backup_timestamp', None)
if self.last_backup: if self.last_backup:
self.last_backup = str(datetime.fromtimestamp( self.last_backup = str(datetime.fromtimestamp(self.last_backup))
self.last_backup))
if not event or event.endswith(':default_dispvm'): if not event or event.endswith(':default_dispvm'):
self.dvm = getattr(self.vm, 'default_dispvm', None) 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)
except exc.QubesPropertyAccessError:
self.dvm = None
if not event or event.endswith(':template_for_dispvms'): if not event or event.endswith(':template_for_dispvms'):
self.dvm_template = getattr(self.vm, 'template_for_dispvms', self.dvm_template = getattr(self.vm, 'template_for_dispvms', None)
None)
if self.qid != 0 and update_size_on_disk: 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.qid != 0: if self.vm.klass != 'AdminVM':
self.virt_mode = self.vm.virt_mode self.virt_mode = getattr(self.vm, 'virt_mode', None)
else: else:
self.virt_mode = None self.virt_mode = None
self.disk = "n/a" 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
try:
if info.vm.klass in {'TemplateVM', 'StandaloneVM'} and \ if info.vm.klass in {'TemplateVM', 'StandaloneVM'} and \
info.vm.features.get('updates-available', False): info.vm.features.get('updates-available', False):
info.state['outdated'] = 'update' 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 = [