Import new rpc confirmation window code

Import unmodified implementation done by @boring-stuff.
Full history for reference is available in rpc-confirmation-window
branch.

QubesOS/qubes-issues#910
This commit is contained in:
Marek Marczykowski-Górecki 2017-03-22 15:14:28 +01:00
parent 83526a28d3
commit b3ceb2d7fa
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
6 changed files with 1704 additions and 0 deletions

View File

@ -0,0 +1,359 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkWindow" id="RPCConfirmationWindow">
<property name="width_request">400</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Operation execution</property>
<property name="window_position">center</property>
<property name="icon_name">dialog-question</property>
<property name="type_hint">dialog</property>
<property name="urgency_hint">True</property>
<property name="gravity">center</property>
<child>
<object class="GtkBox" id="WindowBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkInfoBar" id="ErrorBar">
<property name="app_paintable">True</property>
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="message_type">error</property>
<property name="show_close_button">True</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="ActionArea">
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox" id="ContentArea">
<property name="can_focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkImage" id="ErrorImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-error</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ErrorMessage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">ErrorMessage</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="MainBox">
<property name="width_request">100</property>
<property name="height_request">80</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">12</property>
<property name="margin_right">12</property>
<property name="margin_top">12</property>
<property name="margin_bottom">12</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkButtonBox" id="ButtonBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="cancelButton">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="okButton">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="ContentBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkBox" id="AlwaysShownBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">6</property>
<property name="spacing">12</property>
<child>
<object class="GtkImage" id="RPCConfirmationIcon">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-question</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="rpcDescription">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Do you want to allow the following operation?
&lt;small&gt;Select the target domain and confirm with 'OK'&lt;/small&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="grid1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">12</property>
<property name="margin_bottom">6</property>
<property name="row_spacing">12</property>
<property name="column_spacing">6</property>
<child>
<object class="GtkLabel" id="TargetDescLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">Target:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="TargetCombo">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="has_entry">True</property>
<child internal-child="entry">
<object class="GtkEntry" id="TargetComboEntry">
<property name="can_focus">True</property>
<property name="width_chars">5</property>
<property name="primary_icon_activatable">False</property>
<property name="placeholder_text" translatable="yes">Start typing or use the arrow</property>
<property name="input_hints">GTK_INPUT_HINT_WORD_COMPLETION | GTK_INPUT_HINT_NONE</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="sourceDescLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">Source:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="sourceEntry">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="editable">False</property>
<property name="text" translatable="yes">source</property>
<property name="primary_icon_activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="operationDescLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">Operation:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="rpcLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">qubes.&lt;b&gt;MyOperation&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkExpander" id="AdvancedSection">
<property name="can_focus">True</property>
<property name="no_show_all">True</property>
<child>
<object class="GtkBox" id="AdvancedOptions">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkCheckButton" id="DisplayTemplates">
<property name="label" translatable="yes">Display templates in the target list</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="CustomLocation">
<property name="label" translatable="yes">Choose a custom destination in the target</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="AdvancedLabel">
<property name="can_focus">False</property>
<property name="label" translatable="yes">Advanced options</property>
<property name="use_underline">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

301
qubespolicy/gtkhelpers.py Normal file
View File

@ -0,0 +1,301 @@
#!/usr/bin/python
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2017 boring-stuff <boring-stuff@users.noreply.github.com>
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
import qubes
import gi, os
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkPixbuf, GObject, GLib
from . qubesutils import sanitize_domain_name
glade_directory = os.path.join(os.path.dirname(__file__), "glade")
class GtkIconGetter:
def __init__(self, size):
self._icons = {}
self._size = size
self._theme = Gtk.IconTheme.get_default()
def get_icon(self, name):
if name not in self._icons:
try:
icon = self._theme.load_icon(name, self._size, 0)
except GLib.Error:
icon = self._theme.load_icon("gnome-foot", self._size, 0)
self._icons[name] = icon
return self._icons[name]
class VMListModeler:
def __init__(self):
self._icon_getter = GtkIconGetter(16)
self._entries = {}
self._create_entries()
def _get_icon(self, vm):
return self._icon_getter.get_icon(vm.label.icon)
def _get_list(self):
collection = qubes.QubesVmCollection()
try:
collection.lock_db_for_reading()
collection.load()
return [vm for vm in collection.values()]
finally:
collection.unlock_db()
def _create_entries(self):
for vm in self._get_list():
sanitize_domain_name(vm.name, assert_sanitized = True)
icon = self._get_icon(vm)
self._entries[vm.name] = {'qid': vm.qid,
'icon': icon,
'vm': vm}
def _get_valid_qube_name(self, combo, entry_box, exclusions):
name = None
if combo and combo.get_active_id():
selected = combo.get_active_id()
if selected in self._entries and selected not in exclusions:
name = selected
if not name and entry_box:
typed = entry_box.get_text()
if typed in self._entries and typed not in exclusions:
name = typed
return name
def _combo_change(self, selection_trigger, combo, entry_box, exclusions):
data = None
name = self._get_valid_qube_name(combo, entry_box, exclusions)
if name:
entry = self._entries[name]
data = (entry['qid'], name)
if entry_box:
entry_box.set_icon_from_pixbuf(
Gtk.EntryIconPosition.PRIMARY, entry['icon'])
else:
if entry_box:
entry_box.set_icon_from_stock(
Gtk.EntryIconPosition.PRIMARY, "gtk-find")
if selection_trigger:
selection_trigger(data)
def _entry_activate(self, activation_trigger, combo, entry_box, exclusions):
name = self._get_valid_qube_name(combo, entry_box, exclusions)
if name:
activation_trigger(entry_box)
def apply_model(self, destination_object, vm_filter_list = None,
selection_trigger = None, activation_trigger = None):
if isinstance(destination_object, Gtk.ComboBox):
list_store = Gtk.ListStore(int, str, GdkPixbuf.Pixbuf)
exclusions = []
for vm_name in sorted(self._entries.iterkeys()):
entry = self._entries[vm_name]
matches = True
if vm_filter_list:
for vm_filter in vm_filter_list:
if not vm_filter.matches(entry['vm']):
matches = False
break
if matches:
list_store.append([entry['qid'], vm_name, entry['icon']])
else:
exclusions += [vm_name]
destination_object.set_model(list_store)
destination_object.set_id_column(1)
icon_column = Gtk.CellRendererPixbuf()
destination_object.pack_start(icon_column, False)
destination_object.add_attribute(icon_column, "pixbuf", 2)
destination_object.set_entry_text_column(1)
if destination_object.get_has_entry():
entry_box = destination_object.get_child()
area = Gtk.CellAreaBox()
area.pack_start(icon_column, False, False, False)
area.add_attribute(icon_column, "pixbuf", 2)
completion = Gtk.EntryCompletion.new_with_area(area)
completion.set_inline_selection(True)
completion.set_inline_completion(True)
completion.set_popup_completion(True)
completion.set_popup_single_match(False)
completion.set_model(list_store)
completion.set_text_column(1)
entry_box.set_completion(completion)
if activation_trigger:
entry_box.connect("activate",
lambda entry: self._entry_activate(
activation_trigger,
destination_object,
entry,
exclusions))
# A Combo with an entry has a text column already
text_column = destination_object.get_cells()[0]
destination_object.reorder(text_column, 1)
else:
entry_box = None
text_column = Gtk.CellRendererText()
destination_object.pack_start(text_column, False)
destination_object.add_attribute(text_column, "text", 1)
changed_function = lambda combo: self._combo_change(
selection_trigger,
combo,
entry_box,
exclusions)
destination_object.connect("changed", changed_function)
changed_function(destination_object)
else:
raise TypeError(
"Only expecting Gtk.ComboBox objects to want our model.")
def apply_icon(self, entry, qube_name):
if isinstance(entry, Gtk.Entry):
if qube_name in self._entries:
entry.set_icon_from_pixbuf(
Gtk.EntryIconPosition.PRIMARY,
self._entries[qube_name]['icon'])
else:
raise ValueError("The specified source qube does not exist!")
else:
raise TypeError(
"Only expecting Gtk.Entry objects to want our icon.")
class NameBlacklistFilter:
def __init__(self, avoid_names_list):
self._avoid_names_list = avoid_names_list
def matches(self, vm):
return vm.name not in self._avoid_names_list
class NameWhitelistFilter:
def __init__(self, allowed_names_list):
self._allowed_names_list = allowed_names_list
def matches(self, vm):
return vm.name in self._allowed_names_list
class GtkOneTimerHelper:
def __init__(self, wait_seconds):
self._wait_seconds = wait_seconds
self._current_timer_id = 0
self._timer_completed = False
def _invalidate_timer_completed(self):
self._timer_completed = False
def _invalidate_current_timer(self):
self._current_timer_id += 1
def _timer_check_run(self, timer_id):
if self._current_timer_id == timer_id:
self._timer_run(timer_id)
self._timer_completed = True
else:
pass
def _timer_run(self, timer_id):
raise NotImplementedError("Not yet implemented")
def _timer_schedule(self):
self._invalidate_current_timer()
GObject.timeout_add(int(round(self._wait_seconds * 1000)),
self._timer_check_run,
self._current_timer_id)
def _timer_has_completed(self):
return self._timer_completed
class FocusStealingHelper(GtkOneTimerHelper):
def __init__(self, window, target_button, wait_seconds = 1):
GtkOneTimerHelper.__init__(self, wait_seconds)
self._window = window
self._target_button = target_button
self._window.connect("window-state-event", self._window_state_event)
self._target_sensitivity = False
self._target_button.set_sensitive(self._target_sensitivity)
def _window_changed_focus(self, window_is_focused):
self._target_button.set_sensitive(False)
self._invalidate_timer_completed()
if window_is_focused:
self._timer_schedule()
else:
self._invalidate_current_timer()
def _window_state_event(self, window, event):
assert window == self._window, \
'Window state callback called with wrong window'
changed_focus = event.changed_mask & Gdk.WindowState.FOCUSED
window_focus = event.new_window_state & Gdk.WindowState.FOCUSED
if changed_focus:
self._window_changed_focus(window_focus != 0)
# Propagate event further
return False
def _timer_run(self, timer_id):
self._target_button.set_sensitive(self._target_sensitivity)
def request_sensitivity(self, sensitivity):
if self._timer_has_completed() or not sensitivity:
self._target_button.set_sensitive(sensitivity)
self._target_sensitivity = sensitivity
def can_perform_action(self):
return self._timer_has_completed()

View File

@ -0,0 +1,210 @@
#!/usr/bin/python
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2017 boring-stuff <boring-stuff@users.noreply.github.com>
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
from . gtkhelpers import VMListModeler, FocusStealingHelper, glade_directory
from . qubesutils import sanitize_domain_name, sanitize_service_name
from gi.repository import Gtk, Gdk, GLib
import os
class RPCConfirmationWindow():
_source_file = os.path.join(glade_directory, "RPCConfirmationWindow.glade")
_source_id = { 'window': "RPCConfirmationWindow",
'ok': "okButton",
'cancel': "cancelButton",
'source': "sourceEntry",
'rpc_label' : "rpcLabel",
'target': "TargetCombo",
'error_bar': "ErrorBar",
'error_message': "ErrorMessage",
}
def _clicked_ok(self, source):
assert source != None, \
'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):
valid = (data != None)
if valid:
(self._target_qid, self._target_name) = data
else:
self._target_qid = None
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'
assert response != None, 'Closed the error bar with None as a response'
self._error_bar.set_visible(False)
def _set_initial_target(self, source, target):
if target != None:
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()
def _escape_and_format_rpc_text(self, rpc_operation):
escaped = GLib.markup_escape_text(rpc_operation)
partitioned = escaped.partition('.')
formatted = partitioned[0] + partitioned[1]
if len(partitioned[2]) > 0:
formatted += "<b>" + partitioned[2] + "</b>"
else:
formatted = "<b>" + formatted + "</b>"
return formatted
def _connect_events(self):
self._rpc_window.connect("key-press-event",self._key_pressed)
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)
def __init__(self, source, rpc_operation, name_whitelist, target = None):
sanitize_domain_name(source, assert_sanitized = True)
sanitize_service_name(source, assert_sanitized = True)
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_qid = None
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))
list_modeler = self._new_VM_list_modeler()
domain_filters = [VMListModeler.NameWhitelistFilter(name_whitelist)]
list_modeler.apply_model(self._rpc_combo_box, domain_filters,
selection_trigger = self._update_ok_button_sensitivity,
activation_trigger = self._clicked_ok )
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()
def _new_VM_list_modeler(self):
return VMListModeler()
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:
return { 'name': self._target_name, 'qid': self._target_qid,
'parameters': {} }
else:
return False
def confirm_rpc(source, rpc_operation, name_whitelist, target = None):
window = RPCConfirmationWindow(source, rpc_operation, name_whitelist,
target)
return window.confirm_rpc()

444
qubespolicy/tests/gtkhelpers.py Executable file
View File

@ -0,0 +1,444 @@
#!/usr/bin/python
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2017 boring-stuff <boring-stuff@users.noreply.github.com>
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
import unittest, time
from core.gtkhelpers import VMListModeler, GtkOneTimerHelper, FocusStealingHelper, Gtk
class VMListModelerMock(VMListModeler):
def __init__(self):
VMListModeler.__init__(self)
def _get_list(self):
return [ MockVm(0, "dom0", "black"),
MockVm(2, "test-red1", "red"),
MockVm(4, "test-red2", "red"),
MockVm(7, "test-red3", "red"),
MockVm(8, "test-source", "green"),
MockVm(10, "test-target", "orange"),
MockVm(15, "test-disp6", "red", True) ]
@staticmethod
def get_name_whitelist():
return [ "test-red1", "test-red2", "test-red3",
"test-target", "test-disp6" ]
class MockVmLabel:
def __init__(self, index, color, name, dispvm = False):
self.index = index
self.color = color
self.name = name
self.dispvm = dispvm
self.icon = "gnome-foot"
class MockVm:
def __init__(self, qid, name, color, dispvm = False):
self.qid = qid
self.name = name
self.label = MockVmLabel(qid, 0x000000, color, dispvm)
class MockComboEntry:
def __init__(self, text):
self._text = text
def get_active_id(self):
return self._text
def get_text(self):
return self._text
class GtkTestCase(unittest.TestCase):
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
self._smallest_wait = 0.01
def flush_gtk_events(self, wait_seconds = 0):
start = time.time()
iterations = 0
remaining_wait = wait_seconds
time_length = 0
if wait_seconds < 0:
raise ValueError("Only non-negative intervals are allowed.")
while remaining_wait >= 0:
while Gtk.events_pending():
Gtk.main_iteration_do(blocking = False)
iterations += 1
time_length = time.time() - start
remaining_wait = wait_seconds - time_length
if (remaining_wait > 0):
time.sleep(self._smallest_wait)
return (iterations, time_length)
class VMListModelerTest(VMListModelerMock, unittest.TestCase):
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
VMListModelerMock.__init__(self)
def test_entries_gets_loaded(self):
self.assertIsNotNone(self._entries)
def test_valid_qube_name(self):
self.apply_model(Gtk.ComboBox())
for name in [ "test-red1", "test-red2", "test-red3",
"test-target", "test-disp6" ]:
mock = MockComboEntry(name)
self.assertEquals(name, self._get_valid_qube_name(mock, mock, []))
self.assertEquals(name, self._get_valid_qube_name(None, mock, []))
self.assertEquals(name, self._get_valid_qube_name(mock, None, []))
self.assertIsNone(self._get_valid_qube_name(None, None, []))
def test_valid_qube_name_exceptions(self):
list_exc = ["test-disp6", "test-red2"]
self.apply_model(Gtk.ComboBox(),
[VMListModeler.NameBlacklistFilter([ list_exc[0], list_exc[1] ]) ])
for name in list_exc:
mock = MockComboEntry(name)
self.assertIsNone(self._get_valid_qube_name(mock, mock, list_exc))
self.assertIsNone(self._get_valid_qube_name(None, mock, list_exc))
self.assertIsNone(self._get_valid_qube_name(mock, None, list_exc))
def test_invalid_qube_name(self):
self.apply_model(Gtk.ComboBox())
for name in [ "test-nonexistant", None, "", 1 ]:
mock = MockComboEntry(name)
self.assertIsNone(self._get_valid_qube_name(mock, mock, []))
self.assertIsNone(self._get_valid_qube_name(None, mock, []))
self.assertIsNone(self._get_valid_qube_name(mock, None, []))
def test_apply_model(self):
new_object = Gtk.ComboBox()
self.assertIsNone(new_object.get_model())
self.apply_model(new_object)
self.assertIsNotNone(new_object.get_model())
def test_apply_model_with_entry(self):
new_object = Gtk.ComboBox.new_with_entry()
self.assertIsNone(new_object.get_model())
self.apply_model(new_object)
self.assertIsNotNone(new_object.get_model())
def test_apply_model_only_combobox(self):
invalid_types = [ 1, "One", u'1', {'1': "one"}, VMListModelerMock()]
for invalid_type in invalid_types:
with self.assertRaises(TypeError):
self.apply_model(invalid_type)
def test_apply_model_blacklist(self):
combo = Gtk.ComboBox()
self.apply_model(combo)
self.assertEquals(7, len(combo.get_model()))
self.apply_model(combo, [ VMListModeler.NameBlacklistFilter([
self._entries.keys()[0] ]) ])
self.assertEquals(6, len(combo.get_model()))
self.apply_model(combo, [ VMListModeler.NameBlacklistFilter([
self._entries.keys()[0] ]),
VMListModeler.NameBlacklistFilter([
self._entries.keys()[1] ]) ])
self.assertEquals(5, len(combo.get_model()))
self.apply_model(combo, [ VMListModeler.NameBlacklistFilter([
self._entries.keys()[0],
self._entries.keys()[1] ]) ])
self.assertEquals(5, len(combo.get_model()))
def test_apply_model_whitelist(self):
combo = Gtk.ComboBox()
self.apply_model(combo)
self.assertEquals(7, len(combo.get_model()))
self.apply_model(combo, [ VMListModeler.NameWhitelistFilter([
self._entries.keys()[0] ]) ])
self.assertEquals(1, len(combo.get_model()))
self.apply_model(combo, [ VMListModeler.NameWhitelistFilter([
self._entries.keys()[0],
self._entries.keys()[1] ]) ])
self.assertEquals(2, len(combo.get_model()))
def test_apply_model_multiple_filters(self):
combo = Gtk.ComboBox()
self.apply_model(combo)
self.assertEquals(7, len(combo.get_model()))
self.apply_model(combo, [ VMListModeler.NameWhitelistFilter([
self._entries.keys()[0],
self._entries.keys()[1],
self._entries.keys()[2],
self._entries.keys()[3],
self._entries.keys()[4] ]),
VMListModeler.NameBlacklistFilter([
self._entries.keys()[0],
self._entries.keys()[1] ]) ])
self.assertEquals(3, len(combo.get_model()))
def test_apply_icon(self):
new_object = Gtk.Entry()
self.assertIsNone(
new_object.get_icon_pixbuf(Gtk.EntryIconPosition.PRIMARY))
self.apply_icon(new_object, "test-disp6")
self.assertIsNotNone(
new_object.get_icon_pixbuf(Gtk.EntryIconPosition.PRIMARY))
def test_apply_icon_only_entry(self):
invalid_types = [ 1, "One", u'1', {'1': "one"}, Gtk.ComboBox()]
for invalid_type in invalid_types:
with self.assertRaises(TypeError):
self.apply_icon(invalid_type, "test-disp6")
def test_apply_icon_only_existing(self):
new_object = Gtk.Entry()
for name in [ "test-red1", "test-red2", "test-red3",
"test-target", "test-disp6" ]:
self.apply_icon(new_object, name)
for name in [ "test-nonexistant", None, "", 1 ]:
with self.assertRaises(ValueError):
self.apply_icon(new_object, name)
class GtkOneTimerHelperTest(GtkOneTimerHelper, GtkTestCase):
def __init__(self, *args, **kwargs):
GtkTestCase.__init__(self, *args, **kwargs)
self._test_time = 0.1
GtkOneTimerHelper.__init__(self, self._test_time)
self._run_timers = []
def _timer_run(self, timer_id):
self._run_timers.append(timer_id)
def test_nothing_runs_automatically(self):
self.flush_gtk_events(self._test_time*2)
self.assertEquals([], self._run_timers)
self.assertEquals(0, self._current_timer_id)
self.assertFalse(self._timer_has_completed())
def test_schedule_one_task(self):
self._timer_schedule()
self.flush_gtk_events(self._test_time*2)
self.assertEquals([1], self._run_timers)
self.assertEquals(1, self._current_timer_id)
self.assertTrue(self._timer_has_completed())
def test_invalidate_completed(self):
self._timer_schedule()
self.flush_gtk_events(self._test_time*2)
self.assertEquals([1], self._run_timers)
self.assertEquals(1, self._current_timer_id)
self.assertTrue(self._timer_has_completed())
self._invalidate_timer_completed()
self.assertFalse(self._timer_has_completed())
def test_schedule_and_cancel_one_task(self):
self._timer_schedule()
self._invalidate_current_timer()
self.flush_gtk_events(self._test_time*2)
self.assertEquals([], self._run_timers)
self.assertEquals(2, self._current_timer_id)
self.assertFalse(self._timer_has_completed())
def test_two_tasks(self):
self._timer_schedule()
self.flush_gtk_events(self._test_time/4)
self._timer_schedule()
self.flush_gtk_events(self._test_time*2)
self.assertEquals([2], self._run_timers)
self.assertEquals(2, self._current_timer_id)
self.assertTrue(self._timer_has_completed())
def test_more_tasks(self):
num = 0
for num in range(1,10):
self._timer_schedule()
self.flush_gtk_events(self._test_time/4)
self.flush_gtk_events(self._test_time*1.75)
self.assertEquals([num], self._run_timers)
self.assertEquals(num, self._current_timer_id)
self.assertTrue(self._timer_has_completed())
def test_more_tasks_cancel(self):
num = 0
for num in range(1,10):
self._timer_schedule()
self.flush_gtk_events(self._test_time/4)
self._invalidate_current_timer()
self.flush_gtk_events(self._test_time*1.75)
self.assertEquals([], self._run_timers)
self.assertEquals(num+1, self._current_timer_id)
self.assertFalse(self._timer_has_completed())
def test_subsequent_tasks(self):
self._timer_schedule() #1
self.flush_gtk_events(self._test_time*2)
self.assertEquals([1], self._run_timers)
self.assertEquals(1, self._current_timer_id)
self.assertTrue(self._timer_has_completed())
self._timer_schedule() #2
self.flush_gtk_events(self._test_time*2)
self.assertEquals([1,2], self._run_timers)
self.assertEquals(2, self._current_timer_id)
self.assertTrue(self._timer_has_completed())
self._invalidate_timer_completed()
self._timer_schedule() #3
self._invalidate_current_timer() #4
self.flush_gtk_events(self._test_time*2)
self.assertEquals([1,2], self._run_timers)
self.assertEquals(4, self._current_timer_id)
self.assertFalse(self._timer_has_completed())
self._timer_schedule() #5
self.flush_gtk_events(self._test_time*2)
self.assertEquals([1,2,5], self._run_timers)
self.assertEquals(5, self._current_timer_id)
self.assertTrue(self._timer_has_completed())
class FocusStealingHelperMock(FocusStealingHelper):
def simulate_focus(self):
self._window_changed_focus(True)
class FocusStealingHelperTest(FocusStealingHelperMock, GtkTestCase):
def __init__(self, *args, **kwargs):
GtkTestCase.__init__(self, *args, **kwargs)
self._test_time = 0.1
self._test_button = Gtk.Button()
self._test_window = Gtk.Window()
FocusStealingHelperMock.__init__(self, self._test_window,
self._test_button, self._test_time)
def test_nothing_runs_automatically(self):
self.assertFalse(self.can_perform_action())
self.flush_gtk_events(self._test_time*2)
self.assertFalse(self.can_perform_action())
self.assertFalse(self._test_button.get_sensitive())
def test_nothing_runs_automatically_with_request(self):
self.request_sensitivity(True)
self.assertFalse(self.can_perform_action())
self.flush_gtk_events(self._test_time*2)
self.assertFalse(self.can_perform_action())
self.assertFalse(self._test_button.get_sensitive())
def _simulate_focus(self, focused):
self._window_changed_focus(focused)
def test_focus_with_request(self):
self.request_sensitivity(True)
self._simulate_focus(True)
self.flush_gtk_events(self._test_time*2)
self.assertTrue(self.can_perform_action())
self.assertTrue(self._test_button.get_sensitive())
def test_focus_with_late_request(self):
self._simulate_focus(True)
self.flush_gtk_events(self._test_time*2)
self.assertTrue(self.can_perform_action())
self.assertFalse(self._test_button.get_sensitive())
self.request_sensitivity(True)
self.assertTrue(self._test_button.get_sensitive())
def test_immediate_defocus(self):
self.request_sensitivity(True)
self._simulate_focus(True)
self._simulate_focus(False)
self.flush_gtk_events(self._test_time*2)
self.assertFalse(self.can_perform_action())
self.assertFalse(self._test_button.get_sensitive())
def test_focus_then_unfocus(self):
self.request_sensitivity(True)
self._simulate_focus(True)
self.flush_gtk_events(self._test_time*2)
self.assertTrue(self.can_perform_action())
self.assertTrue(self._test_button.get_sensitive())
self._simulate_focus(False)
self.assertFalse(self.can_perform_action())
self.assertFalse(self._test_button.get_sensitive())
def test_focus_cycle(self):
self.request_sensitivity(True)
self._simulate_focus(True)
self.flush_gtk_events(self._test_time*2)
self.assertTrue(self.can_perform_action())
self.assertTrue(self._test_button.get_sensitive())
self._simulate_focus(False)
self.assertFalse(self.can_perform_action())
self.assertFalse(self._test_button.get_sensitive())
self._simulate_focus(True)
self.assertFalse(self.can_perform_action())
self.assertFalse(self._test_button.get_sensitive())
self.flush_gtk_events(self._test_time*2)
self.assertTrue(self.can_perform_action())
self.assertTrue(self._test_button.get_sensitive())
self.request_sensitivity(False)
self.assertTrue(self.can_perform_action())
self.assertFalse(self._test_button.get_sensitive())
self._simulate_focus(False)
self.assertFalse(self.can_perform_action())
self._simulate_focus(True)
self.assertFalse(self.can_perform_action())
self.assertFalse(self._test_button.get_sensitive())
self.flush_gtk_events(self._test_time*2)
self.assertTrue(self.can_perform_action())
self.assertFalse(self._test_button.get_sensitive())
if __name__=='__main__':
unittest.main()

View File

@ -0,0 +1,334 @@
#!/usr/bin/python
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2017 boring-stuff <boring-stuff@users.noreply.github.com>
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
import unittest, sys
from core.rpcconfirmation import RPCConfirmationWindow
from gtkhelpers import VMListModelerMock, GtkTestCase, FocusStealingHelperMock
class MockRPCConfirmationWindow(RPCConfirmationWindow):
def _new_VM_list_modeler(self):
return VMListModelerMock()
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,
name_whitelist = VMListModelerMock.get_name_whitelist(),
target = None, focus_stealing_seconds = 1):
self._focus_stealing_seconds = focus_stealing_seconds
RPCConfirmationWindow.__init__(self, source, rpc_operation,
name_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 != None:
domain_name = model.get_value(model_iter, 1)
domains = 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",
name_whitelist = VMListModelerMock.get_name_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,
name_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>&lt;script&gt;</b>",
self._escape_and_format_rpc_text("inject.<script>"))
self.assertEquals("&lt;script&gt;.<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_qid)
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 BaseException:
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_qid)
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_qid)
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_qid)
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_qid)
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",
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", "test-disp6", "test-source",
"dom0"], ["test-red1", "test-red2", "test-red3",
"test-target", "test-disp6", "test-source",
"dom0"])
def _assert_whitelist(self, whitelist, expected):
rpcWindow = MockRPCConfirmationWindow("test-source", "test.Operation",
whitelist)
domains = rpcWindow.get_shown_domains()
for domain in expected:
self.assertTrue(domain in domains)
self.assertEquals(len(expected), len(domains))
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",
VMListModelerMock.get_name_whitelist(),
"test-red1").confirm_rpc()
elif test:
unittest.main(argv = [sys.argv[0]])

56
qubespolicy/utils.py Normal file
View File

@ -0,0 +1,56 @@
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2017 boring-stuff <boring-stuff@users.noreply.github.com>
#
# 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 <http://www.gnu.org/licenses/>.
def _sanitize_char(input_char, extra_allowed_characters):
input_char_ord = ord(input_char)
if (input_char_ord >= ord('a') and input_char_ord <= ord('z')) \
or (input_char_ord >= ord('A') and input_char_ord <= ord('Z')) \
or (input_char_ord >= ord('0') and input_char_ord <= ord('9')) \
or (input_char in ['$', '_', '-', '.']) \
or (extra_allowed_characters != None
and input_char in extra_allowed_characters):
result = input_char
else:
result = '_'
return result
# This function needs to be synchronized with qrexec-daemon.c's sanitize_name()
# from the qubes-core-admin-linux repository.
#
# See https://github.com/QubesOS/qubes-core-admin-linux/blob/4f0878ccbf8a95f8264b54d2b6f4dc433ca0793a/qrexec/qrexec-daemon.c#L627-L646
#
def _sanitize_name(input_string, extra_allowed_characters, assert_sanitized):
result = ''.join(_sanitize_char(character, extra_allowed_characters) \
for character in input_string)
if assert_sanitized:
assert input_string == result, \
'Input string was expected to be sanitized, but was not.'
else:
return result
def sanitize_domain_name(input_string, assert_sanitized = False):
return _sanitize_name(input_string, None, assert_sanitized)
def sanitize_service_name(input_string, assert_sanitized = False):
return _sanitize_name(input_string, {'+'}, assert_sanitized)