68b6f1ec76
Using '$' is easy to misuse in shell scripts, shell commands etc. After all this years, lets abandon this dangerous character and move to something safer: '@'. The choice was made after reviewing specifications of various shells on different operating systems and this is the character that have no special meaning in none of them. To preserve compatibility, automatically translate '$' to '@' when loading policy files.
335 lines
12 KiB
Python
Executable File
335 lines
12 KiB
Python
Executable File
#!/usr/bin/python
|
|
#
|
|
# The Qubes OS Project, https://www.qubes-os.org/
|
|
#
|
|
# Copyright (C) 2017 boring-stuff <boring-stuff@users.noreply.github.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/>.
|
|
#
|
|
|
|
import sys
|
|
import unittest
|
|
|
|
from qubespolicy.tests.gtkhelpers import GtkTestCase, FocusStealingHelperMock
|
|
from qubespolicy.tests.gtkhelpers import mock_domains_info, mock_whitelist
|
|
|
|
from qubespolicy.gtkhelpers import VMListModeler
|
|
from qubespolicy.rpcconfirmation import RPCConfirmationWindow
|
|
|
|
|
|
class MockRPCConfirmationWindow(RPCConfirmationWindow):
|
|
def _new_vm_list_modeler(self):
|
|
return VMListModeler(mock_domains_info)
|
|
|
|
def _new_focus_stealing_helper(self):
|
|
return FocusStealingHelperMock(
|
|
self._rpc_window,
|
|
self._rpc_ok_button,
|
|
self._focus_stealing_seconds)
|
|
|
|
def __init__(self, source, rpc_operation, whitelist,
|
|
target=None, focus_stealing_seconds=1):
|
|
self._focus_stealing_seconds = focus_stealing_seconds
|
|
|
|
RPCConfirmationWindow.__init__(
|
|
self, mock_domains_info, source, rpc_operation, whitelist,
|
|
target)
|
|
|
|
def is_error_visible(self):
|
|
return self._error_bar.get_visible()
|
|
|
|
def get_shown_domains(self):
|
|
model = self._rpc_combo_box.get_model()
|
|
model_iter = model.get_iter_first()
|
|
domains = []
|
|
|
|
while model_iter is not None:
|
|
domain_name = model.get_value(model_iter, 1)
|
|
|
|
domains += [domain_name]
|
|
|
|
model_iter = model.iter_next(model_iter)
|
|
|
|
return domains
|
|
|
|
|
|
class RPCConfirmationWindowTestBase(MockRPCConfirmationWindow, GtkTestCase):
|
|
def __init__(self, test_method, source_name="test-source",
|
|
rpc_operation="test.Operation", whitelist=mock_whitelist,
|
|
target_name=None):
|
|
GtkTestCase.__init__(self, test_method)
|
|
self.test_source_name = source_name
|
|
self.test_rpc_operation = rpc_operation
|
|
self.test_target_name = target_name
|
|
|
|
self._test_time = 0.1
|
|
|
|
self.test_called_close = False
|
|
self.test_called_show = False
|
|
|
|
self.test_clicked_ok = False
|
|
self.test_clicked_cancel = False
|
|
|
|
MockRPCConfirmationWindow.__init__(self,
|
|
self.test_source_name,
|
|
self.test_rpc_operation,
|
|
whitelist,
|
|
self.test_target_name,
|
|
focus_stealing_seconds=self._test_time)
|
|
|
|
def _can_perform_action(self):
|
|
return True
|
|
|
|
def _close(self):
|
|
self.test_called_close = True
|
|
|
|
def _show(self):
|
|
self.test_called_show = True
|
|
|
|
def _clicked_ok(self, button):
|
|
MockRPCConfirmationWindow._clicked_ok(self, button)
|
|
self.test_clicked_ok = True
|
|
|
|
def _clicked_cancel(self, button):
|
|
MockRPCConfirmationWindow._clicked_cancel(self, button)
|
|
self.test_clicked_cancel = True
|
|
|
|
def test_has_linked_the_fields(self):
|
|
self.assertIsNotNone(self._rpc_window)
|
|
self.assertIsNotNone(self._rpc_ok_button)
|
|
self.assertIsNotNone(self._rpc_cancel_button)
|
|
self.assertIsNotNone(self._rpc_label)
|
|
self.assertIsNotNone(self._source_entry)
|
|
self.assertIsNotNone(self._rpc_combo_box)
|
|
self.assertIsNotNone(self._error_bar)
|
|
self.assertIsNotNone(self._error_message)
|
|
|
|
def test_is_showing_source(self):
|
|
self.assertTrue(self.test_source_name in self._source_entry.get_text())
|
|
|
|
def test_is_showing_operation(self):
|
|
self.assertTrue(self.test_rpc_operation in self._rpc_label.get_text())
|
|
|
|
def test_escape_and_format_rpc_text(self):
|
|
self.assertEquals("qubes.<b>Test</b>",
|
|
self._escape_and_format_rpc_text("qubes.Test"))
|
|
self.assertEquals("custom.<b>Domain</b>",
|
|
self._escape_and_format_rpc_text("custom.Domain"))
|
|
self.assertEquals("<b>nodomain</b>",
|
|
self._escape_and_format_rpc_text("nodomain"))
|
|
self.assertEquals("domain.<b>Sub.Operation</b>",
|
|
self._escape_and_format_rpc_text("domain.Sub.Operation"))
|
|
self.assertEquals("<b></b>",
|
|
self._escape_and_format_rpc_text(""))
|
|
self.assertEquals("<b>.</b>",
|
|
self._escape_and_format_rpc_text("."))
|
|
self.assertEquals("inject.<b><script></b>",
|
|
self._escape_and_format_rpc_text("inject.<script>"))
|
|
self.assertEquals("<script>.<b>inject</b>",
|
|
self._escape_and_format_rpc_text("<script>.inject"))
|
|
|
|
def test_lifecycle_open_select_ok(self):
|
|
self._lifecycle_start(select_target=True)
|
|
self._lifecycle_click(click_type="ok")
|
|
|
|
def test_lifecycle_open_select_cancel(self):
|
|
self._lifecycle_start(select_target=True)
|
|
self._lifecycle_click(click_type="cancel")
|
|
|
|
def test_lifecycle_open_select_exit(self):
|
|
self._lifecycle_start(select_target=True)
|
|
self._lifecycle_click(click_type="exit")
|
|
|
|
def test_lifecycle_open_cancel(self):
|
|
self._lifecycle_start(select_target=False)
|
|
self._lifecycle_click(click_type="cancel")
|
|
|
|
def test_lifecycle_open_exit(self):
|
|
self._lifecycle_start(select_target=False)
|
|
self._lifecycle_click(click_type="exit")
|
|
|
|
def _lifecycle_click(self, click_type):
|
|
if click_type == "ok":
|
|
self._rpc_ok_button.clicked()
|
|
|
|
self.assertTrue(self.test_clicked_ok)
|
|
self.assertFalse(self.test_clicked_cancel)
|
|
self.assertTrue(self._confirmed)
|
|
self.assertIsNotNone(self._target_name)
|
|
elif click_type == "cancel":
|
|
self._rpc_cancel_button.clicked()
|
|
|
|
self.assertFalse(self.test_clicked_ok)
|
|
self.assertTrue(self.test_clicked_cancel)
|
|
self.assertFalse(self._confirmed)
|
|
elif click_type == "exit":
|
|
self._close()
|
|
|
|
self.assertFalse(self.test_clicked_ok)
|
|
self.assertFalse(self.test_clicked_cancel)
|
|
self.assertIsNone(self._confirmed)
|
|
|
|
self.assertTrue(self.test_called_close)
|
|
|
|
|
|
def _lifecycle_start(self, select_target):
|
|
self.assertFalse(self.test_called_close)
|
|
self.assertFalse(self.test_called_show)
|
|
|
|
self.assert_initial_state(False)
|
|
self.assertTrue(isinstance(self._focus_helper, FocusStealingHelperMock))
|
|
|
|
# Need the following because of pylint's complaints
|
|
if isinstance(self._focus_helper, FocusStealingHelperMock):
|
|
FocusStealingHelperMock.simulate_focus(self._focus_helper)
|
|
|
|
self.flush_gtk_events(self._test_time*2)
|
|
self.assert_initial_state(True)
|
|
|
|
try:
|
|
# We expect the call to exit immediately, since no window is opened
|
|
self.confirm_rpc()
|
|
except Exception:
|
|
pass
|
|
|
|
self.assertFalse(self.test_called_close)
|
|
self.assertTrue(self.test_called_show)
|
|
|
|
self.assert_initial_state(True)
|
|
|
|
if select_target:
|
|
self._rpc_combo_box.set_active(1)
|
|
|
|
self.assertTrue(self._rpc_ok_button.get_sensitive())
|
|
|
|
self.assertIsNotNone(self._target_name)
|
|
|
|
self.assertFalse(self.test_called_close)
|
|
self.assertTrue(self.test_called_show)
|
|
self.assertFalse(self.test_clicked_ok)
|
|
self.assertFalse(self.test_clicked_cancel)
|
|
self.assertFalse(self._confirmed)
|
|
|
|
def assert_initial_state(self, after_focus_timer):
|
|
self.assertIsNone(self._target_name)
|
|
self.assertFalse(self.test_clicked_ok)
|
|
self.assertFalse(self.test_clicked_cancel)
|
|
self.assertFalse(self._confirmed)
|
|
self.assertFalse(self._rpc_ok_button.get_sensitive())
|
|
self.assertFalse(self._error_bar.get_visible())
|
|
|
|
if after_focus_timer:
|
|
self.assertTrue(self._focus_helper.can_perform_action())
|
|
else:
|
|
self.assertFalse(self._focus_helper.can_perform_action())
|
|
|
|
|
|
class RPCConfirmationWindowTestWithTarget(RPCConfirmationWindowTestBase):
|
|
def __init__(self, test_method):
|
|
RPCConfirmationWindowTestBase.__init__(self, test_method,
|
|
source_name="test-source", rpc_operation="test.Operation",
|
|
target_name="test-target")
|
|
|
|
def test_lifecycle_open_ok(self):
|
|
self._lifecycle_start(select_target=False)
|
|
self._lifecycle_click(click_type="ok")
|
|
|
|
def assert_initial_state(self, after_focus_timer):
|
|
self.assertIsNotNone(self._target_name)
|
|
self.assertFalse(self.test_clicked_ok)
|
|
self.assertFalse(self.test_clicked_cancel)
|
|
self.assertFalse(self._confirmed)
|
|
if after_focus_timer:
|
|
self.assertTrue(self._rpc_ok_button.get_sensitive())
|
|
self.assertTrue(self._focus_helper.can_perform_action())
|
|
else:
|
|
self.assertFalse(self._rpc_ok_button.get_sensitive())
|
|
self.assertFalse(self._focus_helper.can_perform_action())
|
|
|
|
def _lifecycle_click(self, click_type):
|
|
RPCConfirmationWindowTestBase._lifecycle_click(self, click_type)
|
|
self.assertIsNotNone(self._target_name)
|
|
|
|
|
|
class RPCConfirmationWindowTestWithTargetInvalid(unittest.TestCase):
|
|
def __init__(self, *args, **kwargs):
|
|
unittest.TestCase.__init__(self, *args, **kwargs)
|
|
|
|
def test_unknown(self):
|
|
self.assert_raises_error(True, "test-source", "test-wrong-target")
|
|
|
|
def test_empty(self):
|
|
self.assert_raises_error(True, "test-source", "")
|
|
|
|
def test_equals_source(self):
|
|
self.assert_raises_error(True, "test-source", "test-source")
|
|
|
|
def assert_raises_error(self, expect, source, target):
|
|
rpcWindow = MockRPCConfirmationWindow(source, "test.Operation",
|
|
mock_whitelist, target=target)
|
|
self.assertEquals(expect, rpcWindow.is_error_visible())
|
|
|
|
|
|
class RPCConfirmationWindowTestWhitelist(unittest.TestCase):
|
|
def __init__(self, *args, **kwargs):
|
|
unittest.TestCase.__init__(self, *args, **kwargs)
|
|
|
|
def test_no_domains(self):
|
|
self._assert_whitelist([], [])
|
|
|
|
def test_all_red_domains(self):
|
|
self._assert_whitelist(["test-red1", "test-red2", "test-red3"],
|
|
["test-red1", "test-red2", "test-red3"])
|
|
|
|
def test_all_red_domains_plus_nonexistent(self):
|
|
self._assert_whitelist(
|
|
["test-red1", "test-red2", "test-red3",
|
|
"test-blue1", "test-blue2", "test-blue3"],
|
|
["test-red1", "test-red2", "test-red3"])
|
|
|
|
def test_all_allowed_domains(self):
|
|
self._assert_whitelist(
|
|
["test-red1", "test-red2", "test-red3",
|
|
"test-target", "@dispvm:test-disp6", "test-source", "dom0"],
|
|
["test-red1", "test-red2", "test-red3",
|
|
"test-target", "Disposable VM (test-disp6)", "test-source",
|
|
"dom0"])
|
|
|
|
def _assert_whitelist(self, whitelist, expected):
|
|
rpcWindow = MockRPCConfirmationWindow(
|
|
"test-source", "test.Operation", whitelist)
|
|
|
|
domains = rpcWindow.get_shown_domains()
|
|
|
|
self.assertCountEqual(domains, expected)
|
|
|
|
if __name__ == '__main__':
|
|
test = False
|
|
window = False
|
|
|
|
if len(sys.argv) == 1 or sys.argv[1] == '-t':
|
|
test = True
|
|
elif sys.argv[1] == '-w':
|
|
window = True
|
|
else:
|
|
print("Usage: " + __file__ + " [-t|-w]")
|
|
|
|
if window:
|
|
print(MockRPCConfirmationWindow("test-source",
|
|
"qubes.Filecopy",
|
|
mock_whitelist,
|
|
"test-red1").confirm_rpc())
|
|
elif test:
|
|
unittest.main(argv=[sys.argv[0]])
|