rpcconfirmation.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. #!/usr/bin/python
  2. #
  3. # The Qubes OS Project, https://www.qubes-os.org/
  4. #
  5. # Copyright (C) 2017 boring-stuff <boring-stuff@users.noreply.github.com>
  6. #
  7. # This library is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU Lesser General Public
  9. # License as published by the Free Software Foundation; either
  10. # version 2.1 of the License, or (at your option) any later version.
  11. #
  12. # This library is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. # Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public
  18. # License along with this library; if not, see <https://www.gnu.org/licenses/>.
  19. #
  20. import sys
  21. import unittest
  22. from qubespolicy.tests.gtkhelpers import GtkTestCase, FocusStealingHelperMock
  23. from qubespolicy.tests.gtkhelpers import mock_domains_info, mock_whitelist
  24. from qubespolicy.gtkhelpers import VMListModeler
  25. from qubespolicy.rpcconfirmation import RPCConfirmationWindow
  26. class MockRPCConfirmationWindow(RPCConfirmationWindow):
  27. def _new_vm_list_modeler(self):
  28. return VMListModeler(mock_domains_info)
  29. def _new_focus_stealing_helper(self):
  30. return FocusStealingHelperMock(
  31. self._rpc_window,
  32. self._rpc_ok_button,
  33. self._focus_stealing_seconds)
  34. def __init__(self, source, rpc_operation, whitelist,
  35. target=None, focus_stealing_seconds=1):
  36. self._focus_stealing_seconds = focus_stealing_seconds
  37. RPCConfirmationWindow.__init__(
  38. self, mock_domains_info, source, rpc_operation, whitelist,
  39. target)
  40. def is_error_visible(self):
  41. return self._error_bar.get_visible()
  42. def get_shown_domains(self):
  43. model = self._rpc_combo_box.get_model()
  44. model_iter = model.get_iter_first()
  45. domains = []
  46. while model_iter is not None:
  47. domain_name = model.get_value(model_iter, 1)
  48. domains += [domain_name]
  49. model_iter = model.iter_next(model_iter)
  50. return domains
  51. class RPCConfirmationWindowTestBase(MockRPCConfirmationWindow, GtkTestCase):
  52. def __init__(self, test_method, source_name="test-source",
  53. rpc_operation="test.Operation", whitelist=mock_whitelist,
  54. target_name=None):
  55. GtkTestCase.__init__(self, test_method)
  56. self.test_source_name = source_name
  57. self.test_rpc_operation = rpc_operation
  58. self.test_target_name = target_name
  59. self._test_time = 0.1
  60. self.test_called_close = False
  61. self.test_called_show = False
  62. self.test_clicked_ok = False
  63. self.test_clicked_cancel = False
  64. MockRPCConfirmationWindow.__init__(self,
  65. self.test_source_name,
  66. self.test_rpc_operation,
  67. whitelist,
  68. self.test_target_name,
  69. focus_stealing_seconds=self._test_time)
  70. def _can_perform_action(self):
  71. return True
  72. def _close(self):
  73. self.test_called_close = True
  74. def _show(self):
  75. self.test_called_show = True
  76. def _clicked_ok(self, button):
  77. MockRPCConfirmationWindow._clicked_ok(self, button)
  78. self.test_clicked_ok = True
  79. def _clicked_cancel(self, button):
  80. MockRPCConfirmationWindow._clicked_cancel(self, button)
  81. self.test_clicked_cancel = True
  82. def test_has_linked_the_fields(self):
  83. self.assertIsNotNone(self._rpc_window)
  84. self.assertIsNotNone(self._rpc_ok_button)
  85. self.assertIsNotNone(self._rpc_cancel_button)
  86. self.assertIsNotNone(self._rpc_label)
  87. self.assertIsNotNone(self._source_entry)
  88. self.assertIsNotNone(self._rpc_combo_box)
  89. self.assertIsNotNone(self._error_bar)
  90. self.assertIsNotNone(self._error_message)
  91. def test_is_showing_source(self):
  92. self.assertTrue(self.test_source_name in self._source_entry.get_text())
  93. def test_is_showing_operation(self):
  94. self.assertTrue(self.test_rpc_operation in self._rpc_label.get_text())
  95. def test_escape_and_format_rpc_text(self):
  96. self.assertEquals("qubes.<b>Test</b>",
  97. self._escape_and_format_rpc_text("qubes.Test"))
  98. self.assertEquals("custom.<b>Domain</b>",
  99. self._escape_and_format_rpc_text("custom.Domain"))
  100. self.assertEquals("<b>nodomain</b>",
  101. self._escape_and_format_rpc_text("nodomain"))
  102. self.assertEquals("domain.<b>Sub.Operation</b>",
  103. self._escape_and_format_rpc_text("domain.Sub.Operation"))
  104. self.assertEquals("<b></b>",
  105. self._escape_and_format_rpc_text(""))
  106. self.assertEquals("<b>.</b>",
  107. self._escape_and_format_rpc_text("."))
  108. self.assertEquals("inject.<b>&lt;script&gt;</b>",
  109. self._escape_and_format_rpc_text("inject.<script>"))
  110. self.assertEquals("&lt;script&gt;.<b>inject</b>",
  111. self._escape_and_format_rpc_text("<script>.inject"))
  112. def test_lifecycle_open_select_ok(self):
  113. self._lifecycle_start(select_target=True)
  114. self._lifecycle_click(click_type="ok")
  115. def test_lifecycle_open_select_cancel(self):
  116. self._lifecycle_start(select_target=True)
  117. self._lifecycle_click(click_type="cancel")
  118. def test_lifecycle_open_select_exit(self):
  119. self._lifecycle_start(select_target=True)
  120. self._lifecycle_click(click_type="exit")
  121. def test_lifecycle_open_cancel(self):
  122. self._lifecycle_start(select_target=False)
  123. self._lifecycle_click(click_type="cancel")
  124. def test_lifecycle_open_exit(self):
  125. self._lifecycle_start(select_target=False)
  126. self._lifecycle_click(click_type="exit")
  127. def _lifecycle_click(self, click_type):
  128. if click_type == "ok":
  129. self._rpc_ok_button.clicked()
  130. self.assertTrue(self.test_clicked_ok)
  131. self.assertFalse(self.test_clicked_cancel)
  132. self.assertTrue(self._confirmed)
  133. self.assertIsNotNone(self._target_name)
  134. elif click_type == "cancel":
  135. self._rpc_cancel_button.clicked()
  136. self.assertFalse(self.test_clicked_ok)
  137. self.assertTrue(self.test_clicked_cancel)
  138. self.assertFalse(self._confirmed)
  139. elif click_type == "exit":
  140. self._close()
  141. self.assertFalse(self.test_clicked_ok)
  142. self.assertFalse(self.test_clicked_cancel)
  143. self.assertIsNone(self._confirmed)
  144. self.assertTrue(self.test_called_close)
  145. def _lifecycle_start(self, select_target):
  146. self.assertFalse(self.test_called_close)
  147. self.assertFalse(self.test_called_show)
  148. self.assert_initial_state(False)
  149. self.assertTrue(isinstance(self._focus_helper, FocusStealingHelperMock))
  150. # Need the following because of pylint's complaints
  151. if isinstance(self._focus_helper, FocusStealingHelperMock):
  152. FocusStealingHelperMock.simulate_focus(self._focus_helper)
  153. self.flush_gtk_events(self._test_time*2)
  154. self.assert_initial_state(True)
  155. try:
  156. # We expect the call to exit immediately, since no window is opened
  157. self.confirm_rpc()
  158. except Exception:
  159. pass
  160. self.assertFalse(self.test_called_close)
  161. self.assertTrue(self.test_called_show)
  162. self.assert_initial_state(True)
  163. if select_target:
  164. self._rpc_combo_box.set_active(1)
  165. self.assertTrue(self._rpc_ok_button.get_sensitive())
  166. self.assertIsNotNone(self._target_name)
  167. self.assertFalse(self.test_called_close)
  168. self.assertTrue(self.test_called_show)
  169. self.assertFalse(self.test_clicked_ok)
  170. self.assertFalse(self.test_clicked_cancel)
  171. self.assertFalse(self._confirmed)
  172. def assert_initial_state(self, after_focus_timer):
  173. self.assertIsNone(self._target_name)
  174. self.assertFalse(self.test_clicked_ok)
  175. self.assertFalse(self.test_clicked_cancel)
  176. self.assertFalse(self._confirmed)
  177. self.assertFalse(self._rpc_ok_button.get_sensitive())
  178. self.assertFalse(self._error_bar.get_visible())
  179. if after_focus_timer:
  180. self.assertTrue(self._focus_helper.can_perform_action())
  181. else:
  182. self.assertFalse(self._focus_helper.can_perform_action())
  183. class RPCConfirmationWindowTestWithTarget(RPCConfirmationWindowTestBase):
  184. def __init__(self, test_method):
  185. RPCConfirmationWindowTestBase.__init__(self, test_method,
  186. source_name="test-source", rpc_operation="test.Operation",
  187. target_name="test-target")
  188. def test_lifecycle_open_ok(self):
  189. self._lifecycle_start(select_target=False)
  190. self._lifecycle_click(click_type="ok")
  191. def assert_initial_state(self, after_focus_timer):
  192. self.assertIsNotNone(self._target_name)
  193. self.assertFalse(self.test_clicked_ok)
  194. self.assertFalse(self.test_clicked_cancel)
  195. self.assertFalse(self._confirmed)
  196. if after_focus_timer:
  197. self.assertTrue(self._rpc_ok_button.get_sensitive())
  198. self.assertTrue(self._focus_helper.can_perform_action())
  199. self.assertEqual(self._target_name, 'test-target')
  200. else:
  201. self.assertFalse(self._rpc_ok_button.get_sensitive())
  202. self.assertFalse(self._focus_helper.can_perform_action())
  203. def _lifecycle_click(self, click_type):
  204. RPCConfirmationWindowTestBase._lifecycle_click(self, click_type)
  205. self.assertIsNotNone(self._target_name)
  206. class RPCConfirmationWindowTestWithDispVMTarget(RPCConfirmationWindowTestBase):
  207. def __init__(self, test_method):
  208. RPCConfirmationWindowTestBase.__init__(self, test_method,
  209. source_name="test-source", rpc_operation="test.Operation",
  210. target_name="@dispvm:test-disp6")
  211. def test_lifecycle_open_ok(self):
  212. self._lifecycle_start(select_target=False)
  213. self._lifecycle_click(click_type="ok")
  214. def assert_initial_state(self, after_focus_timer):
  215. self.assertIsNotNone(self._target_name)
  216. self.assertFalse(self.test_clicked_ok)
  217. self.assertFalse(self.test_clicked_cancel)
  218. self.assertFalse(self._confirmed)
  219. if after_focus_timer:
  220. self.assertTrue(self._rpc_ok_button.get_sensitive())
  221. self.assertTrue(self._focus_helper.can_perform_action())
  222. self.assertEqual(self._target_name, '@dispvm:test-disp6')
  223. else:
  224. self.assertFalse(self._rpc_ok_button.get_sensitive())
  225. self.assertFalse(self._focus_helper.can_perform_action())
  226. class RPCConfirmationWindowTestWithTargetInvalid(unittest.TestCase):
  227. def __init__(self, *args, **kwargs):
  228. unittest.TestCase.__init__(self, *args, **kwargs)
  229. def test_unknown(self):
  230. self.assert_raises_error(True, "test-source", "test-wrong-target")
  231. def test_empty(self):
  232. self.assert_raises_error(True, "test-source", "")
  233. def test_equals_source(self):
  234. self.assert_raises_error(True, "test-source", "test-source")
  235. def assert_raises_error(self, expect, source, target):
  236. rpcWindow = MockRPCConfirmationWindow(source, "test.Operation",
  237. mock_whitelist, target=target)
  238. self.assertEquals(expect, rpcWindow.is_error_visible())
  239. class RPCConfirmationWindowTestWhitelist(unittest.TestCase):
  240. def __init__(self, *args, **kwargs):
  241. unittest.TestCase.__init__(self, *args, **kwargs)
  242. def test_no_domains(self):
  243. self._assert_whitelist([], [])
  244. def test_all_red_domains(self):
  245. self._assert_whitelist(["test-red1", "test-red2", "test-red3"],
  246. ["test-red1", "test-red2", "test-red3"])
  247. def test_all_red_domains_plus_nonexistent(self):
  248. self._assert_whitelist(
  249. ["test-red1", "test-red2", "test-red3",
  250. "test-blue1", "test-blue2", "test-blue3"],
  251. ["test-red1", "test-red2", "test-red3"])
  252. def test_all_allowed_domains(self):
  253. self._assert_whitelist(
  254. ["test-red1", "test-red2", "test-red3",
  255. "test-target", "@dispvm:test-disp6", "test-source", "dom0"],
  256. ["test-red1", "test-red2", "test-red3",
  257. "test-target", "Disposable VM (test-disp6)", "test-source",
  258. "dom0"])
  259. def _assert_whitelist(self, whitelist, expected):
  260. rpcWindow = MockRPCConfirmationWindow(
  261. "test-source", "test.Operation", whitelist)
  262. domains = rpcWindow.get_shown_domains()
  263. self.assertCountEqual(domains, expected)
  264. if __name__ == '__main__':
  265. test = False
  266. window = False
  267. if len(sys.argv) == 1 or sys.argv[1] == '-t':
  268. test = True
  269. elif sys.argv[1] == '-w':
  270. window = True
  271. else:
  272. print("Usage: " + __file__ + " [-t|-w]")
  273. if window:
  274. print(MockRPCConfirmationWindow("test-source",
  275. "qubes.Filecopy",
  276. mock_whitelist,
  277. "test-red1").confirm_rpc())
  278. elif test:
  279. unittest.main(argv=[sys.argv[0]])