From f763f735d23eaa983f93dc651071a4386418aa97 Mon Sep 17 00:00:00 2001 From: Erik Moeller Date: Sat, 25 Apr 2020 00:35:22 -0700 Subject: [PATCH 1/2] Clear clipboard after login screen --- securedrop_client/gui/main.py | 10 +++++++++- securedrop_client/logic.py | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/securedrop_client/gui/main.py b/securedrop_client/gui/main.py index 09630a7c2..0381450f2 100644 --- a/securedrop_client/gui/main.py +++ b/securedrop_client/gui/main.py @@ -23,7 +23,8 @@ from gettext import gettext as _ from typing import Dict, List, Optional # noqa: F401 -from PyQt5.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QVBoxLayout, QDesktopWidget +from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QHBoxLayout, \ + QVBoxLayout, QDesktopWidget from securedrop_client import __version__ from securedrop_client.db import Source, User @@ -197,3 +198,10 @@ def clear_error_status(self): Clear any message currently in the error status bar. """ self.top_pane.clear_error_status() + + def clear_clipboard(self): + """ + Purge any clipboard contents. + """ + cb = QApplication.clipboard() + cb.clear() diff --git a/securedrop_client/logic.py b/securedrop_client/logic.py index 80ed41b94..916979aca 100644 --- a/securedrop_client/logic.py +++ b/securedrop_client/logic.py @@ -437,6 +437,8 @@ def on_authenticate_success(self, result): self.api.journalist_first_name, self.api.journalist_last_name, self.session) + # Clear clipboard contents in case of previously pasted creds + self.gui.clear_clipboard() self.gui.show_main_window(user) self.update_sources() self.api_job_queue.start(self.api) @@ -456,6 +458,9 @@ def login_offline_mode(self): Allow user to view in offline mode without authentication. """ self.gui.hide_login() + # Clear clipboard contents in case of previously pasted creds (user + # may have attempted online mode login, then switched to offline) + self.gui.clear_clipboard() self.gui.show_main_window() storage.mark_all_pending_drafts_as_failed(self.session) self.is_authenticated = False From 58b8db400b50e21cc80c2a751cb0aa1e0b7dcb6d Mon Sep 17 00:00:00 2001 From: Erik Moeller Date: Thu, 30 Apr 2020 09:48:58 -0700 Subject: [PATCH 2/2] Add tests for clipboard-purge logic --- tests/gui/test_main.py | 12 ++++++++++++ tests/test_logic.py | 13 ++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/gui/test_main.py b/tests/gui/test_main.py index 83746a723..3ce89629f 100644 --- a/tests/gui/test_main.py +++ b/tests/gui/test_main.py @@ -283,3 +283,15 @@ def test_logout(mocker): w.left_pane.set_logged_out.assert_called_once_with() w.top_pane.set_logged_out.assert_called_once_with() + + +def test_clear_clipboard(mocker): + """ + Ensure we are clearing the system-level clipboard in the expected manner. + """ + mock_clipboard = mocker.MagicMock() + mocker.patch('securedrop_client.gui.main.QApplication.clipboard', + return_value=mock_clipboard) + w = Window() + w.clear_clipboard() + mock_clipboard.clear.assert_called_once_with() diff --git a/tests/test_logic.py b/tests/test_logic.py index f3d076f59..a29fdfbfc 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -11,6 +11,7 @@ from PyQt5.QtCore import Qt from sdclientapi import RequestTimeoutError, ServerConnectionError from tests import factory +from unittest.mock import call from securedrop_client import db from securedrop_client.logic import APICallRunner, Controller, TIME_BETWEEN_SHOWING_LAST_SYNC_MS @@ -157,8 +158,8 @@ def test_Controller_login(homedir, config, mocker, session_maker): def test_Controller_login_offline_mode(homedir, config, mocker): """ - Ensures user is not authenticated when logging in in offline mode and that the correct windows - are displayed. + Ensures user is not authenticated when logging in in offline mode, that the system + clipboard is cleared, and that the correct windows are subsequently displayed. """ co = Controller('http://localhost', mocker.MagicMock(), mocker.MagicMock(), homedir) co.call_api = mocker.MagicMock() @@ -172,7 +173,7 @@ def test_Controller_login_offline_mode(homedir, config, mocker): assert co.call_api.called is False assert co.is_authenticated is False - co.gui.show_main_window.assert_called_once_with() + co.gui.assert_has_calls([call.clear_clipboard(), call.show_main_window()]) co.gui.hide_login.assert_called_once_with() co.update_sources.assert_called_once_with() co.show_last_sync_timer.start.assert_called_once_with(TIME_BETWEEN_SHOWING_LAST_SYNC_MS) @@ -202,7 +203,9 @@ def test_Controller_on_authenticate_failure(homedir, config, mocker, session_mak def test_Controller_on_authenticate_success(homedir, config, mocker, session_maker, session): """ - Ensure the client syncs when the user successfully logs in. + Ensure the client syncs when the user successfully logs in, and that the + system clipboard is cleared prior to display of the main window. + Using the `config` fixture to ensure the config is written to disk. """ user = factory.User() @@ -221,7 +224,7 @@ def test_Controller_on_authenticate_success(homedir, config, mocker, session_mak co.resume_queues = mocker.MagicMock() co.on_authenticate_success(True) - + co.gui.assert_has_calls([call.clear_clipboard(), call.show_main_window(user)]) co.api_sync.start.assert_called_once_with(co.api) co.api_job_queue.start.assert_called_once_with(co.api) assert co.is_authenticated