diff --git a/Makefile b/Makefile index e219888b..3b27dc74 100644 --- a/Makefile +++ b/Makefile @@ -173,6 +173,8 @@ endif cp qubes-rpc/qubes.NotifyTools $(DESTDIR)/etc/qubes-rpc/ cp qubes-rpc/qubes.NotifyUpdates $(DESTDIR)/etc/qubes-rpc/ install qubes-rpc/qubesd-query-fast $(DESTDIR)/usr/libexec/qubes/ + install -m 0755 qvm-tools/qubes-bug-report $(DESTDIR)/usr/bin/qubes-bug-report + install -m 0755 qvm-tools/qubes-hcl-report $(DESTDIR)/usr/bin/qubes-hcl-report install -m 0755 qvm-tools/qvm-sync-clock $(DESTDIR)/usr/bin/qvm-sync-clock for method in $(ADMIN_API_METHODS_SIMPLE); do \ ln -s ../../usr/libexec/qubes/qubesd-query-fast \ diff --git a/qubes/ext/services.py b/qubes/ext/services.py index 85f29dde..23be77c0 100644 --- a/qubes/ext/services.py +++ b/qubes/ext/services.py @@ -28,8 +28,9 @@ class ServicesExtension(qubes.ext.Extension): ''' # pylint: disable=no-self-use @qubes.ext.handler('domain-qdb-create') - def on_domain_qdb_create(self, vm): + def on_domain_qdb_create(self, vm, event): '''Actually export features''' + # pylint: disable=unused-argument for feature, value in vm.features.items(): if not feature.startswith('service.'): continue @@ -39,7 +40,7 @@ class ServicesExtension(qubes.ext.Extension): str(int(bool(value)))) @qubes.ext.handler('domain-feature-set') - def on_domain_feature_set(self, vm, feature, value, oldvalue=None): + def on_domain_feature_set(self, vm, event, feature, value, oldvalue=None): '''Update /qubes-service/ QubesDB tree in runtime''' # pylint: disable=unused-argument if not vm.is_running(): @@ -52,8 +53,9 @@ class ServicesExtension(qubes.ext.Extension): str(int(bool(value)))) @qubes.ext.handler('domain-feature-delete') - def on_domain_feature_delete(self, vm, feature): + def on_domain_feature_delete(self, vm, event, feature): '''Update /qubes-service/ QubesDB tree in runtime''' + # pylint: disable=unused-argument if not vm.is_running(): return if not feature.startswith('service.'): diff --git a/qubes/tests/__init__.py b/qubes/tests/__init__.py index 80b18eba..d41e2739 100644 --- a/qubes/tests/__init__.py +++ b/qubes/tests/__init__.py @@ -1010,6 +1010,7 @@ def load_tests(loader, tests, pattern): # pylint: disable=unused-argument 'qubes.tests.api_admin', 'qubes.tests.api_misc', 'qubespolicy.tests', + 'qubespolicy.tests.cli', ): tests.addTests(loader.loadTestsFromName(modname)) diff --git a/qubes/vm/__init__.py b/qubes/vm/__init__.py index 7b714038..4694efad 100644 --- a/qubes/vm/__init__.py +++ b/qubes/vm/__init__.py @@ -60,6 +60,9 @@ def validate_name(holder, prop, value): else: raise qubes.exc.QubesValueError( 'VM name contains illegal characters') + if value in ('none', 'default'): + raise qubes.exc.QubesValueError( + 'VM name cannot be \'none\' nor \'default\'') class Features(dict): diff --git a/qubespolicy/__init__.py b/qubespolicy/__init__.py index 37532d2c..c3b5da10 100755 --- a/qubespolicy/__init__.py +++ b/qubespolicy/__init__.py @@ -47,6 +47,12 @@ class PolicySyntaxError(AccessDenied): super(PolicySyntaxError, self).__init__( '{}:{}: {}'.format(filename, lineno, msg)) +class PolicyNotFound(AccessDenied): + ''' Policy was not found for this service ''' + def __init__(self, service_name): + super(PolicyNotFound, self).__init__( + 'Policy not found for service {}'.format(service_name)) + class Action(enum.Enum): ''' Action as defined by policy ''' @@ -486,6 +492,8 @@ class Policy(object): if not os.path.exists(policy_file): # fallback to policy without specific argument set (if any) policy_file = os.path.join(policy_dir, service.split('+')[0]) + if not os.path.exists(policy_file): + raise PolicyNotFound(service) #: policy storage directory self.policy_dir = policy_dir diff --git a/qubespolicy/agent.py b/qubespolicy/agent.py index f0bf4df3..704312c1 100644 --- a/qubespolicy/agent.py +++ b/qubespolicy/agent.py @@ -30,6 +30,7 @@ from gi.repository import GLib # pylint: enable=import-error import qubespolicy.rpcconfirmation +import qubespolicy.policycreateconfirmation # pylint: enable=wrong-import-position class PolicyAgent(object): @@ -45,6 +46,11 @@ class PolicyAgent(object): + + + + + """ @@ -63,6 +69,13 @@ class PolicyAgent(object): targets, default_target or None) return response or '' + @staticmethod + def ConfirmPolicyCreate(source, service_name): + # pylint: disable=invalid-name + + response = qubespolicy.policycreateconfirmation.confirm( + source, service_name) + return response def main(): loop = GLib.MainLoop() diff --git a/qubespolicy/cli.py b/qubespolicy/cli.py index 9fcd4c15..c6f5fc16 100644 --- a/qubespolicy/cli.py +++ b/qubespolicy/cli.py @@ -20,6 +20,7 @@ import argparse import logging import logging.handlers +import os import sys @@ -46,6 +47,20 @@ parser.add_argument('process_ident', metavar='process-ident', help='Qrexec process identifier - for connecting data channel') +def create_default_policy(service_name): + policy_file = os.path.join(qubespolicy.POLICY_DIR, service_name) + with open(policy_file, "w") as policy: + policy.write( + "## Policy file automatically created on first service call.\n") + policy.write( + "## Fill free to edit.\n") + policy.write("## Note that policy parsing stops at the first match\n") + policy.write("\n") + policy.write("## Please use a single # to start your custom comments\n") + policy.write("\n") + policy.write("$anyvm $anyvm ask\n") + + def main(args=None): args = parser.parse_args(args) @@ -64,7 +79,22 @@ def main(args=None): log.error(log_prefix + 'error getting system info: ' + str(e)) return 1 try: - policy = qubespolicy.Policy(args.service_name) + try: + policy = qubespolicy.Policy(args.service_name) + except qubespolicy.PolicyNotFound: + service_name = args.service_name.split('+')[0] + import pydbus + bus = pydbus.SystemBus() + proxy = bus.get('org.qubesos.PolicyAgent', + '/org/qubesos/PolicyAgent') + create_policy = proxy.ConfirmPolicyCreate( + args.domain, service_name) + if create_policy: + create_default_policy(service_name) + policy = qubespolicy.Policy(args.service_name) + else: + raise + action = policy.evaluate(system_info, args.domain, args.target) if args.assume_yes_for_ask and action.action == qubespolicy.Action.ask: action.action = qubespolicy.Action.allow diff --git a/qubespolicy/glade/PolicyCreateConfirmationWindow.glade b/qubespolicy/glade/PolicyCreateConfirmationWindow.glade new file mode 100644 index 00000000..14e1994f --- /dev/null +++ b/qubespolicy/glade/PolicyCreateConfirmationWindow.glade @@ -0,0 +1,141 @@ + + + + + + False + Default service policy + dialog-warning + dialog + question + ok-cancel + + + False + vertical + + + False + end + + + False + True + 0 + + + + + True + False + vertical + 2 + + + True + False + Policy for requested service does not exist. +Do you want to create default one (ask for everything)? + + + False + True + 2 + 0 + + + + + True + False + 2 + 2 + + + True + False + 4 + Source: + + + 0 + 0 + + + + + True + False + 4 + Service: + + + 0 + 1 + + + + + True + False + True + False + + + 1 + 0 + + + + + True + False + True + False + + + 1 + 1 + + + + + True + False + Type capital YES to confirm: + + + 0 + 2 + + + + + True + True + True + False + + + 1 + 2 + + + + + False + False + 1 + + + + + False + True + 1 + + + + + + diff --git a/qubespolicy/graph.py b/qubespolicy/graph.py index eff64bcb..159de772 100644 --- a/qubespolicy/graph.py +++ b/qubespolicy/graph.py @@ -52,17 +52,19 @@ def handle_single_action(args, action): service = '' else: service = action.service + target = action.target or action.original_target + # handle forced target= + if action.rule.override_target: + target = action.rule.override_target + if args.target and target not in args.target: + return '' if action.action == qubespolicy.Action.ask: if args.include_ask: - # handle forced target= - if len(action.targets_for_ask) == 1: - return ' "{}" -> "{}" [label="{}" color=orange];\n'.format( - action.source, action.targets_for_ask[0], service) return ' "{}" -> "{}" [label="{}" color=orange];\n'.format( - action.source, action.original_target, service) + action.source, target, service) elif action.action == qubespolicy.Action.allow: return ' "{}" -> "{}" [label="{}" color=red];\n'.format( - action.source, action.target, service) + action.source, target, service) return '' def main(args=None): @@ -83,12 +85,9 @@ def main(args=None): sources = args.source targets = list(system_info['domains'].keys()) - if args.target: - targets = args.target - else: - targets.append('$dispvm') - targets.extend('$dispvm:' + dom for dom in system_info['domains'] - if system_info['domains'][dom]['dispvm_allowed']) + targets.append('$dispvm') + targets.extend('$dispvm:' + dom for dom in system_info['domains'] + if system_info['domains'][dom]['dispvm_allowed']) connections = set() diff --git a/qubespolicy/policycreateconfirmation.py b/qubespolicy/policycreateconfirmation.py new file mode 100644 index 00000000..85c633dd --- /dev/null +++ b/qubespolicy/policycreateconfirmation.py @@ -0,0 +1,82 @@ +# -*- encoding: utf-8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2017 Marek Marczykowski-Górecki +# +# +# 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 . + +import os + +import pkg_resources + +# pylint: disable=import-error,wrong-import-position +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +# pylint: enable=import-error + +class PolicyCreateConfirmationWindow(object): + # pylint: disable=too-few-public-methods + _source_file = pkg_resources.resource_filename('qubespolicy', + os.path.join('glade', "PolicyCreateConfirmationWindow.glade")) + _source_id = {'window': "PolicyCreateConfirmationWindow", + 'ok': "okButton", + 'cancel': "cancelButton", + 'source': "sourceEntry", + 'service': "serviceEntry", + 'confirm': "confirmEntry", + } + + def __init__(self, source, service): + self._gtk_builder = Gtk.Builder() + self._gtk_builder.add_from_file(self._source_file) + self._window = self._gtk_builder.get_object( + self._source_id['window']) + self._rpc_ok_button = self._gtk_builder.get_object( + self._source_id['ok']) + self._rpc_cancel_button = self._gtk_builder.get_object( + self._source_id['cancel']) + self._service_entry = self._gtk_builder.get_object( + self._source_id['service']) + self._source_entry = self._gtk_builder.get_object( + self._source_id['source']) + self._confirm_entry = self._gtk_builder.get_object( + self._source_id['confirm']) + + self._source_entry.set_text(source) + self._service_entry.set_text(service) + + # make OK button the default + ok_button = self._window.get_widget_for_response(Gtk.ResponseType.OK) + ok_button.set_can_default(True) + ok_button.grab_default() + + def run(self): + self._window.set_keep_above(True) + self._window.connect("delete-event", Gtk.main_quit) + self._window.show_all() + + response = self._window.run() + + self._window.hide() + if response == Gtk.ResponseType.OK: + return self._confirm_entry.get_text() == 'YES' + return False + +def confirm(source, service): + window = PolicyCreateConfirmationWindow(source, service) + + return window.run() diff --git a/qubespolicy/tests/cli.py b/qubespolicy/tests/cli.py new file mode 100644 index 00000000..a7387939 --- /dev/null +++ b/qubespolicy/tests/cli.py @@ -0,0 +1,343 @@ +# -*- encoding: utf-8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2017 Marek Marczykowski-Górecki +# +# +# 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 . +import os +import tempfile +import unittest.mock + +import shutil + +import qubes.tests +import qubespolicy +import qubespolicy.cli +import qubespolicy.tests + + + +class TC_00_qrexec_policy(qubes.tests.QubesTestCase): + def setUp(self): + super(TC_00_qrexec_policy, self).setUp() + self.policy_patch = unittest.mock.patch('qubespolicy.Policy') + self.policy_mock = self.policy_patch.start() + + self.system_info_patch = unittest.mock.patch( + 'qubespolicy.get_system_info') + self.system_info_mock = self.system_info_patch.start() + + self.system_info = { + 'domains': {'dom0': {'icon': 'black', 'dispvm_allowed': False}, + 'test-vm1': {'icon': 'red', 'dispvm_allowed': False}, + 'test-vm2': {'icon': 'red', 'dispvm_allowed': False}, + 'test-vm3': {'icon': 'green', 'dispvm_allowed': True}, }} + self.system_info_mock.return_value = self.system_info + + self.dbus_patch = unittest.mock.patch('pydbus.SystemBus') + self.dbus_mock = self.dbus_patch.start() + + self.policy_dir = tempfile.TemporaryDirectory() + self.policydir_patch = unittest.mock.patch('qubespolicy.POLICY_DIR', + self.policy_dir.name) + self.policydir_patch.start() + + def tearDown(self): + self.policydir_patch.stop() + self.policy_dir.cleanup() + self.dbus_patch.start() + self.system_info_patch.stop() + self.policy_patch.stop() + super(TC_00_qrexec_policy, self).tearDown() + + def test_000_allow(self): + self.policy_mock.configure_mock(**{ + 'return_value.evaluate.return_value.action': + qubespolicy.Action.allow, + }) + retval = qubespolicy.cli.main( + ['source-id', 'source', 'target', 'service', 'process_ident']) + self.assertEqual(retval, 0) + self.assertEqual(self.policy_mock.mock_calls, [ + ('', ('service',), {}), + ('().evaluate', (self.system_info, 'source', + 'target'), {}), + ('().evaluate().target.__str__', (), {}), + ('().evaluate().execute', ('process_ident,source,source-id', ), {}), + ]) + self.assertEqual(self.dbus_mock.mock_calls, []) + + def test_010_ask_allow(self): + self.policy_mock.configure_mock(**{ + 'return_value.evaluate.return_value.action': + qubespolicy.Action.ask, + 'return_value.evaluate.return_value.target': + None, + 'return_value.evaluate.return_value.targets_for_ask': + ['test-vm1', 'test-vm2'], + }) + self.dbus_mock.configure_mock(**{ + 'return_value.get.return_value.Ask.return_value': 'test-vm1' + }) + retval = qubespolicy.cli.main( + ['source-id', 'source', 'target', 'service', 'process_ident']) + self.assertEqual(retval, 0) + self.assertEqual(self.policy_mock.mock_calls, [ + ('', ('service',), {}), + ('().evaluate', (self.system_info, 'source', + 'target'), {}), + ('().evaluate().handle_user_response', (True, 'test-vm1'), {}), + ('().evaluate().execute', ('process_ident,source,source-id', ), {}), + ]) + icons = { + 'dom0': 'black', + 'test-vm1': 'red', + 'test-vm2': 'red', + 'test-vm3': 'green', + '$dispvm:test-vm3': 'green', + } + self.assertEqual(self.dbus_mock.mock_calls, [ + ('', (), {}), + ('().get', ('org.qubesos.PolicyAgent', + '/org/qubesos/PolicyAgent'), {}), + ('().get().Ask', ('source', 'service', ['test-vm1', 'test-vm2'], + '', icons), {}), + ]) + + def test_011_ask_deny(self): + self.policy_mock.configure_mock(**{ + 'return_value.evaluate.return_value.action': + qubespolicy.Action.ask, + 'return_value.evaluate.return_value.target': + None, + 'return_value.evaluate.return_value.targets_for_ask': + ['test-vm1', 'test-vm2'], + 'return_value.evaluate.return_value.handle_user_response' + '.side_effect': + qubespolicy.AccessDenied, + }) + self.dbus_mock.configure_mock(**{ + 'return_value.get.return_value.Ask.return_value': '' + }) + retval = qubespolicy.cli.main( + ['source-id', 'source', 'target', 'service', 'process_ident']) + self.assertEqual(retval, 1) + self.assertEqual(self.policy_mock.mock_calls, [ + ('', ('service',), {}), + ('().evaluate', (self.system_info, 'source', + 'target'), {}), + ('().evaluate().handle_user_response', (False,), {}), + ]) + icons = { + 'dom0': 'black', + 'test-vm1': 'red', + 'test-vm2': 'red', + 'test-vm3': 'green', + '$dispvm:test-vm3': 'green', + } + self.assertEqual(self.dbus_mock.mock_calls, [ + ('', (), {}), + ('().get', ('org.qubesos.PolicyAgent', + '/org/qubesos/PolicyAgent'), {}), + ('().get().Ask', ('source', 'service', ['test-vm1', 'test-vm2'], + '', icons), {}), + ]) + + def test_012_ask_default_target(self): + self.policy_mock.configure_mock(**{ + 'return_value.evaluate.return_value.action': + qubespolicy.Action.ask, + 'return_value.evaluate.return_value.target': + 'test-vm1', + 'return_value.evaluate.return_value.targets_for_ask': + ['test-vm1', 'test-vm2'], + }) + self.dbus_mock.configure_mock(**{ + 'return_value.get.return_value.Ask.return_value': 'test-vm1' + }) + retval = qubespolicy.cli.main( + ['source-id', 'source', 'target', 'service', 'process_ident']) + self.assertEqual(retval, 0) + self.assertEqual(self.policy_mock.mock_calls, [ + ('', ('service',), {}), + ('().evaluate', (self.system_info, 'source', + 'target'), {}), + ('().evaluate().handle_user_response', (True, 'test-vm1'), {}), + ('().evaluate().execute', ('process_ident,source,source-id',), {}), + ]) + icons = { + 'dom0': 'black', + 'test-vm1': 'red', + 'test-vm2': 'red', + 'test-vm3': 'green', + '$dispvm:test-vm3': 'green', + } + self.assertEqual(self.dbus_mock.mock_calls, [ + ('', (), {}), + ('().get', ('org.qubesos.PolicyAgent', + '/org/qubesos/PolicyAgent'), {}), + ('().get().Ask', ('source', 'service', ['test-vm1', 'test-vm2'], + 'test-vm1', icons), {}), + ]) + + def test_020_deny(self): + self.policy_mock.configure_mock(**{ + 'return_value.evaluate.return_value.action': + qubespolicy.Action.deny, + 'return_value.evaluate.return_value.execute.side_effect': + qubespolicy.AccessDenied, + }) + retval = qubespolicy.cli.main( + ['source-id', 'source', 'target', 'service', 'process_ident']) + self.assertEqual(retval, 1) + self.assertEqual(self.policy_mock.mock_calls, [ + ('', ('service',), {}), + ('().evaluate', (self.system_info, 'source', + 'target'), {}), + ('().evaluate().target.__str__', (), {}), + ('().evaluate().execute', ('process_ident,source,source-id',), {}), + ]) + self.assertEqual(self.dbus_mock.mock_calls, []) + + def test_030_just_evaluate_allow(self): + self.policy_mock.configure_mock(**{ + 'return_value.evaluate.return_value.action': + qubespolicy.Action.allow, + }) + retval = qubespolicy.cli.main( + ['--just-evaluate', + 'source-id', 'source', 'target', 'service', 'process_ident']) + self.assertEqual(retval, 0) + self.assertEqual(self.policy_mock.mock_calls, [ + ('', ('service',), {}), + ('().evaluate', (self.system_info, 'source', + 'target'), {}), + ]) + self.assertEqual(self.dbus_mock.mock_calls, []) + + def test_031_just_evaluate_deny(self): + self.policy_mock.configure_mock(**{ + 'return_value.evaluate.return_value.action': + qubespolicy.Action.deny, + }) + retval = qubespolicy.cli.main( + ['--just-evaluate', + 'source-id', 'source', 'target', 'service', 'process_ident']) + self.assertEqual(retval, 1) + self.assertEqual(self.policy_mock.mock_calls, [ + ('', ('service',), {}), + ('().evaluate', (self.system_info, 'source', + 'target'), {}), + ]) + self.assertEqual(self.dbus_mock.mock_calls, []) + + def test_032_just_evaluate_ask(self): + self.policy_mock.configure_mock(**{ + 'return_value.evaluate.return_value.action': + qubespolicy.Action.ask, + }) + retval = qubespolicy.cli.main( + ['--just-evaluate', + 'source-id', 'source', 'target', 'service', 'process_ident']) + self.assertEqual(retval, 1) + self.assertEqual(self.policy_mock.mock_calls, [ + ('', ('service',), {}), + ('().evaluate', (self.system_info, 'source', + 'target'), {}), + ]) + self.assertEqual(self.dbus_mock.mock_calls, []) + + def test_033_just_evaluate_ask_assume_yes(self): + self.policy_mock.configure_mock(**{ + 'return_value.evaluate.return_value.action': + qubespolicy.Action.ask, + }) + retval = qubespolicy.cli.main( + ['--just-evaluate', '--assume-yes-for-ask', + 'source-id', 'source', 'target', 'service', 'process_ident']) + self.assertEqual(retval, 0) + self.assertEqual(self.policy_mock.mock_calls, [ + ('', ('service',), {}), + ('().evaluate', (self.system_info, 'source', + 'target'), {}), + ]) + self.assertEqual(self.dbus_mock.mock_calls, []) + + def test_040_create_policy(self): + self.policy_mock.configure_mock(**{ + 'side_effect': + [qubespolicy.PolicyNotFound('service'), unittest.mock.DEFAULT], + 'return_value.evaluate.return_value.action': + qubespolicy.Action.allow, + }) + self.dbus_mock.configure_mock(**{ + 'return_value.get.return_value.ConfirmPolicyCreate.return_value': + True + }) + retval = qubespolicy.cli.main( + ['source-id', 'source', 'target', 'service', 'process_ident']) + self.assertEqual(retval, 0) + self.assertEqual(self.policy_mock.mock_calls, [ + ('', ('service',), {}), + ('', ('service',), {}), + ('().evaluate', (self.system_info, 'source', + 'target'), {}), + ('().evaluate().target.__str__', (), {}), + ('().evaluate().execute', ('process_ident,source,source-id',), {}), + ]) + self.assertEqual(self.dbus_mock.mock_calls, [ + ('', (), {}), + ('().get', ('org.qubesos.PolicyAgent', + '/org/qubesos/PolicyAgent'), {}), + ('().get().ConfirmPolicyCreate', ('source', 'service'), {}), + ]) + policy_path = os.path.join(self.policy_dir.name, 'service') + self.assertTrue(os.path.exists(policy_path)) + with open(policy_path) as policy_file: + self.assertEqual(policy_file.read(), + "## Policy file automatically created on first service call.\n" + "## Fill free to edit.\n" + "## Note that policy parsing stops at the first match\n" + "\n" + "## Please use a single # to start your custom comments\n" + "\n" + "$anyvm $anyvm ask\n") + + def test_041_create_policy_abort(self): + self.policy_mock.configure_mock(**{ + 'side_effect': + [qubespolicy.PolicyNotFound('service'), unittest.mock.DEFAULT], + 'return_value.evaluate.return_value.action': + qubespolicy.Action.deny, + }) + self.dbus_mock.configure_mock(**{ + 'return_value.get.return_value.ConfirmPolicyCreate.return_value': + False + }) + retval = qubespolicy.cli.main( + ['source-id', 'source', 'target', 'service', 'process_ident']) + self.assertEqual(retval, 1) + self.assertEqual(self.policy_mock.mock_calls, [ + ('', ('service',), {}), + ]) + self.assertEqual(self.dbus_mock.mock_calls, [ + ('', (), {}), + ('().get', ('org.qubesos.PolicyAgent', + '/org/qubesos/PolicyAgent'), {}), + ('().get().ConfirmPolicyCreate', ('source', 'service'), {}), + ]) + policy_path = os.path.join(self.policy_dir.name, 'service') + self.assertFalse(os.path.exists(policy_path)) diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index 33929150..6d586460 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -374,6 +374,7 @@ fi %{python3_sitelib}/qubespolicy/cli.py %{python3_sitelib}/qubespolicy/agent.py %{python3_sitelib}/qubespolicy/gtkhelpers.py +%{python3_sitelib}/qubespolicy/policycreateconfirmation.py %{python3_sitelib}/qubespolicy/rpcconfirmation.py %{python3_sitelib}/qubespolicy/utils.py %{python3_sitelib}/qubespolicy/graph.py @@ -382,10 +383,12 @@ fi %dir %{python3_sitelib}/qubespolicy/tests/__pycache__ %{python3_sitelib}/qubespolicy/tests/__pycache__/* %{python3_sitelib}/qubespolicy/tests/__init__.py +%{python3_sitelib}/qubespolicy/tests/cli.py %{python3_sitelib}/qubespolicy/tests/gtkhelpers.py %{python3_sitelib}/qubespolicy/tests/rpcconfirmation.py %dir %{python3_sitelib}/qubespolicy/glade +%{python3_sitelib}/qubespolicy/glade/PolicyCreateConfirmationWindow.glade %{python3_sitelib}/qubespolicy/glade/RPCConfirmationWindow.glade /usr/lib/qubes/cleanup-dispvms @@ -414,6 +417,7 @@ fi /etc/xen/scripts/block-snapshot /etc/xen/scripts/block-origin /etc/xen/scripts/vif-route-qubes +%attr(2775,root,qubes) %dir /etc/qubes-rpc/policy %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/admin.* %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/include/admin-local-ro %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/include/admin-local-rwx diff --git a/setup.py b/setup.py index 0f1f40e1..6f4b0ca5 100644 --- a/setup.py +++ b/setup.py @@ -51,6 +51,7 @@ if __name__ == '__main__': 'qubes.ext.r3compatibility = qubes.ext.r3compatibility:R3Compatibility', 'qubes.ext.pci = qubes.ext.pci:PCIDeviceExtension', 'qubes.ext.block = qubes.ext.block:BlockDeviceExtension', + 'qubes.ext.services = qubes.ext.services:ServicesExtension', ], 'qubes.devices': [ 'pci = qubes.ext.pci:PCIDevice', diff --git a/test-packages/pydbus.py b/test-packages/pydbus.py new file mode 100644 index 00000000..00dd359b --- /dev/null +++ b/test-packages/pydbus.py @@ -0,0 +1,2 @@ +class SystemBus(object): + pass