rpcconfirmation.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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 program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program 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
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License along
  18. # with this program; if not, write to the Free Software Foundation, Inc.,
  19. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  20. #
  21. import os
  22. from gi.repository import Gtk, Gdk, GLib # pylint: disable=import-error
  23. import pkg_resources
  24. from qubespolicy.gtkhelpers import VMListModeler, FocusStealingHelper
  25. from qubespolicy.utils import sanitize_domain_name, \
  26. sanitize_service_name
  27. class RPCConfirmationWindow:
  28. # pylint: disable=too-few-public-methods
  29. _source_file = pkg_resources.resource_filename('qubespolicy',
  30. os.path.join('glade', "RPCConfirmationWindow.glade"))
  31. _source_id = {'window': "RPCConfirmationWindow",
  32. 'ok': "okButton",
  33. 'cancel': "cancelButton",
  34. 'source': "sourceEntry",
  35. 'rpc_label': "rpcLabel",
  36. 'target': "TargetCombo",
  37. 'error_bar': "ErrorBar",
  38. 'error_message': "ErrorMessage",
  39. }
  40. def _clicked_ok(self, source):
  41. assert source is not None, \
  42. 'Called the clicked ok callback from no source object'
  43. if self._can_perform_action():
  44. self._confirmed = True
  45. self._close()
  46. def _clicked_cancel(self, button):
  47. assert button == self._rpc_cancel_button, \
  48. 'Called the clicked cancel callback through the wrong button'
  49. if self._can_perform_action():
  50. self._confirmed = False
  51. self._close()
  52. def _key_pressed(self, window, key):
  53. assert window == self._rpc_window, \
  54. 'Key pressed callback called with wrong window'
  55. if self._can_perform_action():
  56. if key.keyval == Gdk.KEY_Escape:
  57. self._confirmed = False
  58. self._close()
  59. def _update_ok_button_sensitivity(self, data):
  60. valid = (data is not None)
  61. if valid:
  62. self._target_name = data
  63. else:
  64. self._target_name = None
  65. self._focus_helper.request_sensitivity(valid)
  66. def _show_error(self, error_message):
  67. self._error_message.set_text(error_message)
  68. self._error_bar.set_visible(True)
  69. def _close_error(self, error_bar, response):
  70. assert error_bar == self._error_bar, \
  71. 'Closed the error bar with the wrong error bar as parameter'
  72. assert response is not None, \
  73. 'Closed the error bar with None as a response'
  74. self._error_bar.set_visible(False)
  75. def _set_initial_target(self, source, target):
  76. if target is not None:
  77. if target == source:
  78. self._show_error(
  79. "Source and target domains must not be the same.")
  80. else:
  81. model = self._rpc_combo_box.get_model()
  82. found = False
  83. for item in model:
  84. if item[1] == target:
  85. found = True
  86. self._rpc_combo_box.set_active_iter(
  87. model.get_iter(item.path))
  88. break
  89. if not found:
  90. self._show_error("Domain '%s' doesn't exist." % target)
  91. def _can_perform_action(self):
  92. return self._focus_helper.can_perform_action()
  93. @staticmethod
  94. def _escape_and_format_rpc_text(rpc_operation):
  95. escaped = GLib.markup_escape_text(rpc_operation)
  96. partitioned = escaped.partition('.')
  97. formatted = partitioned[0] + partitioned[1]
  98. if partitioned[2]:
  99. formatted += "<b>" + partitioned[2] + "</b>"
  100. else:
  101. formatted = "<b>" + formatted + "</b>"
  102. return formatted
  103. def _connect_events(self):
  104. self._rpc_window.connect("key-press-event", self._key_pressed)
  105. self._rpc_ok_button.connect("clicked", self._clicked_ok)
  106. self._rpc_cancel_button.connect("clicked", self._clicked_cancel)
  107. self._error_bar.connect("response", self._close_error)
  108. def __init__(self, entries_info, source, rpc_operation, targets_list,
  109. target=None):
  110. sanitize_domain_name(source, assert_sanitized=True)
  111. sanitize_service_name(source, assert_sanitized=True)
  112. self._gtk_builder = Gtk.Builder()
  113. self._gtk_builder.add_from_file(self._source_file)
  114. self._rpc_window = self._gtk_builder.get_object(
  115. self._source_id['window'])
  116. self._rpc_ok_button = self._gtk_builder.get_object(
  117. self._source_id['ok'])
  118. self._rpc_cancel_button = self._gtk_builder.get_object(
  119. self._source_id['cancel'])
  120. self._rpc_label = self._gtk_builder.get_object(
  121. self._source_id['rpc_label'])
  122. self._source_entry = self._gtk_builder.get_object(
  123. self._source_id['source'])
  124. self._rpc_combo_box = self._gtk_builder.get_object(
  125. self._source_id['target'])
  126. self._error_bar = self._gtk_builder.get_object(
  127. self._source_id['error_bar'])
  128. self._error_message = self._gtk_builder.get_object(
  129. self._source_id['error_message'])
  130. self._target_name = None
  131. self._focus_helper = self._new_focus_stealing_helper()
  132. self._rpc_label.set_markup(
  133. self._escape_and_format_rpc_text(rpc_operation))
  134. self._entries_info = entries_info
  135. list_modeler = self._new_vm_list_modeler()
  136. list_modeler.apply_model(self._rpc_combo_box, targets_list,
  137. selection_trigger=self._update_ok_button_sensitivity,
  138. activation_trigger=self._clicked_ok)
  139. self._source_entry.set_text(source)
  140. list_modeler.apply_icon(self._source_entry, source)
  141. self._confirmed = None
  142. self._set_initial_target(source, target)
  143. self._connect_events()
  144. def _close(self):
  145. self._rpc_window.close()
  146. def _show(self):
  147. self._rpc_window.set_keep_above(True)
  148. self._rpc_window.connect("delete-event", Gtk.main_quit)
  149. self._rpc_window.show_all()
  150. Gtk.main()
  151. def _new_vm_list_modeler(self):
  152. return VMListModeler(self._entries_info)
  153. def _new_focus_stealing_helper(self):
  154. return FocusStealingHelper(
  155. self._rpc_window,
  156. self._rpc_ok_button,
  157. 1)
  158. def confirm_rpc(self):
  159. self._show()
  160. if self._confirmed:
  161. return self._target_name
  162. return False
  163. def confirm_rpc(entries_info, source, rpc_operation, targets_list, target=None):
  164. window = RPCConfirmationWindow(entries_info, source, rpc_operation,
  165. targets_list, target)
  166. return window.confirm_rpc()