212 lines
8.0 KiB
Python
212 lines
8.0 KiB
Python
import sys
|
|
import types
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
import cthulhu as cthulhu_package
|
|
from cthulhu import cthulhu_state
|
|
|
|
stubCthulhu = types.ModuleType("cthulhu.cthulhu")
|
|
stubCthulhu.cthulhuApp = mock.Mock()
|
|
stubCthulhu.cthulhuApp.getPluginSystemManager.return_value = None
|
|
previousCthulhuModule = sys.modules.get("cthulhu.cthulhu")
|
|
sys.modules.setdefault("cthulhu.cthulhu", stubCthulhu)
|
|
|
|
from cthulhu import event_manager
|
|
from cthulhu import input_event_manager
|
|
from cthulhu.screen_lock_monitor import ScreenLockMonitor
|
|
|
|
if previousCthulhuModule is None:
|
|
sys.modules.pop("cthulhu.cthulhu", None)
|
|
if getattr(cthulhu_package, "cthulhu", None) is stubCthulhu:
|
|
delattr(cthulhu_package, "cthulhu")
|
|
else:
|
|
sys.modules["cthulhu.cthulhu"] = previousCthulhuModule
|
|
|
|
|
|
class ScreenLockMonitorTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.app = mock.Mock()
|
|
self.monitor = ScreenLockMonitor(self.app)
|
|
|
|
def test_name_appearance_enters_secure_lock_mode(self):
|
|
with mock.patch.object(self.monitor, "_is_trusted_owner", return_value=True):
|
|
self.monitor._on_name_appeared(mock.Mock(), self.monitor.BUS_NAME, ":1.42")
|
|
|
|
self.app.eventManager.set_screen_lock_active.assert_called_once_with(
|
|
True,
|
|
schedule_focus_resync=True,
|
|
)
|
|
self.assertTrue(self.monitor.is_active())
|
|
|
|
def test_untrusted_name_owner_does_not_enter_secure_lock_mode(self):
|
|
with mock.patch.object(self.monitor, "_is_trusted_owner", return_value=False):
|
|
self.monitor._on_name_appeared(mock.Mock(), self.monitor.BUS_NAME, ":1.42")
|
|
|
|
self.app.eventManager.set_screen_lock_active.assert_not_called()
|
|
self.assertFalse(self.monitor.is_active())
|
|
|
|
def test_name_disappearance_leaves_secure_lock_mode(self):
|
|
with mock.patch.object(self.monitor, "_is_trusted_owner", return_value=True):
|
|
self.monitor._on_name_appeared(mock.Mock(), self.monitor.BUS_NAME, ":1.42")
|
|
self.monitor._on_name_vanished(mock.Mock(), self.monitor.BUS_NAME)
|
|
|
|
self.assertEqual(
|
|
self.app.eventManager.set_screen_lock_active.call_args_list,
|
|
[
|
|
mock.call(True, schedule_focus_resync=True),
|
|
mock.call(False, schedule_focus_resync=True),
|
|
],
|
|
)
|
|
self.assertFalse(self.monitor.is_active())
|
|
|
|
def test_start_watches_i38lock_bus_name(self):
|
|
connection = mock.Mock()
|
|
with (
|
|
mock.patch("cthulhu.screen_lock_monitor.Gio.bus_get_sync", return_value=connection),
|
|
mock.patch(
|
|
"cthulhu.screen_lock_monitor.Gio.bus_watch_name_on_connection",
|
|
return_value=23,
|
|
) as watch_name,
|
|
):
|
|
self.monitor.start()
|
|
|
|
watch_name.assert_called_once_with(
|
|
connection,
|
|
self.monitor.BUS_NAME,
|
|
mock.ANY,
|
|
self.monitor._on_name_appeared,
|
|
self.monitor._on_name_vanished,
|
|
)
|
|
self.assertTrue(self.monitor.is_running())
|
|
|
|
def test_stop_releases_secure_lock_without_focus_resync(self):
|
|
with mock.patch.object(self.monitor, "_is_trusted_owner", return_value=True):
|
|
self.monitor._on_name_appeared(mock.Mock(), self.monitor.BUS_NAME, ":1.42")
|
|
|
|
with mock.patch("cthulhu.screen_lock_monitor.Gio.bus_unwatch_name"):
|
|
self.monitor.stop()
|
|
|
|
self.assertEqual(
|
|
self.app.eventManager.set_screen_lock_active.call_args_list,
|
|
[
|
|
mock.call(True, schedule_focus_resync=True),
|
|
mock.call(False, schedule_focus_resync=False),
|
|
],
|
|
)
|
|
self.assertFalse(self.monitor.is_active())
|
|
|
|
def test_i38lock_process_owner_is_trusted(self):
|
|
connection = mock.Mock()
|
|
result = mock.Mock()
|
|
result.unpack.return_value = (1234,)
|
|
connection.call_sync.return_value = result
|
|
|
|
with mock.patch("cthulhu.screen_lock_monitor.os.readlink", return_value="/usr/bin/i38lock"):
|
|
self.assertTrue(self.monitor._is_trusted_owner(connection, ":1.42"))
|
|
|
|
def test_non_i38lock_process_owner_is_not_trusted(self):
|
|
connection = mock.Mock()
|
|
result = mock.Mock()
|
|
result.unpack.return_value = (1234,)
|
|
connection.call_sync.return_value = result
|
|
|
|
with mock.patch("cthulhu.screen_lock_monitor.os.readlink", return_value="/usr/bin/python"):
|
|
self.assertFalse(self.monitor._is_trusted_owner(connection, ":1.42"))
|
|
|
|
|
|
class SecureScreenLockStateTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.original_screen_lock_active = cthulhu_state.screenLockActive
|
|
cthulhu_state.screenLockActive = False
|
|
|
|
def tearDown(self):
|
|
cthulhu_state.screenLockActive = self.original_screen_lock_active
|
|
|
|
def test_entering_secure_lock_clears_context_and_pauses_input(self):
|
|
app = mock.Mock()
|
|
active_script = mock.Mock()
|
|
app.scriptManager.get_active_script.return_value = active_script
|
|
manager = event_manager.EventManager(app, asyncMode=True)
|
|
keyboard_manager = mock.Mock()
|
|
focus_manager = mock.Mock()
|
|
|
|
with (
|
|
mock.patch.object(input_event_manager, "get_manager", return_value=keyboard_manager),
|
|
mock.patch("cthulhu.event_manager.focus_manager.get_manager", return_value=focus_manager),
|
|
mock.patch("cthulhu.event_manager.braille.clear") as clear_braille,
|
|
):
|
|
manager.set_screen_lock_active(True)
|
|
|
|
self.assertTrue(cthulhu_state.screenLockActive)
|
|
active_script.presentationInterrupt.assert_called_once_with()
|
|
app.scriptManager.set_active_script.assert_called_once_with(
|
|
None,
|
|
"I38Lock secure screen lock active",
|
|
)
|
|
focus_manager.clear_state.assert_called_once_with(
|
|
"I38Lock secure screen lock active"
|
|
)
|
|
keyboard_manager.set_secure_input_active.assert_called_once_with(
|
|
True,
|
|
"I38Lock secure screen lock active",
|
|
)
|
|
clear_braille.assert_called_once_with()
|
|
|
|
def test_object_events_are_dropped_without_inspecting_locked_content(self):
|
|
app = mock.Mock()
|
|
manager = event_manager.EventManager(app, asyncMode=True)
|
|
cthulhu_state.screenLockActive = True
|
|
event = mock.Mock()
|
|
|
|
with (
|
|
mock.patch.object(manager, "_addToQueue") as add_to_queue,
|
|
mock.patch("cthulhu.event_manager.AXObject.get_application") as get_application,
|
|
):
|
|
manager._enqueue(event)
|
|
|
|
add_to_queue.assert_not_called()
|
|
get_application.assert_not_called()
|
|
|
|
def test_unlock_resumes_input_and_schedules_focus_resync(self):
|
|
app = mock.Mock()
|
|
manager = event_manager.EventManager(app, asyncMode=True)
|
|
keyboard_manager = mock.Mock()
|
|
focus_manager = mock.Mock()
|
|
cthulhu_state.screenLockActive = True
|
|
|
|
with (
|
|
mock.patch.object(input_event_manager, "get_manager", return_value=keyboard_manager),
|
|
mock.patch("cthulhu.event_manager.focus_manager.get_manager", return_value=focus_manager),
|
|
mock.patch("cthulhu.event_manager.GLib.idle_add") as idle_add,
|
|
):
|
|
manager.set_screen_lock_active(False)
|
|
|
|
self.assertFalse(cthulhu_state.screenLockActive)
|
|
keyboard_manager.set_secure_input_active.assert_called_once_with(
|
|
False,
|
|
"I38Lock secure screen lock released",
|
|
)
|
|
idle_add.assert_called_once_with(manager._sync_focus_on_startup)
|
|
|
|
def test_secure_input_blocks_key_echo_before_event_creation(self):
|
|
manager = input_event_manager.InputEventManager()
|
|
manager.set_secure_input_active(True, "test lock")
|
|
|
|
with mock.patch("cthulhu.input_event_manager.input_event.KeyboardEvent") as event_class:
|
|
consumed = manager.process_keyboard_event(
|
|
mock.Mock(),
|
|
True,
|
|
25,
|
|
112,
|
|
0,
|
|
"p",
|
|
)
|
|
|
|
self.assertFalse(consumed)
|
|
event_class.assert_not_called()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|