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 @@
+
+
+
+
+
+
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