Update windows-related feature requests
Handle 'os' feature - if it's Windows, then set rpc-clipboard feature. Handle 'gui-emulated' feature - request for specifically stubdomain GUI. With 'gui' feature it is only possible to enable gui-agent based on, or disable GUI completely. Handle 'default-user' - verify it for weird characters and set 'default_user' property (if wasn't already set). QubesOS/qubes-issues#3585
This commit is contained in:
		
							parent
							
								
									ce87451c73
								
							
						
					
					
						commit
						af7d54d388
					
				@ -76,7 +76,8 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        untrusted_features = {}
 | 
					        untrusted_features = {}
 | 
				
			||||||
        safe_set = string.ascii_letters + string.digits
 | 
					        safe_set = string.ascii_letters + string.digits
 | 
				
			||||||
        expected_features = ('qrexec', 'gui', 'default-user')
 | 
					        expected_features = ('qrexec', 'gui', 'gui-emulated', 'default-user',
 | 
				
			||||||
 | 
					            'os')
 | 
				
			||||||
        for feature in expected_features:
 | 
					        for feature in expected_features:
 | 
				
			||||||
            untrusted_value = self.src.untrusted_qdb.read(
 | 
					            untrusted_value = self.src.untrusted_qdb.read(
 | 
				
			||||||
                '/qubes-tools/' + feature)
 | 
					                '/qubes-tools/' + feature)
 | 
				
			||||||
 | 
				
			|||||||
@ -32,7 +32,7 @@ class CoreFeatures(qubes.ext.Extension):
 | 
				
			|||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        requested_features = {}
 | 
					        requested_features = {}
 | 
				
			||||||
        for feature in ('qrexec', 'gui', 'qubes-firewall'):
 | 
					        for feature in ('qrexec', 'gui', 'gui-emulated', 'qubes-firewall'):
 | 
				
			||||||
            untrusted_value = untrusted_features.get(feature, None)
 | 
					            untrusted_value = untrusted_features.get(feature, None)
 | 
				
			||||||
            if untrusted_value in ('1', '0'):
 | 
					            if untrusted_value in ('1', '0'):
 | 
				
			||||||
                requested_features[feature] = bool(int(untrusted_value))
 | 
					                requested_features[feature] = bool(int(untrusted_value))
 | 
				
			||||||
@ -44,7 +44,7 @@ class CoreFeatures(qubes.ext.Extension):
 | 
				
			|||||||
        # gui agent presence (0 or 1)
 | 
					        # gui agent presence (0 or 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        qrexec_before = vm.features.get('qrexec', False)
 | 
					        qrexec_before = vm.features.get('qrexec', False)
 | 
				
			||||||
        for feature in ('qrexec', 'gui'):
 | 
					        for feature in ('qrexec', 'gui', 'gui-emulated'):
 | 
				
			||||||
            # do not allow (Template)VM to override setting if already set
 | 
					            # do not allow (Template)VM to override setting if already set
 | 
				
			||||||
            # some other way
 | 
					            # some other way
 | 
				
			||||||
            if feature in requested_features and feature not in vm.features:
 | 
					            if feature in requested_features and feature not in vm.features:
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										64
									
								
								qubes/ext/windows.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								qubes/ext/windows.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					# -*- encoding: utf-8 -*-
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# The Qubes OS Project, http://www.qubes-os.org
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Copyright (C) 2017 Marek Marczykowski-Górecki
 | 
				
			||||||
 | 
					#                               <marmarek@invisiblethingslab.com>
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This program is free software; you can redistribute it and/or modify
 | 
				
			||||||
 | 
					# it under the terms of the GNU General Public License as published by
 | 
				
			||||||
 | 
					# the Free Software Foundation; either version 2 of the License, or
 | 
				
			||||||
 | 
					# (at your option) any later version.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This program is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
				
			||||||
 | 
					# GNU General Public License for more details.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# You should have received a copy of the GNU General Public License along
 | 
				
			||||||
 | 
					# with this program; if not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import qubes.ext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WindowsFeatures(qubes.ext.Extension):
 | 
				
			||||||
 | 
					    # pylint: disable=too-few-public-methods
 | 
				
			||||||
 | 
					    @qubes.ext.handler('features-request')
 | 
				
			||||||
 | 
					    def qubes_features_request(self, vm, event, untrusted_features):
 | 
				
			||||||
 | 
					        '''Handle features provided requested by Qubes Windows Tools'''
 | 
				
			||||||
 | 
					        # pylint: disable=no-self-use,unused-argument
 | 
				
			||||||
 | 
					        if getattr(vm, 'template', None):
 | 
				
			||||||
 | 
					            vm.log.warning(
 | 
				
			||||||
 | 
					                'Ignoring qubes.NotifyTools for template-based VM')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        guest_os = None
 | 
				
			||||||
 | 
					        if 'os' in untrusted_features:
 | 
				
			||||||
 | 
					            if untrusted_features['os'] in ['Windows']:
 | 
				
			||||||
 | 
					                guest_os = untrusted_features['os']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        qrexec = None
 | 
				
			||||||
 | 
					        if 'qrexec' in untrusted_features:
 | 
				
			||||||
 | 
					            if untrusted_features['qrexec'] == '1':
 | 
				
			||||||
 | 
					                # qrexec feature is set by CoreFeatures extension
 | 
				
			||||||
 | 
					                qrexec = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        del untrusted_features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if guest_os:
 | 
				
			||||||
 | 
					            vm.features['os'] = guest_os
 | 
				
			||||||
 | 
					        if guest_os == 'Windows' and qrexec:
 | 
				
			||||||
 | 
					            vm.features['rpc-clipboard'] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @qubes.ext.handler('domain-add', system=True)
 | 
				
			||||||
 | 
					    def on_domain_add(self, app, _event, vm, **kwargs):
 | 
				
			||||||
 | 
					        # pylint: disable=no-self-use,unused-argument
 | 
				
			||||||
 | 
					        if getattr(vm, 'template', None) is None:
 | 
				
			||||||
 | 
					            # handle only template-based vms
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        template = vm.template
 | 
				
			||||||
 | 
					        if template.features.check_with_template('os', None) != 'Windows':
 | 
				
			||||||
 | 
					            # ignore non-windows templates
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # TODO: consider copying template's root volume here
 | 
				
			||||||
@ -131,11 +131,14 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
 | 
				
			|||||||
        self.assertEqual(self.src.mock_calls, [
 | 
					        self.assertEqual(self.src.mock_calls, [
 | 
				
			||||||
            mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
 | 
					            mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
 | 
				
			||||||
            mock.call.untrusted_qdb.read('/qubes-tools/gui'),
 | 
					            mock.call.untrusted_qdb.read('/qubes-tools/gui'),
 | 
				
			||||||
 | 
					            mock.call.untrusted_qdb.read('/qubes-tools/gui-emulated'),
 | 
				
			||||||
            mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
 | 
					            mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
 | 
				
			||||||
 | 
					            mock.call.untrusted_qdb.read('/qubes-tools/os'),
 | 
				
			||||||
            mock.call.fire_event_async('features-request', untrusted_features={
 | 
					            mock.call.fire_event_async('features-request', untrusted_features={
 | 
				
			||||||
                'gui': '1',
 | 
					                'gui': '1',
 | 
				
			||||||
                'default-user': 'user',
 | 
					                'default-user': 'user',
 | 
				
			||||||
                'qrexec': '1'}),
 | 
					                'qrexec': '1',
 | 
				
			||||||
 | 
					                'os': 'Linux'}),
 | 
				
			||||||
            ('fire_event_async().__iter__', (), {}),
 | 
					            ('fire_event_async().__iter__', (), {}),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
        self.assertEqual(self.app.mock_calls, [mock.call.save()])
 | 
					        self.assertEqual(self.app.mock_calls, [mock.call.save()])
 | 
				
			||||||
@ -153,11 +156,14 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
 | 
				
			|||||||
        self.assertEqual(self.src.mock_calls, [
 | 
					        self.assertEqual(self.src.mock_calls, [
 | 
				
			||||||
            mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
 | 
					            mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
 | 
				
			||||||
            mock.call.untrusted_qdb.read('/qubes-tools/gui'),
 | 
					            mock.call.untrusted_qdb.read('/qubes-tools/gui'),
 | 
				
			||||||
 | 
					            mock.call.untrusted_qdb.read('/qubes-tools/gui-emulated'),
 | 
				
			||||||
            mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
 | 
					            mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
 | 
				
			||||||
 | 
					            mock.call.untrusted_qdb.read('/qubes-tools/os'),
 | 
				
			||||||
            mock.call.fire_event_async('features-request', untrusted_features={
 | 
					            mock.call.fire_event_async('features-request', untrusted_features={
 | 
				
			||||||
                'gui': '1',
 | 
					                'gui': '1',
 | 
				
			||||||
                'default-user': 'user',
 | 
					                'default-user': 'user',
 | 
				
			||||||
                'qrexec': '1'}),
 | 
					                'qrexec': '1',
 | 
				
			||||||
 | 
					                'os': 'Linux'}),
 | 
				
			||||||
            ('fire_event_async().__iter__', (), {}),
 | 
					            ('fire_event_async().__iter__', (), {}),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
        self.assertEqual(self.app.mock_calls, [mock.call.save()])
 | 
					        self.assertEqual(self.app.mock_calls, [mock.call.save()])
 | 
				
			||||||
 | 
				
			|||||||
@ -21,6 +21,7 @@
 | 
				
			|||||||
from unittest import mock
 | 
					from unittest import mock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import qubes.ext.core_features
 | 
					import qubes.ext.core_features
 | 
				
			||||||
 | 
					import qubes.ext.windows
 | 
				
			||||||
import qubes.tests
 | 
					import qubes.tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -163,3 +164,53 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase):
 | 
				
			|||||||
            ('features.__contains__', ('qrexec',), {}),
 | 
					            ('features.__contains__', ('qrexec',), {}),
 | 
				
			||||||
            ('features.__contains__', ('gui',), {}),
 | 
					            ('features.__contains__', ('gui',), {}),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TC_10_WindowsFeatures(qubes.tests.QubesTestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        super().setUp()
 | 
				
			||||||
 | 
					        self.ext = qubes.ext.windows.WindowsFeatures()
 | 
				
			||||||
 | 
					        self.vm = mock.MagicMock()
 | 
				
			||||||
 | 
					        self.features = {}
 | 
				
			||||||
 | 
					        self.vm.configure_mock(**{
 | 
				
			||||||
 | 
					            'features.get.side_effect': self.features.get,
 | 
				
			||||||
 | 
					            'features.__contains__.side_effect': self.features.__contains__,
 | 
				
			||||||
 | 
					            'features.__setitem__.side_effect': self.features.__setitem__,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_000_notify_tools_full(self):
 | 
				
			||||||
 | 
					        del self.vm.template
 | 
				
			||||||
 | 
					        self.ext.qubes_features_request(self.vm, 'features-request',
 | 
				
			||||||
 | 
					            untrusted_features={
 | 
				
			||||||
 | 
					                'gui': '1',
 | 
				
			||||||
 | 
					                'version': '1',
 | 
				
			||||||
 | 
					                'default-user': 'user',
 | 
				
			||||||
 | 
					                'qrexec': '1',
 | 
				
			||||||
 | 
					                'os': 'Windows'})
 | 
				
			||||||
 | 
					        self.assertEqual(self.vm.mock_calls, [
 | 
				
			||||||
 | 
					            ('features.__setitem__', ('os', 'Windows'), {}),
 | 
				
			||||||
 | 
					            ('features.__setitem__', ('rpc-clipboard', True), {}),
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_001_notify_tools_no_qrexec(self):
 | 
				
			||||||
 | 
					        del self.vm.template
 | 
				
			||||||
 | 
					        self.ext.qubes_features_request(self.vm, 'features-request',
 | 
				
			||||||
 | 
					            untrusted_features={
 | 
				
			||||||
 | 
					                'gui': '1',
 | 
				
			||||||
 | 
					                'version': '1',
 | 
				
			||||||
 | 
					                'default-user': 'user',
 | 
				
			||||||
 | 
					                'qrexec': '0',
 | 
				
			||||||
 | 
					                'os': 'Windows'})
 | 
				
			||||||
 | 
					        self.assertEqual(self.vm.mock_calls, [
 | 
				
			||||||
 | 
					            ('features.__setitem__', ('os', 'Windows'), {}),
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_002_notify_tools_other_os(self):
 | 
				
			||||||
 | 
					        del self.vm.template
 | 
				
			||||||
 | 
					        self.ext.qubes_features_request(self.vm, 'features-request',
 | 
				
			||||||
 | 
					            untrusted_features={
 | 
				
			||||||
 | 
					                'gui': '1',
 | 
				
			||||||
 | 
					                'version': '1',
 | 
				
			||||||
 | 
					                'default-user': 'user',
 | 
				
			||||||
 | 
					                'qrexec': '1',
 | 
				
			||||||
 | 
					                'os': 'Linux'})
 | 
				
			||||||
 | 
					        self.assertEqual(self.vm.mock_calls, [])
 | 
				
			||||||
 | 
				
			|||||||
@ -284,6 +284,7 @@ fi
 | 
				
			|||||||
%{python3_sitelib}/qubes/ext/qubesmanager.py
 | 
					%{python3_sitelib}/qubes/ext/qubesmanager.py
 | 
				
			||||||
%{python3_sitelib}/qubes/ext/r3compatibility.py
 | 
					%{python3_sitelib}/qubes/ext/r3compatibility.py
 | 
				
			||||||
%{python3_sitelib}/qubes/ext/services.py
 | 
					%{python3_sitelib}/qubes/ext/services.py
 | 
				
			||||||
 | 
					%{python3_sitelib}/qubes/ext/windows.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%dir %{python3_sitelib}/qubes/tests
 | 
					%dir %{python3_sitelib}/qubes/tests
 | 
				
			||||||
%dir %{python3_sitelib}/qubes/tests/__pycache__
 | 
					%dir %{python3_sitelib}/qubes/tests/__pycache__
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								setup.py
									
									
									
									
									
								
							@ -74,6 +74,7 @@ if __name__ == '__main__':
 | 
				
			|||||||
                'qubes.ext.pci = qubes.ext.pci:PCIDeviceExtension',
 | 
					                'qubes.ext.pci = qubes.ext.pci:PCIDeviceExtension',
 | 
				
			||||||
                'qubes.ext.block = qubes.ext.block:BlockDeviceExtension',
 | 
					                'qubes.ext.block = qubes.ext.block:BlockDeviceExtension',
 | 
				
			||||||
                'qubes.ext.services = qubes.ext.services:ServicesExtension',
 | 
					                'qubes.ext.services = qubes.ext.services:ServicesExtension',
 | 
				
			||||||
 | 
					                'qubes.ext.windows = qubes.ext.windows:WindowsFeatures',
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            'qubes.devices': [
 | 
					            'qubes.devices': [
 | 
				
			||||||
                'pci = qubes.ext.pci:PCIDevice',
 | 
					                'pci = qubes.ext.pci:PCIDevice',
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user