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()