Bladeren bron

Merge remote-tracking branch 'origin/pr/350'

* origin/pr/350:
  Changed feature keyboard_layout to a property
Marek Marczykowski-Górecki 3 jaren geleden
bovenliggende
commit
01b33e58d7
5 gewijzigde bestanden met toevoegingen van 152 en 31 verwijderingen
  1. 23 28
      qubes/ext/gui.py
  2. 1 0
      qubes/tests/vm/__init__.py
  3. 84 3
      qubes/tests/vm/qubesvm.py
  4. 9 0
      qubes/vm/adminvm.py
  5. 35 0
      qubes/vm/qubesvm.py

+ 23 - 28
qubes/ext/gui.py

@@ -21,8 +21,6 @@
 # License along with this library; if not, see <https://www.gnu.org/licenses/>.
 #
 
-import re
-
 import qubes.config
 import qubes.ext
 import qubes.exc
@@ -89,14 +87,12 @@ class GUI(qubes.ext.Extension):
 
         # Add GuiVM Xen ID for gui-daemon
         if getattr(vm, 'guivm', None):
-            if vm != vm.guivm and vm.guivm.is_running():
-                vm.untrusted_qdb.write('/qubes-gui-domain-xid',
-                                       str(vm.guivm.xid))
+            if vm != vm.guivm:
+                vm.untrusted_qdb.write('/keyboard-layout', vm.keyboard_layout)
 
-            # 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)
+                if vm.guivm.is_running():
+                    vm.untrusted_qdb.write('/qubes-gui-domain-xid',
+                                           str(vm.guivm.xid))
 
         # Set GuiVM prefix
         guivm_windows_prefix = vm.features.get('guivm-windows-prefix', 'GuiVM')
@@ -121,22 +117,21 @@ class GUI(qubes.ext.Extension):
             attached_vm.untrusted_qdb.write('/qubes-gui-domain-xid',
                                             str(vm.xid))
 
-    @qubes.ext.handler('domain-feature-pre-set:keyboard-layout')
-    def on_feature_pre_set(self, subject, event, feature, value, oldvalue=None):
-        untrusted_xkb_layout = value.split('+')
-        if len(untrusted_xkb_layout) != 3:
-            raise qubes.exc.QubesValueError("Invalid number of 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.QubesValueError("Invalid layout provided")
-        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")
+    @qubes.ext.handler('property-reset:keyboard_layout')
+    def on_keyboard_reset(self, vm, event, name, oldvalue=None):
+        if not vm.is_running():
+            return
+        kbd_layout = vm.keyboard_layout
+
+        vm.untrusted_qdb.write('/keyboard-layout', kbd_layout)
+
+    @qubes.ext.handler('property-set:keyboard_layout')
+    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 vm.is_running():
+            vm.untrusted_qdb.write('/keyboard-layout', newvalue)

+ 1 - 0
qubes/tests/vm/__init__.py

@@ -100,6 +100,7 @@ class TestApp(qubes.tests.TestEmitter):
         self.default_pool_kernel = 'linux-kernel'
         self.default_qrexec_timeout = 60
         self.default_netvm = None
+        self.default_guivm = None
         self.domains = TestVMsCollection()
         #: jinja2 environment for libvirt XML templates
         self.env = jinja2.Environment(

+ 84 - 3
qubes/tests/vm/qubesvm.py

@@ -1857,7 +1857,8 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase):
             name='appvm', qid=3)
         vm.netvm = None
         vm.guivm = guivm
-        guivm.features['keyboard-layout'] = 'fr++'
+        vm.is_running = lambda: True
+        guivm.keyboard_layout = 'fr++'
         guivm.is_running = lambda: True
         vm.events_enabled = True
         test_qubesdb = TestQubesDB()
@@ -1905,6 +1906,7 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase):
             name='appvm', qid=3)
         vm.netvm = None
         vm.audiovm = audiovm
+        vm.is_running = lambda: True
         audiovm.is_running = lambda: True
         vm.events_enabled = True
         test_qubesdb = TestQubesDB()
@@ -1948,8 +1950,87 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase):
             name='sys-gui', qid=2, provides_network=False)
         guivm.is_running = lambda: True
         guivm.events_enabled = True
-        with self.assertRaises(qubes.exc.QubesValueError):
-            guivm.features['keyboard-layout'] = 'fr123++'
+        with self.assertRaises(qubes.exc.QubesPropertyValueError):
+            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
     def coroutine_mock(self, mock, *args, **kwargs):

+ 9 - 0
qubes/vm/adminvm.py

@@ -28,6 +28,7 @@ import libvirt
 import qubes
 import qubes.exc
 import qubes.vm
+from qubes.vm.qubesvm import _setter_kbd_layout
 
 
 class AdminVM(qubes.vm.BaseVM):
@@ -61,6 +62,14 @@ class AdminVM(qubes.vm.BaseVM):
         setter=qubes.property.forbidden,
         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):
         super().__init__(*args, **kwargs)
 

+ 35 - 0
qubes/vm/qubesvm.py

@@ -23,6 +23,7 @@
 import asyncio
 import base64
 import grp
+import re
 import os
 import os.path
 import shutil
@@ -115,6 +116,32 @@ def _setter_virt_mode(self, prop, 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):
     if self.devices['pci'].persistent():
         return 'hvm'
@@ -690,6 +717,14 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
         setter=qubes.property.forbidden,
         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
     #