core-admin/qubes/tests/ext.py
Marek Marczykowski-Górecki d1f5cb5d15
ext/services: mechanism for advertising supported services
Support 'supported-service.*' features requests coming from VMs. Set
such features directly (allow only value '1') and remove any not
reported in given call. This way uninstalling package providing given
service will automatically remove related 'supported-service...'
feature.

Fixes QubesOS/qubes-issues#4402
2018-10-23 16:47:39 +02:00

308 lines
12 KiB
Python

# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2017 Marek Marczykowski-Górecki
# <marmarek@invisiblethingslab.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
from unittest import mock
import qubes.ext.core_features
import qubes.ext.services
import qubes.ext.windows
import qubes.tests
class TC_00_CoreFeatures(qubes.tests.QubesTestCase):
def setUp(self):
super().setUp()
self.ext = qubes.ext.core_features.CoreFeatures()
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_010_notify_tools(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'}),
self.assertEqual(self.vm.mock_calls, [
('features.get', ('qrexec', False), {}),
('features.__contains__', ('qrexec',), {}),
('features.__setitem__', ('qrexec', True), {}),
('features.__contains__', ('gui',), {}),
('features.__setitem__', ('gui', True), {}),
('features.get', ('qrexec', False), {}),
('fire_event', ('template-postinstall',), {})
])
def test_011_notify_tools_uninstall(self):
del self.vm.template
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'gui': '0',
'version': '1',
'default-user': 'user',
'qrexec': '0'}),
self.assertEqual(self.vm.mock_calls, [
('features.get', ('qrexec', False), {}),
('features.__contains__', ('qrexec',), {}),
('features.__setitem__', ('qrexec', False), {}),
('features.__contains__', ('gui',), {}),
('features.__setitem__', ('gui', False), {}),
('features.get', ('qrexec', False), {}),
])
def test_012_notify_tools_uninstall2(self):
del self.vm.template
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'version': '1',
'default-user': 'user',
})
self.assertEqual(self.vm.mock_calls, [
('features.get', ('qrexec', False), {}),
('features.get', ('qrexec', False), {}),
])
def test_013_notify_tools_no_version(self):
del self.vm.template
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'qrexec': '1',
'gui': '1',
'default-user': 'user',
})
self.assertEqual(self.vm.mock_calls, [
('features.get', ('qrexec', False), {}),
('features.__contains__', ('qrexec',), {}),
('features.__setitem__', ('qrexec', True), {}),
('features.__contains__', ('gui',), {}),
('features.__setitem__', ('gui', True), {}),
('features.get', ('qrexec', False), {}),
('fire_event', ('template-postinstall',), {})
])
def test_015_notify_tools_invalid_value_qrexec(self):
del self.vm.template
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'version': '1',
'qrexec': 'invalid',
'gui': '1',
'default-user': 'user',
})
self.assertEqual(self.vm.mock_calls, [
('features.get', ('qrexec', False), {}),
('features.__contains__', ('gui',), {}),
('features.__setitem__', ('gui', True), {}),
('features.get', ('qrexec', False), {}),
])
def test_016_notify_tools_invalid_value_gui(self):
del self.vm.template
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'version': '1',
'qrexec': '1',
'gui': 'invalid',
'default-user': 'user',
})
self.assertEqual(self.vm.mock_calls, [
('features.get', ('qrexec', False), {}),
('features.__contains__', ('qrexec',), {}),
('features.__setitem__', ('qrexec', True), {}),
('features.get', ('qrexec', False), {}),
('fire_event', ('template-postinstall',), {})
])
def test_017_notify_tools_template_based(self):
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'version': '1',
'qrexec': '1',
'gui': '1',
'default-user': 'user',
})
self.assertEqual(self.vm.mock_calls, [
('template.__bool__', (), {}),
('log.warning', ('Ignoring qubes.NotifyTools for template-based '
'VM',), {})
])
def test_018_notify_tools_already_installed(self):
self.features['qrexec'] = True
self.features['gui'] = True
del self.vm.template
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'gui': '1',
'version': '1',
'default-user': 'user',
'qrexec': '1'}),
self.assertEqual(self.vm.mock_calls, [
('features.get', ('qrexec', False), {}),
('features.__contains__', ('qrexec',), {}),
('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, [])
class TC_20_Services(qubes.tests.QubesTestCase):
def setUp(self):
super().setUp()
self.ext = qubes.ext.services.ServicesExtension()
self.vm = mock.MagicMock()
self.features = {}
self.vm.configure_mock(**{
'template': None,
'is_running.return_value': True,
'features.get.side_effect': self.features.get,
'features.items.side_effect': self.features.items,
'features.__iter__.side_effect': self.features.__iter__,
'features.__contains__.side_effect': self.features.__contains__,
'features.__setitem__.side_effect': self.features.__setitem__,
'features.__delitem__.side_effect': self.features.__delitem__,
})
def test_000_write_to_qdb(self):
self.features['service.test1'] = '1'
self.features['service.test2'] = ''
self.ext.on_domain_qdb_create(self.vm, 'domain-qdb-create')
self.assertEqual(sorted(self.vm.untrusted_qdb.mock_calls), [
('write', ('/qubes-service/test1', '1'), {}),
('write', ('/qubes-service/test2', '0'), {}),
])
def test_001_feature_set(self):
self.ext.on_domain_feature_set(self.vm,
'feature-set:service.test_no_oldvalue',
'service.test_no_oldvalue', '1')
self.ext.on_domain_feature_set(self.vm,
'feature-set:service.test_oldvalue',
'service.test_oldvalue', '1', '')
self.ext.on_domain_feature_set(self.vm,
'feature-set:service.test_disable',
'service.test_disable', '', '1')
self.ext.on_domain_feature_set(self.vm,
'feature-set:service.test_disable_no_oldvalue',
'service.test_disable_no_oldvalue', '')
self.assertEqual(sorted(self.vm.untrusted_qdb.mock_calls), sorted([
('write', ('/qubes-service/test_no_oldvalue', '1'), {}),
('write', ('/qubes-service/test_oldvalue', '1'), {}),
('write', ('/qubes-service/test_disable', '0'), {}),
('write', ('/qubes-service/test_disable_no_oldvalue', '0'), {}),
]))
def test_002_feature_delete(self):
self.ext.on_domain_feature_delete(self.vm,
'feature-delete:service.test3', 'service.test3')
self.assertEqual(sorted(self.vm.untrusted_qdb.mock_calls), [
('rm', ('/qubes-service/test3',), {}),
])
def test_010_supported_services(self):
self.ext.supported_services(self.vm, 'features-request',
untrusted_features={
'supported-service.test1': '1', # ok
'supported-service.test2': '0', # ignored
'supported-service.test3': 'some text', # ignored
'no-service': '1', # ignored
})
self.assertEqual(self.features, {
'supported-service.test1': True,
})
def test_011_supported_services_add(self):
self.features['supported-service.test1'] = '1'
self.ext.supported_services(self.vm, 'features-request',
untrusted_features={
'supported-service.test1': '1', # ok
'supported-service.test2': '1', # ok
})
# also check if existing one is untouched
self.assertEqual(self.features, {
'supported-service.test1': '1',
'supported-service.test2': True,
})
def test_012_supported_services_remove(self):
self.features['supported-service.test1'] = '1'
self.ext.supported_services(self.vm, 'features-request',
untrusted_features={
'supported-service.test2': '1', # ok
})
self.assertEqual(self.features, {
'supported-service.test2': True,
})