2017-03-22 15:14:28 +01:00
|
|
|
#!/usr/bin/python
|
|
|
|
#
|
|
|
|
# The Qubes OS Project, https://www.qubes-os.org/
|
|
|
|
#
|
|
|
|
# Copyright (C) 2017 boring-stuff <boring-stuff@users.noreply.github.com>
|
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# 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.
|
2017-03-22 15:14:28 +01:00
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# This library is distributed in the hope that it will be useful,
|
2017-03-22 15:14:28 +01:00
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2017-10-12 00:11:50 +02:00
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
# Lesser General Public License for more details.
|
2017-03-22 15:14:28 +01:00
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# 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/>.
|
2017-03-22 15:14:28 +01:00
|
|
|
#
|
|
|
|
import os
|
2017-04-06 16:29:49 +02:00
|
|
|
from gi.repository import Gtk, Gdk, GLib # pylint: disable=import-error
|
|
|
|
import pkg_resources
|
|
|
|
|
2017-03-24 01:44:52 +01:00
|
|
|
from qubespolicy.gtkhelpers import VMListModeler, FocusStealingHelper
|
2017-03-22 16:27:14 +01:00
|
|
|
from qubespolicy.utils import sanitize_domain_name, \
|
|
|
|
sanitize_service_name
|
|
|
|
|
2017-03-22 15:14:28 +01:00
|
|
|
|
2017-03-22 16:27:14 +01:00
|
|
|
class RPCConfirmationWindow:
|
2017-04-06 16:29:49 +02:00
|
|
|
# pylint: disable=too-few-public-methods
|
2017-03-24 01:44:52 +01:00
|
|
|
_source_file = pkg_resources.resource_filename('qubespolicy',
|
|
|
|
os.path.join('glade', "RPCConfirmationWindow.glade"))
|
2017-03-22 16:27:14 +01:00
|
|
|
_source_id = {'window': "RPCConfirmationWindow",
|
2017-03-22 15:14:28 +01:00
|
|
|
'ok': "okButton",
|
|
|
|
'cancel': "cancelButton",
|
|
|
|
'source': "sourceEntry",
|
2017-03-22 16:27:14 +01:00
|
|
|
'rpc_label': "rpcLabel",
|
2017-03-22 15:14:28 +01:00
|
|
|
'target': "TargetCombo",
|
|
|
|
'error_bar': "ErrorBar",
|
|
|
|
'error_message': "ErrorMessage",
|
2017-03-22 16:27:14 +01:00
|
|
|
}
|
2017-03-22 15:14:28 +01:00
|
|
|
|
|
|
|
def _clicked_ok(self, source):
|
2017-03-22 16:27:14 +01:00
|
|
|
assert source is not None, \
|
2017-03-22 15:14:28 +01:00
|
|
|
'Called the clicked ok callback from no source object'
|
|
|
|
|
|
|
|
if self._can_perform_action():
|
|
|
|
self._confirmed = True
|
|
|
|
self._close()
|
|
|
|
|
|
|
|
def _clicked_cancel(self, button):
|
|
|
|
assert button == self._rpc_cancel_button, \
|
|
|
|
'Called the clicked cancel callback through the wrong button'
|
|
|
|
|
|
|
|
if self._can_perform_action():
|
|
|
|
self._confirmed = False
|
|
|
|
self._close()
|
|
|
|
|
|
|
|
def _key_pressed(self, window, key):
|
|
|
|
assert window == self._rpc_window, \
|
|
|
|
'Key pressed callback called with wrong window'
|
|
|
|
|
|
|
|
if self._can_perform_action():
|
|
|
|
if key.keyval == Gdk.KEY_Escape:
|
|
|
|
self._confirmed = False
|
|
|
|
self._close()
|
|
|
|
|
|
|
|
def _update_ok_button_sensitivity(self, data):
|
2017-03-22 16:27:14 +01:00
|
|
|
valid = (data is not None)
|
2017-03-22 15:14:28 +01:00
|
|
|
|
|
|
|
if valid:
|
2017-03-24 02:49:02 +01:00
|
|
|
self._target_name = data
|
2017-03-22 15:14:28 +01:00
|
|
|
else:
|
|
|
|
self._target_name = None
|
|
|
|
|
|
|
|
self._focus_helper.request_sensitivity(valid)
|
|
|
|
|
|
|
|
def _show_error(self, error_message):
|
|
|
|
self._error_message.set_text(error_message)
|
|
|
|
self._error_bar.set_visible(True)
|
|
|
|
|
|
|
|
def _close_error(self, error_bar, response):
|
|
|
|
assert error_bar == self._error_bar, \
|
|
|
|
'Closed the error bar with the wrong error bar as parameter'
|
2017-03-22 16:27:14 +01:00
|
|
|
assert response is not None, \
|
|
|
|
'Closed the error bar with None as a response'
|
2017-03-22 15:14:28 +01:00
|
|
|
|
|
|
|
self._error_bar.set_visible(False)
|
|
|
|
|
|
|
|
def _set_initial_target(self, source, target):
|
2017-03-22 16:27:14 +01:00
|
|
|
if target is not None:
|
2017-03-22 15:14:28 +01:00
|
|
|
if target == source:
|
|
|
|
self._show_error(
|
|
|
|
"Source and target domains must not be the same.")
|
|
|
|
else:
|
|
|
|
model = self._rpc_combo_box.get_model()
|
|
|
|
|
|
|
|
found = False
|
|
|
|
for item in model:
|
|
|
|
if item[1] == target:
|
|
|
|
found = True
|
|
|
|
|
|
|
|
self._rpc_combo_box.set_active_iter(
|
|
|
|
model.get_iter(item.path))
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
if not found:
|
|
|
|
self._show_error("Domain '%s' doesn't exist." % target)
|
|
|
|
|
|
|
|
def _can_perform_action(self):
|
|
|
|
return self._focus_helper.can_perform_action()
|
|
|
|
|
2017-04-06 16:29:49 +02:00
|
|
|
@staticmethod
|
|
|
|
def _escape_and_format_rpc_text(rpc_operation):
|
2017-03-22 15:14:28 +01:00
|
|
|
escaped = GLib.markup_escape_text(rpc_operation)
|
|
|
|
|
|
|
|
partitioned = escaped.partition('.')
|
|
|
|
formatted = partitioned[0] + partitioned[1]
|
|
|
|
|
2017-04-21 15:43:46 +02:00
|
|
|
if partitioned[2]:
|
2017-03-22 15:14:28 +01:00
|
|
|
formatted += "<b>" + partitioned[2] + "</b>"
|
|
|
|
else:
|
|
|
|
formatted = "<b>" + formatted + "</b>"
|
|
|
|
|
|
|
|
return formatted
|
|
|
|
|
|
|
|
def _connect_events(self):
|
2017-03-22 16:27:14 +01:00
|
|
|
self._rpc_window.connect("key-press-event", self._key_pressed)
|
2017-03-22 15:14:28 +01:00
|
|
|
self._rpc_ok_button.connect("clicked", self._clicked_ok)
|
|
|
|
self._rpc_cancel_button.connect("clicked", self._clicked_cancel)
|
|
|
|
|
|
|
|
self._error_bar.connect("response", self._close_error)
|
|
|
|
|
2017-03-24 02:49:02 +01:00
|
|
|
def __init__(self, entries_info, source, rpc_operation, targets_list,
|
|
|
|
target=None):
|
2017-03-22 16:27:14 +01:00
|
|
|
sanitize_domain_name(source, assert_sanitized=True)
|
|
|
|
sanitize_service_name(source, assert_sanitized=True)
|
2017-03-22 15:14:28 +01:00
|
|
|
|
|
|
|
self._gtk_builder = Gtk.Builder()
|
|
|
|
self._gtk_builder.add_from_file(self._source_file)
|
|
|
|
self._rpc_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._rpc_label = self._gtk_builder.get_object(
|
|
|
|
self._source_id['rpc_label'])
|
|
|
|
self._source_entry = self._gtk_builder.get_object(
|
|
|
|
self._source_id['source'])
|
|
|
|
self._rpc_combo_box = self._gtk_builder.get_object(
|
|
|
|
self._source_id['target'])
|
|
|
|
self._error_bar = self._gtk_builder.get_object(
|
|
|
|
self._source_id['error_bar'])
|
|
|
|
self._error_message = self._gtk_builder.get_object(
|
|
|
|
self._source_id['error_message'])
|
|
|
|
self._target_name = None
|
|
|
|
|
|
|
|
self._focus_helper = self._new_focus_stealing_helper()
|
|
|
|
|
|
|
|
self._rpc_label.set_markup(
|
|
|
|
self._escape_and_format_rpc_text(rpc_operation))
|
|
|
|
|
2017-03-24 02:49:02 +01:00
|
|
|
self._entries_info = entries_info
|
2017-04-06 16:29:49 +02:00
|
|
|
list_modeler = self._new_vm_list_modeler()
|
2017-03-22 15:14:28 +01:00
|
|
|
|
2017-03-24 02:49:02 +01:00
|
|
|
list_modeler.apply_model(self._rpc_combo_box, targets_list,
|
2017-03-22 16:27:14 +01:00
|
|
|
selection_trigger=self._update_ok_button_sensitivity,
|
|
|
|
activation_trigger=self._clicked_ok)
|
2017-03-22 15:14:28 +01:00
|
|
|
|
|
|
|
self._source_entry.set_text(source)
|
|
|
|
list_modeler.apply_icon(self._source_entry, source)
|
|
|
|
|
|
|
|
self._confirmed = None
|
|
|
|
|
|
|
|
self._set_initial_target(source, target)
|
|
|
|
|
|
|
|
self._connect_events()
|
|
|
|
|
|
|
|
def _close(self):
|
|
|
|
self._rpc_window.close()
|
|
|
|
|
|
|
|
def _show(self):
|
|
|
|
self._rpc_window.set_keep_above(True)
|
|
|
|
self._rpc_window.connect("delete-event", Gtk.main_quit)
|
|
|
|
self._rpc_window.show_all()
|
|
|
|
|
|
|
|
Gtk.main()
|
|
|
|
|
2017-04-06 16:29:49 +02:00
|
|
|
def _new_vm_list_modeler(self):
|
2017-03-24 02:49:02 +01:00
|
|
|
return VMListModeler(self._entries_info)
|
2017-03-22 15:14:28 +01:00
|
|
|
|
|
|
|
def _new_focus_stealing_helper(self):
|
|
|
|
return FocusStealingHelper(
|
|
|
|
self._rpc_window,
|
|
|
|
self._rpc_ok_button,
|
|
|
|
1)
|
|
|
|
|
|
|
|
def confirm_rpc(self):
|
|
|
|
self._show()
|
|
|
|
|
|
|
|
if self._confirmed:
|
2017-03-24 02:49:02 +01:00
|
|
|
return self._target_name
|
2017-04-21 15:43:46 +02:00
|
|
|
return False
|
2017-03-22 15:14:28 +01:00
|
|
|
|
2017-03-22 16:27:14 +01:00
|
|
|
|
2017-03-24 02:49:02 +01:00
|
|
|
def confirm_rpc(entries_info, source, rpc_operation, targets_list, target=None):
|
|
|
|
window = RPCConfirmationWindow(entries_info, source, rpc_operation,
|
|
|
|
targets_list, target)
|
2017-03-22 15:14:28 +01:00
|
|
|
|
|
|
|
return window.confirm_rpc()
|