Global Settings now can function with partial permissions
Unreadable properties/features will be disabled, the tool will start even if it can access nothing or almost nothing, and errors on settings features/properties will now be communicated to the user.
This commit is contained in:
		
							parent
							
								
									1f933b775a
								
							
						
					
					
						commit
						7cbc7d9db1
					
				| @ -25,6 +25,7 @@ import subprocess | ||||
| from PyQt5 import QtWidgets, QtCore, QtGui  # pylint: disable=import-error | ||||
| 
 | ||||
| from qubesadmin.utils import parse_size | ||||
| from qubesadmin import exc | ||||
| 
 | ||||
| from . import ui_globalsettingsdlg  # pylint: disable=no-name-in-module | ||||
| from . import utils | ||||
| @ -83,6 +84,8 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|         self.__init_updates__() | ||||
|         self.__init_gui_defaults() | ||||
| 
 | ||||
|         self.errors = [] | ||||
| 
 | ||||
|     def setup_application(self): | ||||
|         self.app.setApplicationName(self.tr("Qubes Global Settings")) | ||||
|         self.app.setWindowIcon(QtGui.QIcon.fromTheme("qubes-manager")) | ||||
| @ -112,7 +115,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|         utils.initialize_widget_with_vms( | ||||
|             widget=self.default_netvm_combo, | ||||
|             qubes_app=self.qubes_app, | ||||
|             filter_function=(lambda vm: vm.provides_network), | ||||
|             filter_function=(lambda vm: getattr(vm, 'provides_network', False)), | ||||
|             allow_none=True, | ||||
|             holder=self.qubes_app, | ||||
|             property_name="default_netvm" | ||||
| @ -142,39 +145,67 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|     def __apply_system_defaults__(self): | ||||
|         # updatevm | ||||
|         if utils.did_widget_selection_change(self.update_vm_combo): | ||||
|             try: | ||||
|                 self.qubes_app.updatevm = self.update_vm_combo.currentData() | ||||
|             except exc.QubesException as ex: | ||||
|                 self.errors.append( | ||||
|                     "Failed to set UpdateVM due to {}".format(str(ex))) | ||||
| 
 | ||||
|         # clockvm | ||||
|         if utils.did_widget_selection_change(self.clock_vm_combo): | ||||
|             try: | ||||
|                 self.qubes_app.clockvm = self.clock_vm_combo.currentData() | ||||
|             except exc.QubesException as ex: | ||||
|                 self.errors.append( | ||||
|                     "Failed to set ClockVM due to {}".format(str(ex))) | ||||
| 
 | ||||
|         # default netvm | ||||
|         if utils.did_widget_selection_change(self.default_netvm_combo): | ||||
|             try: | ||||
|                 self.qubes_app.default_netvm = \ | ||||
|                     self.default_netvm_combo.currentData() | ||||
|             except exc.QubesException as ex: | ||||
|                 self.errors.append( | ||||
|                     "Failed to set Default NetVM due to {}".format(str(ex))) | ||||
| 
 | ||||
|         # default template | ||||
|         if utils.did_widget_selection_change(self.default_template_combo): | ||||
|             try: | ||||
|                 self.qubes_app.default_template = \ | ||||
|                     self.default_template_combo.currentData() | ||||
|             except exc.QubesException as ex: | ||||
|                 self.errors.append( | ||||
|                     "Failed to set Default Template due to {}".format(str(ex))) | ||||
| 
 | ||||
|         # default_dispvm | ||||
|         if utils.did_widget_selection_change(self.default_dispvm_combo): | ||||
|             try: | ||||
|                 self.qubes_app.default_dispvm = \ | ||||
|                     self.default_dispvm_combo.currentData() | ||||
|             except exc.QubesException as ex: | ||||
|                 self.errors.append( | ||||
|                     "Failed to set Default DispVM due to {}".format(str(ex))) | ||||
| 
 | ||||
|     def __init_kernel_defaults__(self): | ||||
|         try: | ||||
|             utils.initialize_widget_with_kernels( | ||||
|                 widget=self.default_kernel_combo, | ||||
|                 qubes_app=self.qubes_app, | ||||
|                 allow_none=True, | ||||
|                 holder=self.qubes_app, | ||||
|                 property_name='default_kernel') | ||||
|         except exc.QubesPropertyAccessError: | ||||
|             self.default_kernel_combo.clear() | ||||
|             self.default_kernel_combo.setEnabled(False) | ||||
| 
 | ||||
|     def __apply_kernel_defaults__(self): | ||||
|         if utils.did_widget_selection_change(self.default_kernel_combo): | ||||
|             try: | ||||
|                 self.qubes_app.default_kernel = \ | ||||
|                     self.default_kernel_combo.currentData() | ||||
|             except exc.QubesException as ex: | ||||
|                 self.errors.append( | ||||
|                     "Failed to set Default Kernel due to {}".format(str(ex))) | ||||
| 
 | ||||
|     def __init_gui_defaults(self): | ||||
|         utils.initialize_widget( | ||||
| @ -210,8 +241,8 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|                 ('tinted icon with modified white', 'tint+whitehack'), | ||||
|                 ('tinted icon with 50% saturation', 'tint+saturation50') | ||||
|             ], | ||||
|             selected_value=self.vm.features.get('gui-default-trayicon-mode', | ||||
|                                                 None)) | ||||
|             selected_value=utils.get_feature( | ||||
|                 self.vm, 'gui-default-trayicon-mode', None)) | ||||
| 
 | ||||
|         utils.initialize_widget( | ||||
|             widget=self.securecopy, | ||||
| @ -220,8 +251,8 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|                 ('Ctrl+Shift+C', 'Ctrl-Shift-c'), | ||||
|                 ('Ctrl+Win+C', 'Ctrl-Mod4-c'), | ||||
|             ], | ||||
|             selected_value=self.vm.features.get( | ||||
|                 'gui-default-secure-copy-sequence', None)) | ||||
|             selected_value=utils.get_feature( | ||||
|                 self.vm, 'gui-default-secure-copy-sequence', None)) | ||||
| 
 | ||||
|         utils.initialize_widget( | ||||
|             widget=self.securepaste, | ||||
| @ -231,15 +262,25 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|                 ('Ctrl+Win+V', 'Ctrl-Mod4-v'), | ||||
|                 ('Ctrl+Insert', 'Ctrl-Ins'), | ||||
|             ], | ||||
|             selected_value=self.vm.features.get( | ||||
|                 'gui-default-secure-paste-sequence', None)) | ||||
|             selected_value=utils.get_feature( | ||||
|                 self.vm, 'gui-default-secure-paste-sequence', None)) | ||||
| 
 | ||||
|     def __apply_feature_change(self, widget, feature): | ||||
|         if utils.did_widget_selection_change(widget): | ||||
|             if widget.currentData() is None: | ||||
|                 try: | ||||
|                     del self.vm.features[feature] | ||||
|                 except exc.QubesDaemonCommunicationError: | ||||
|                     self.errors.append( | ||||
|                         "Failed to set {} due to insufficient " | ||||
|                         "permissions".format(feature)) | ||||
|             else: | ||||
|                 try: | ||||
|                     self.vm.features[feature] = widget.currentData() | ||||
|                 except exc.QubesDaemonCommunicationError as ex: | ||||
|                     self.errors.append( | ||||
|                         "Failed to set {} due to insufficient " | ||||
|                         "permissions".format(feature)) | ||||
| 
 | ||||
|     def __apply_gui_defaults(self): | ||||
|         self.__apply_feature_change(widget=self.allow_fullscreen, | ||||
| @ -255,6 +296,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
| 
 | ||||
|     def __init_mem_defaults__(self): | ||||
|         # qmemman settings | ||||
|         try: | ||||
|             self.qmemman_config = ConfigParser() | ||||
|             self.vm_min_mem_val = '200MiB'  # str(qmemman_algo.MIN_PREFMEM) | ||||
|             self.dom0_mem_boost_val = '350MiB'  # str(qmemman_algo.DOM0_MEM_BOOST) | ||||
| @ -269,11 +311,20 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|             self.vm_min_mem_val = parse_size(self.vm_min_mem_val) | ||||
|             self.dom0_mem_boost_val = parse_size(self.dom0_mem_boost_val) | ||||
| 
 | ||||
|         self.min_vm_mem.setValue(int(self.vm_min_mem_val / 1024 / 1024)) | ||||
|         self.dom0_mem_boost.setValue(int(self.dom0_mem_boost_val / 1024 / 1024)) | ||||
|             self.min_vm_mem.setValue( | ||||
|                 int(self.vm_min_mem_val / 1024 / 1024)) | ||||
|             self.dom0_mem_boost.setValue( | ||||
|                 int(self.dom0_mem_boost_val / 1024 / 1024)) | ||||
|         except exc.QubesException: | ||||
|             self.min_vm_mem.setEnabled(False) | ||||
|             self.dom0_mem_boost.setEnabled(False) | ||||
| 
 | ||||
|     def __apply_mem_defaults__(self): | ||||
| 
 | ||||
|         if not self.min_vm_mem.isEnabled() or \ | ||||
|                 not self.dom0_mem_boost.isEnabled(): | ||||
|             return | ||||
| 
 | ||||
|         # qmemman settings | ||||
|         current_min_vm_mem = self.min_vm_mem.value() | ||||
|         current_dom0_mem_boost = self.dom0_mem_boost.value() | ||||
| @ -295,9 +346,14 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|                     'global', 'cache-margin-factor', str(1.3)) | ||||
|                 # removed qmemman_algo.CACHE_FACTOR | ||||
| 
 | ||||
|                 try: | ||||
|                     qmemman_config_file = open(qmemman_config_path, 'a') | ||||
|                     self.qmemman_config.write(qmemman_config_file) | ||||
|                     qmemman_config_file.close() | ||||
|                 except Exception as ex: | ||||
|                     self.errors.append( | ||||
|                         "Failed to set memory settings due to {}".format( | ||||
|                             str(ex))) | ||||
| 
 | ||||
|             else: | ||||
|                 # If there already is a 'global' section, we don't use | ||||
| @ -312,7 +368,14 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
| 
 | ||||
|                 config_lines = [] | ||||
| 
 | ||||
|                 try: | ||||
|                     qmemman_config_file = open(qmemman_config_path, 'r') | ||||
|                 except Exception as ex: | ||||
|                     self.errors.append( | ||||
|                         "Failed to set memory settings due to {}".format( | ||||
|                             str(ex))) | ||||
|                     return | ||||
| 
 | ||||
|                 for line in qmemman_config_file: | ||||
|                     if line.strip().startswith('vm-min-mem'): | ||||
|                         config_lines.append(lines_to_add['vm-min-mem']) | ||||
| @ -328,28 +391,44 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|                 for line in lines_to_add: | ||||
|                     config_lines.append(line) | ||||
| 
 | ||||
|                 try: | ||||
|                     qmemman_config_file = open(qmemman_config_path, 'w') | ||||
|                     qmemman_config_file.writelines(config_lines) | ||||
|                     qmemman_config_file.close() | ||||
|                 except Exception as ex: | ||||
|                     self.errors.append( | ||||
|                         "Failed to set memory settings due to {}".format( | ||||
|                             str(ex))) | ||||
|                     return | ||||
| 
 | ||||
|     def __init_updates__(self): | ||||
|         self.updates_dom0_val = bool( | ||||
|             self.qubes_app.domains['dom0'].features.get( | ||||
|                 'service.qubes-update-check', True)) | ||||
|             utils.get_feature(self.qubes_app.domains['dom0'], | ||||
|                               'service.qubes-update-check', | ||||
|                               True)) | ||||
| 
 | ||||
|         self.updates_dom0.setChecked(self.updates_dom0_val) | ||||
| 
 | ||||
|         try: | ||||
|             self.updates_vm.setChecked(self.qubes_app.check_updates_vm) | ||||
|         except exc.QubesPropertyAccessError: | ||||
|             self.updates_vm.isEnabled(False) | ||||
| 
 | ||||
|         self.enable_updates_all.clicked.connect(self.__enable_updates_all) | ||||
|         self.disable_updates_all.clicked.connect(self.__disable_updates_all) | ||||
| 
 | ||||
|         self.repos = repos = dict() | ||||
|         try: | ||||
|             for i in _run_qrexec_repo('qubes.repos.List').split('\n'): | ||||
|                 lst = i.split('\0') | ||||
|                 # Keyed by repo name | ||||
|                 dct = repos[lst[0]] = dict() | ||||
|                 dct['prettyname'] = lst[1] | ||||
|                 dct['enabled'] = lst[2] == 'enabled' | ||||
|         except Exception as ex: | ||||
|             self.dom0_updates_repo.setEnabled(False) | ||||
|             self.itl_tmpl_updates_repo.setEnabled(False) | ||||
|             self.comm_tmpl_updates_repo.setEnabled(False) | ||||
| 
 | ||||
|         if repos['qubes-dom0-unstable']['enabled']: | ||||
|             self.dom0_updates_repo.setCurrentIndex(3) | ||||
| @ -401,18 +480,38 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|         self.__set_updates_all(False) | ||||
| 
 | ||||
|     def __set_updates_all(self, state): | ||||
|         errors = [] | ||||
|         for vm in self.qubes_app.domains: | ||||
|             if vm.klass != "AdminVM": | ||||
|                 try: | ||||
|                     vm.features['service.qubes-update-check'] = state | ||||
|                 except exc.QubesDaemonCommunicationError: | ||||
|                     errors.append(vm.name) | ||||
| 
 | ||||
|         if errors: | ||||
|             QtWidgets.QMessageBox.warning( | ||||
|                 self, "Error!", | ||||
|                 "Failed to set state for some qubes: {}".format( | ||||
|                     ", ".join(errors))) | ||||
| 
 | ||||
|     def __apply_updates__(self): | ||||
|         if self.updates_dom0.isChecked() != self.updates_dom0_val: | ||||
|         if self.updates_dom0.isEnabled() and \ | ||||
|                 self.updates_dom0.isChecked() != self.updates_dom0_val: | ||||
|             try: | ||||
|                 self.qubes_app.domains['dom0'].features[ | ||||
|                     'service.qubes-update-check'] = \ | ||||
|                     self.updates_dom0.isChecked() | ||||
|             except exc.QubesDaemonCommunicationError: | ||||
|                 self.errors.append("Failed to change dom0 update value due " | ||||
|                                    "to insufficient permissions.") | ||||
| 
 | ||||
|         if self.qubes_app.check_updates_vm != self.updates_vm.isChecked(): | ||||
|         if self.updates_vm.isEnabled() and \ | ||||
|                 self.qubes_app.check_updates_vm != self.updates_vm.isChecked(): | ||||
|             try: | ||||
|                 self.qubes_app.check_updates_vm = self.updates_vm.isChecked() | ||||
|             except exc.QubesPropertyAccessError: | ||||
|                 self.errors.append("Failed to set qube update checking due " | ||||
|                                    "to insufficient permissions.") | ||||
| 
 | ||||
|     def _manage_repos(self, repolist, action): | ||||
|         for name in repolist: | ||||
| @ -470,10 +569,13 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|         self._manage_repos(disable, 'Disable') | ||||
| 
 | ||||
|     def __apply_repos__(self): | ||||
|         if self.dom0_updates_repo.isEnabled(): | ||||
|             self._handle_dom0_updates_combobox( | ||||
|                 self.dom0_updates_repo.currentIndex()) | ||||
|         if self.itl_tmpl_updates_repo.isEnabled(): | ||||
|             self._handle_itl_tmpl_updates_combobox( | ||||
|                 self.itl_tmpl_updates_repo.currentIndex()) | ||||
|         if self.comm_tmpl_updates_repo.isEnabled(): | ||||
|             self._handle_comm_tmpl_updates_combobox( | ||||
|                 self.comm_tmpl_updates_repo.currentIndex()) | ||||
| 
 | ||||
| @ -481,6 +583,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|         self.done(0) | ||||
| 
 | ||||
|     def save_and_apply(self): | ||||
|         self.errors = [] | ||||
| 
 | ||||
|         self.__apply_system_defaults__() | ||||
|         self.__apply_kernel_defaults__() | ||||
| @ -489,6 +592,11 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, | ||||
|         self.__apply_repos__() | ||||
|         self.__apply_gui_defaults() | ||||
| 
 | ||||
|         if self.errors: | ||||
|             err_msg = "Failed to apply some settings:\n" + "\n".join( | ||||
|                 self.errors) | ||||
|             QtWidgets.QMessageBox.warning(self, "Error", err_msg) | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     utils.run_synchronous(GlobalSettingsWindow) | ||||
|  | ||||
| @ -105,8 +105,11 @@ class GlobalSettingsTest(unittest.TestCase): | ||||
| 
 | ||||
|         # correct defaultDispVM | ||||
|         selected_default_dispvm = self.dialog.default_dispvm_combo.currentText() | ||||
|         correct_default_dispvm = \ | ||||
|             str(getattr(self.qapp, 'default_dispvm', "(none)")) | ||||
|         current_default_dispvm = getattr(self.qapp, 'default_dispvm', None) | ||||
|         if current_default_dispvm is None: | ||||
|             correct_default_dispvm = "(none)" | ||||
|         else: | ||||
|             correct_default_dispvm = str(current_default_dispvm) | ||||
|         self.assertTrue( | ||||
|             selected_default_dispvm.startswith(correct_default_dispvm), | ||||
|             "Incorrect defaultDispVM loaded") | ||||
| @ -118,11 +121,8 @@ class GlobalSettingsTest(unittest.TestCase): | ||||
| 
 | ||||
|     def test_02_dom0_updates_load(self): | ||||
|         # check dom0 updates | ||||
|         try: | ||||
|         dom0_updates = self.qapp.domains[ | ||||
|                 'dom0'].features['service.qubes-update-check'] | ||||
|         except KeyError: | ||||
|             dom0_updates = True | ||||
|             'dom0'].features.get('service.qubes-update-check', True) | ||||
| 
 | ||||
|         self.assertEqual(bool(dom0_updates), | ||||
|                          self.dialog.updates_dom0.isChecked(), | ||||
|  | ||||
| @ -31,6 +31,7 @@ from contextlib import suppress | ||||
| import sys | ||||
| import qasync | ||||
| from qubesadmin import events | ||||
| from qubesadmin import exc | ||||
| 
 | ||||
| from PyQt5 import QtWidgets, QtCore, QtGui  # pylint: disable=import-error | ||||
| 
 | ||||
| @ -91,11 +92,18 @@ class SizeSpinBox(QtWidgets.QSpinBox): | ||||
|         return int(float(value) * multiplier) | ||||
| 
 | ||||
| 
 | ||||
| def get_feature(vm, feature_name, default_value): | ||||
|     try: | ||||
|         return vm.features.get(feature_name, default_value) | ||||
|     except exc.QubesDaemonCommunicationError: | ||||
|         return default_value | ||||
| 
 | ||||
| 
 | ||||
| def get_boolean_feature(vm, feature_name): | ||||
|     """heper function to get a feature converted to a Bool if it does exist. | ||||
|     Necessary because of the true/false in features being coded as 1/empty | ||||
|     string.""" | ||||
|     result = vm.features.get(feature_name, None) | ||||
|     result = get_feature(vm, feature_name, None) | ||||
|     if result is not None: | ||||
|         result = bool(result) | ||||
|     return result | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Marta Marczykowska-Górecka
						Marta Marczykowska-Górecka