Changed feature keyboard_layout to a property
Purpose: make it easier to implement more robust keyboard layout tools and propagation. references QubesOS/qubes-issues#1396 references QubesOS/qubes-issues#4294
This commit is contained in:
		
							parent
							
								
									b889bf98f4
								
							
						
					
					
						commit
						06e2d14a97
					
				| @ -12,7 +12,6 @@ services: | |||||||
| install: | install: | ||||||
|   - sudo apt-get -y install python3-gi gir1.2-gtk-3.0 |   - sudo apt-get -y install python3-gi gir1.2-gtk-3.0 | ||||||
|   - pip3 install --quiet -r ci/requirements.txt |   - pip3 install --quiet -r ci/requirements.txt | ||||||
|   - git clone https://github.com/"${TRAVIS_REPO_SLUG%%/*}"/qubes-builder ~/qubes-builder |  | ||||||
|   - git clone https://github.com/"${TRAVIS_REPO_SLUG%%/*}"/qubes-core-qrexec ~/qubes-core-qrexec |   - git clone https://github.com/"${TRAVIS_REPO_SLUG%%/*}"/qubes-core-qrexec ~/qubes-core-qrexec | ||||||
| script: | script: | ||||||
|   - PYTHONPATH=test-packages:~/qubes-core-qrexec pylint qubes |   - PYTHONPATH=test-packages:~/qubes-core-qrexec pylint qubes | ||||||
|  | |||||||
| @ -21,8 +21,6 @@ | |||||||
| # License along with this library; if not, see <https://www.gnu.org/licenses/>. | # License along with this library; if not, see <https://www.gnu.org/licenses/>. | ||||||
| # | # | ||||||
| 
 | 
 | ||||||
| import re |  | ||||||
| 
 |  | ||||||
| import qubes.config | import qubes.config | ||||||
| import qubes.ext | import qubes.ext | ||||||
| import qubes.exc | import qubes.exc | ||||||
| @ -89,15 +87,13 @@ class GUI(qubes.ext.Extension): | |||||||
| 
 | 
 | ||||||
|         # Add GuiVM Xen ID for gui-daemon |         # Add GuiVM Xen ID for gui-daemon | ||||||
|         if getattr(vm, 'guivm', None): |         if getattr(vm, 'guivm', None): | ||||||
|             if vm != vm.guivm and vm.guivm.is_running(): |             if vm != vm.guivm: | ||||||
|  |                 vm.untrusted_qdb.write('/keyboard-layout', vm.keyboard_layout) | ||||||
|  | 
 | ||||||
|  |                 if vm.guivm.is_running(): | ||||||
|                     vm.untrusted_qdb.write('/qubes-gui-domain-xid', |                     vm.untrusted_qdb.write('/qubes-gui-domain-xid', | ||||||
|                                            str(vm.guivm.xid)) |                                            str(vm.guivm.xid)) | ||||||
| 
 | 
 | ||||||
|             # Add keyboard layout from that of GuiVM |  | ||||||
|             kbd_layout = vm.guivm.features.get('keyboard-layout', None) |  | ||||||
|             if kbd_layout: |  | ||||||
|                 vm.untrusted_qdb.write('/keyboard-layout', kbd_layout) |  | ||||||
| 
 |  | ||||||
|         # Set GuiVM prefix |         # Set GuiVM prefix | ||||||
|         guivm_windows_prefix = vm.features.get('guivm-windows-prefix', 'GuiVM') |         guivm_windows_prefix = vm.features.get('guivm-windows-prefix', 'GuiVM') | ||||||
|         if vm.features.get('service.guivm-gui-agent', None): |         if vm.features.get('service.guivm-gui-agent', None): | ||||||
| @ -121,22 +117,21 @@ class GUI(qubes.ext.Extension): | |||||||
|             attached_vm.untrusted_qdb.write('/qubes-gui-domain-xid', |             attached_vm.untrusted_qdb.write('/qubes-gui-domain-xid', | ||||||
|                                             str(vm.xid)) |                                             str(vm.xid)) | ||||||
| 
 | 
 | ||||||
|     @qubes.ext.handler('domain-feature-pre-set:keyboard-layout') |     @qubes.ext.handler('property-reset:keyboard_layout') | ||||||
|     def on_feature_pre_set(self, subject, event, feature, value, oldvalue=None): |     def on_keyboard_reset(self, vm, event, name, oldvalue=None): | ||||||
|         untrusted_xkb_layout = value.split('+') |         if not vm.is_running(): | ||||||
|         if len(untrusted_xkb_layout) != 3: |             return | ||||||
|             raise qubes.exc.QubesValueError("Invalid number of parameters") |         kbd_layout = vm.keyboard_layout | ||||||
| 
 | 
 | ||||||
|         untrusted_layout = untrusted_xkb_layout[0] |         vm.untrusted_qdb.write('/keyboard-layout', kbd_layout) | ||||||
|         untrusted_variant = untrusted_xkb_layout[1] |  | ||||||
|         untrusted_options = untrusted_xkb_layout[2] |  | ||||||
| 
 | 
 | ||||||
|         re_variant = r'^[a-zA-Z0-9-_]*$' |     @qubes.ext.handler('property-set:keyboard_layout') | ||||||
|         re_options = r'^[a-zA-Z0-9-_:,]*$' |     def on_keyboard_set(self, vm, event, name, newvalue, oldvalue=None): | ||||||
|  |         for domain in vm.app.domains: | ||||||
|  |             if getattr(domain, 'guivm', None) == vm and \ | ||||||
|  |                     domain.property_is_default('keyboard_layout'): | ||||||
|  |                 domain.fire_event('property-reset:keyboard_layout', | ||||||
|  |                                   name='keyboard_layout', oldvalue=oldvalue) | ||||||
| 
 | 
 | ||||||
|         if not untrusted_layout.isalpha(): |         if vm.is_running(): | ||||||
|             raise qubes.exc.QubesValueError("Invalid layout provided") |             vm.untrusted_qdb.write('/keyboard-layout', newvalue) | ||||||
|         if not re.match(re_variant, untrusted_variant): |  | ||||||
|             raise qubes.exc.QubesValueError("Invalid variant provided") |  | ||||||
|         if not re.match(re_options, untrusted_options): |  | ||||||
|             raise qubes.exc.QubesValueError("Invalid options provided") |  | ||||||
|  | |||||||
| @ -99,6 +99,7 @@ class TestApp(qubes.tests.TestEmitter): | |||||||
|         self.default_pool_kernel = 'linux-kernel' |         self.default_pool_kernel = 'linux-kernel' | ||||||
|         self.default_qrexec_timeout = 60 |         self.default_qrexec_timeout = 60 | ||||||
|         self.default_netvm = None |         self.default_netvm = None | ||||||
|  |         self.default_guivm = None | ||||||
|         self.domains = TestVMsCollection() |         self.domains = TestVMsCollection() | ||||||
|         #: jinja2 environment for libvirt XML templates |         #: jinja2 environment for libvirt XML templates | ||||||
|         self.env = jinja2.Environment( |         self.env = jinja2.Environment( | ||||||
|  | |||||||
| @ -1877,7 +1877,8 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase): | |||||||
|             name='appvm', qid=3) |             name='appvm', qid=3) | ||||||
|         vm.netvm = None |         vm.netvm = None | ||||||
|         vm.guivm = guivm |         vm.guivm = guivm | ||||||
|         guivm.features['keyboard-layout'] = 'fr++' |         vm.is_running = lambda: True | ||||||
|  |         guivm.keyboard_layout = 'fr++' | ||||||
|         guivm.is_running = lambda: True |         guivm.is_running = lambda: True | ||||||
|         vm.events_enabled = True |         vm.events_enabled = True | ||||||
|         test_qubesdb = TestQubesDB() |         test_qubesdb = TestQubesDB() | ||||||
| @ -1925,6 +1926,7 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase): | |||||||
|             name='appvm', qid=3) |             name='appvm', qid=3) | ||||||
|         vm.netvm = None |         vm.netvm = None | ||||||
|         vm.audiovm = audiovm |         vm.audiovm = audiovm | ||||||
|  |         vm.is_running = lambda: True | ||||||
|         audiovm.is_running = lambda: True |         audiovm.is_running = lambda: True | ||||||
|         vm.events_enabled = True |         vm.events_enabled = True | ||||||
|         test_qubesdb = TestQubesDB() |         test_qubesdb = TestQubesDB() | ||||||
| @ -1968,8 +1970,87 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase): | |||||||
|             name='sys-gui', qid=2, provides_network=False) |             name='sys-gui', qid=2, provides_network=False) | ||||||
|         guivm.is_running = lambda: True |         guivm.is_running = lambda: True | ||||||
|         guivm.events_enabled = True |         guivm.events_enabled = True | ||||||
|         with self.assertRaises(qubes.exc.QubesValueError): |         with self.assertRaises(qubes.exc.QubesPropertyValueError): | ||||||
|             guivm.features['keyboard-layout'] = 'fr123++' |             guivm.keyboard_layout = 'fr123++' | ||||||
|  | 
 | ||||||
|  |         with self.assertRaises(qubes.exc.QubesPropertyValueError): | ||||||
|  |             guivm.keyboard_layout = 'fr+???+' | ||||||
|  | 
 | ||||||
|  |         with self.assertRaises(qubes.exc.QubesPropertyValueError): | ||||||
|  |             guivm.keyboard_layout = 'fr++variant?' | ||||||
|  | 
 | ||||||
|  |         with self.assertRaises(qubes.exc.QubesPropertyValueError): | ||||||
|  |             guivm.keyboard_layout = 'fr' | ||||||
|  | 
 | ||||||
|  |     @unittest.mock.patch('qubes.utils.get_timezone') | ||||||
|  |     @unittest.mock.patch('qubes.utils.urandom') | ||||||
|  |     @unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb') | ||||||
|  |     def test_625_qdb_keyboard_layout_change(self, mock_qubesdb, mock_urandom, | ||||||
|  |             mock_timezone): | ||||||
|  |         mock_urandom.return_value = b'A' * 64 | ||||||
|  |         mock_timezone.return_value = 'UTC' | ||||||
|  |         template = self.get_vm( | ||||||
|  |             cls=qubes.vm.templatevm.TemplateVM, name='template') | ||||||
|  |         template.netvm = None | ||||||
|  |         guivm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template, | ||||||
|  |             name='sys-gui', qid=2, provides_network=False) | ||||||
|  |         vm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template, | ||||||
|  |             name='appvm', qid=3) | ||||||
|  |         vm.netvm = None | ||||||
|  |         vm.guivm = guivm | ||||||
|  |         vm.is_running = lambda: True | ||||||
|  |         guivm.keyboard_layout = 'fr++' | ||||||
|  |         guivm.is_running = lambda: True | ||||||
|  |         vm.events_enabled = True | ||||||
|  |         test_qubesdb = TestQubesDB() | ||||||
|  |         mock_qubesdb.write.side_effect = test_qubesdb.write | ||||||
|  |         mock_qubesdb.rm.side_effect = test_qubesdb.rm | ||||||
|  |         vm.create_qdb_entries() | ||||||
|  |         self.maxDiff = None | ||||||
|  | 
 | ||||||
|  |         expected = { | ||||||
|  |             '/name': 'test-inst-appvm', | ||||||
|  |             '/type': 'AppVM', | ||||||
|  |             '/default-user': 'user', | ||||||
|  |             '/keyboard-layout': 'fr++', | ||||||
|  |             '/qubes-vm-type': 'AppVM', | ||||||
|  |             '/qubes-gui-domain-xid': '{}'.format(guivm.xid), | ||||||
|  |             '/qubes-debug-mode': '0', | ||||||
|  |             '/qubes-base-template': 'test-inst-template', | ||||||
|  |             '/qubes-timezone': 'UTC', | ||||||
|  |             '/qubes-random-seed': base64.b64encode(b'A' * 64), | ||||||
|  |             '/qubes-vm-persistence': 'rw-only', | ||||||
|  |             '/qubes-vm-updateable': 'False', | ||||||
|  |             '/qubes-block-devices': '', | ||||||
|  |             '/qubes-usb-devices': '', | ||||||
|  |             '/qubes-iptables': 'reload', | ||||||
|  |             '/qubes-iptables-error': '', | ||||||
|  |             '/qubes-iptables-header': unittest.mock.ANY, | ||||||
|  |             '/qubes-service/qubes-update-check': '0', | ||||||
|  |             '/qubes-service/meminfo-writer': '1', | ||||||
|  |             '/connected-ips': '', | ||||||
|  |             '/connected-ips6': '', | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         with self.subTest('default'): | ||||||
|  |             self.assertEqual(test_qubesdb.data, expected) | ||||||
|  | 
 | ||||||
|  |         with self.subTest('value_change'): | ||||||
|  |             vm.keyboard_layout = 'de++' | ||||||
|  |             expected['/keyboard-layout'] = 'de++' | ||||||
|  |             self.assertEqual(test_qubesdb.data, expected) | ||||||
|  | 
 | ||||||
|  |         with self.subTest('value_revert'): | ||||||
|  |             vm.keyboard_layout = qubes.property.DEFAULT | ||||||
|  |             expected['/keyboard-layout'] = 'fr++' | ||||||
|  |             self.assertEqual(test_qubesdb.data, expected) | ||||||
|  | 
 | ||||||
|  |         with self.subTest('no_default'): | ||||||
|  |             guivm.keyboard_layout = qubes.property.DEFAULT | ||||||
|  |             vm.keyboard_layout = qubes.property.DEFAULT | ||||||
|  |             expected['/keyboard-layout'] = 'us++' | ||||||
|  |             self.assertEqual(test_qubesdb.data, expected) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|     @asyncio.coroutine |     @asyncio.coroutine | ||||||
|     def coroutine_mock(self, mock, *args, **kwargs): |     def coroutine_mock(self, mock, *args, **kwargs): | ||||||
|  | |||||||
| @ -28,6 +28,7 @@ import libvirt | |||||||
| import qubes | import qubes | ||||||
| import qubes.exc | import qubes.exc | ||||||
| import qubes.vm | import qubes.vm | ||||||
|  | from qubes.vm.qubesvm import _setter_kbd_layout | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AdminVM(qubes.vm.BaseVM): | class AdminVM(qubes.vm.BaseVM): | ||||||
| @ -61,6 +62,14 @@ class AdminVM(qubes.vm.BaseVM): | |||||||
|         setter=qubes.property.forbidden, |         setter=qubes.property.forbidden, | ||||||
|         doc='True if this machine may be updated on its own.') |         doc='True if this machine may be updated on its own.') | ||||||
| 
 | 
 | ||||||
|  |     # for changes in keyboard_layout, see also the same property in QubesVM | ||||||
|  |     keyboard_layout = qubes.property( | ||||||
|  |         'keyboard_layout', | ||||||
|  |         type=str, | ||||||
|  |         setter=_setter_kbd_layout, | ||||||
|  |         default='us++', | ||||||
|  |         doc='Keyboard layout for this VM') | ||||||
|  | 
 | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         super().__init__(*args, **kwargs) |         super().__init__(*args, **kwargs) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ | |||||||
| import asyncio | import asyncio | ||||||
| import base64 | import base64 | ||||||
| import grp | import grp | ||||||
|  | import re | ||||||
| import os | import os | ||||||
| import os.path | import os.path | ||||||
| import shutil | import shutil | ||||||
| @ -115,6 +116,32 @@ def _setter_virt_mode(self, prop, value): | |||||||
|     return value |     return value | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def _setter_kbd_layout(self, prop, value): | ||||||
|  |     untrusted_xkb_layout = value.split('+') | ||||||
|  |     if len(untrusted_xkb_layout) != 3: | ||||||
|  |         raise qubes.exc.QubesPropertyValueError( | ||||||
|  |             self, prop, value, "invalid number of keyboard layout parameters") | ||||||
|  | 
 | ||||||
|  |     untrusted_layout = untrusted_xkb_layout[0] | ||||||
|  |     untrusted_variant = untrusted_xkb_layout[1] | ||||||
|  |     untrusted_options = untrusted_xkb_layout[2] | ||||||
|  | 
 | ||||||
|  |     re_variant = r'^[a-zA-Z0-9-_]*$' | ||||||
|  |     re_options = r'^[a-zA-Z0-9-_:,]*$' | ||||||
|  | 
 | ||||||
|  |     if not untrusted_layout.isalpha(): | ||||||
|  |         raise qubes.exc.QubesPropertyValueError( | ||||||
|  |             self, prop, value, "Invalid keyboard layout provided") | ||||||
|  |     if not re.match(re_variant, untrusted_variant): | ||||||
|  |         raise qubes.exc.QubesPropertyValueError( | ||||||
|  |             self, prop, value, "Invalid layout variant provided") | ||||||
|  |     if not re.match(re_options, untrusted_options): | ||||||
|  |         raise qubes.exc.QubesPropertyValueError( | ||||||
|  |             self, prop, value, "Invalid layout options provided") | ||||||
|  | 
 | ||||||
|  |     return value | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def _default_virt_mode(self): | def _default_virt_mode(self): | ||||||
|     if self.devices['pci'].persistent(): |     if self.devices['pci'].persistent(): | ||||||
|         return 'hvm' |         return 'hvm' | ||||||
| @ -690,6 +717,14 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): | |||||||
|         setter=qubes.property.forbidden, |         setter=qubes.property.forbidden, | ||||||
|         doc='True if this machine may be updated on its own.') |         doc='True if this machine may be updated on its own.') | ||||||
| 
 | 
 | ||||||
|  |     # for changes in keyboard_layout, see also the same property in AdminVM | ||||||
|  |     keyboard_layout = qubes.property( | ||||||
|  |         'keyboard_layout', | ||||||
|  |         default=(lambda self: getattr(self.guivm, 'keyboard_layout', 'us++')), | ||||||
|  |         type=str, | ||||||
|  |         setter=_setter_kbd_layout, | ||||||
|  |         doc='Keyboard layout for this VM') | ||||||
|  | 
 | ||||||
|     # |     # | ||||||
|     # static, class-wide properties |     # static, class-wide properties | ||||||
|     # |     # | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Marta Marczykowska-Górecka
						Marta Marczykowska-Górecka