def test_Client_on_file_download_Reply(homedir, config, mocker):
    """
    If the handler is passed a reply, check the download_reply
    function is the one called against the API.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    source = factory.Source()
    journalist = db.User('Testy mcTestface')
    reply = db.Reply('reply-uuid', journalist, source, 'my-reply.gpg',
                     123)  # Not a sdclientapi.Submission
    cl.call_api = mocker.MagicMock()
    cl.api = mocker.MagicMock()
    reply_sdk_object = mocker.MagicMock()
    mock_reply = mocker.patch('sdclientapi.Reply')
    mock_reply.return_value = reply_sdk_object
    cl.on_file_download(source, reply)
    cl.call_api.assert_called_once_with(cl.api.download_reply,
                                        cl.on_file_downloaded,
                                        cl.on_download_timeout,
                                        reply_sdk_object,
                                        cl.data_dir,
                                        current_object=reply)
def test_Client_timeout_cleanup_with_current_object(homedir, config, mocker):
    """
    Ensure that cleanup is performed if an API call times out.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    mock_thread = mocker.MagicMock()
    mock_runner = mocker.MagicMock()
    mock_runner.current_object = 'current_object'
    mock_timer = mocker.MagicMock()
    cl.api_threads = {
        'thread_uuid': {
            'thread': mock_thread,
            'runner': mock_runner,
            'timer': mock_timer,
        }
    }
    cl.clean_thread = mocker.MagicMock()
    mock_user_callback = mocker.MagicMock()
    cl.timeout_cleanup('thread_uuid', mock_user_callback)
    assert mock_runner.i_timed_out is True
    cl.clean_thread.assert_called_once_with('thread_uuid')
    mock_user_callback.assert_called_once_with(current_object='current_object')
def test_Client_completed_api_call_without_current_object(safe_tmpdir):
    """
    Ensure that cleanup is performed if an API call returns in the expected
    time.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    mock_thread = mock.MagicMock()
    mock_runner = mock.MagicMock()
    mock_runner.result = 'result'
    mock_runner.current_object = None
    mock_timer = mock.MagicMock()
    cl.api_threads = {
        'thread_uuid': {
            'thread': mock_thread,
            'runner': mock_runner,
            'timer': mock_timer,
        }
    }
    cl.clean_thread = mock.MagicMock()
    mock_user_callback = mock.MagicMock()
    cl.completed_api_call('thread_uuid', mock_user_callback)
    cl.clean_thread.assert_called_once_with('thread_uuid')
    mock_user_callback.assert_called_once_with('result')
    mock_timer.stop.assert_called_once_with()
예제 #4
0
def test_Client_on_file_click_Reply(safe_tmpdir):
    """
    If the handler is passed a reply, check the download_reply
    function is the one called against the API.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    source = models.Source('source-uuid', 'testy-mctestface', False,
                           'mah pub key', 1, False, datetime.now())
    journalist = models.User('Testy mcTestface')
    reply = models.Reply('reply-uuid', journalist, source, 'my-reply.gpg',
                         123)  # Not a sdclientapi.Submission
    cl.call_api = mock.MagicMock()
    cl.api = mock.MagicMock()
    reply_sdk_object = mock.MagicMock()
    with mock.patch('sdclientapi.Reply') as mock_reply:
        mock_reply.return_value = reply_sdk_object
        cl.on_file_click(source, reply)
    cl.call_api.assert_called_once_with(cl.api.download_reply,
                                        cl.on_file_download,
                                        cl.on_download_timeout,
                                        reply_sdk_object,
                                        cl.data_dir,
                                        current_object=reply)
예제 #5
0
def test_Client_on_download_timeout(safe_tmpdir):
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.update_sources = mock.MagicMock()
    cl.api_runner = mock.MagicMock()
    current_object = mock.MagicMock()
    test_filename = "my-file-location-msg.gpg"
    cl.api_runner.result = ("", test_filename)
    cl.call_reset = mock.MagicMock()
    cl.set_status = mock.MagicMock()
    cl.on_download_timeout(current_object)
    cl.set_status.assert_called_once_with(
        "Connection to server timed out, please try again.")
def test_Client_completed_api_call_with_current_object(homedir, config,
                                                       mocker):
    """
    Ensure that cleanup is performed if an API call returns in the expected
    time.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    mock_thread = mocker.MagicMock()
    mock_runner = mocker.MagicMock()
    mock_runner.result = 'result'
    mock_runner.current_object = 'current_object'
    mock_timer = mocker.MagicMock()
    cl.api_threads = {
        'thread_uuid': {
            'thread': mock_thread,
            'runner': mock_runner,
            'timer': mock_timer,
        }
    }
    cl.clean_thread = mocker.MagicMock()
    mock_user_callback = mocker.MagicMock()
    cl.completed_api_call('thread_uuid', mock_user_callback)
    cl.clean_thread.assert_called_once_with('thread_uuid')
    mock_user_callback.assert_called_once_with('result',
                                               current_object='current_object')
    mock_timer.stop.assert_called_once_with()
예제 #7
0
def start_app(args, qt_args) -> None:
    """
    Create all the top-level assets for the application, set things up and
    run the application. Specific tasks include:

    - set up logging.
    - create an application object.
    - create a window for the app.
    - create an API connection to the SecureDrop proxy.
    - create a SqlAlchemy session to local storage.
    - configure the client (logic) object.
    - ensure the application is setup in the default safe starting state.
    """
    init(args.sdc_home)
    configure_logging(args.sdc_home)
    logging.info('Starting SecureDrop Client {}'.format(__version__))

    app = QApplication(qt_args)
    app.setApplicationName('SecureDrop Client')
    app.setDesktopFileName('org.freedomofthepress.securedrop.client')
    app.setApplicationVersion(__version__)
    app.setAttribute(Qt.AA_UseHighDpiPixmaps)

    gui = Window()
    app.setWindowIcon(load_icon(gui.icon))
    app.setStyleSheet(load_css('sdclient.css'))

    engine = make_engine(args.sdc_home)
    Session = sessionmaker(bind=engine)
    session = Session()

    client = Client("http://localhost:8081/", gui, session, args.sdc_home)
    client.setup()

    configure_signal_handlers(app)
    timer = QTimer()
    timer.start(500)
    timer.timeout.connect(lambda: None)

    sys.exit(app.exec_())
def test_Client_init(homedir, config, mocker):
    """
    The passed in gui, app and session instances are correctly referenced and,
    where appropriate, have a reference back to the client.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost/', mock_gui, mock_session, homedir)
    assert cl.hostname == 'http://localhost/'
    assert cl.gui == mock_gui
    assert cl.session == mock_session
    assert cl.api_threads == {}
def test_Client_on_synced_remove_stale_sources(homedir, config, mocker):
    """
    On an API sync, if a source no longer exists, remove it from the GUI.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_source_id = 'abc123'
    mock_conv_wrapper = 'mock'

    gui = mocker.Mock()
    gui.conversations = {mock_source_id: mock_conv_wrapper}

    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', gui, mock_session, homedir)

    mock_source = mocker.Mock()
    mock_source.uuid = mock_source_id

    # not that the first item does *not* have the mock_source
    api_res = ([], mocker.MagicMock(), mocker.MagicMock())
    cl.on_synced(api_res)

    # check that the uuid is not longer in the dict
    assert mock_source_id not in gui.conversations
def test_Client_on_download_timeout(homedir, config, mocker):
    '''
    Using the `config` fixture to ensure the config is written to disk.
    '''
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.update_sources = mocker.MagicMock()
    cl.api_runner = mocker.MagicMock()
    current_object = mocker.MagicMock()
    test_filename = "my-file-location-msg.gpg"
    cl.api_runner.result = ("", test_filename)
    cl.call_reset = mocker.MagicMock()
    cl.set_status = mocker.MagicMock()
    cl.on_download_timeout(current_object)
    cl.set_status.assert_called_once_with(
        "The connection to the SecureDrop server timed out. Please try again.")
예제 #11
0
def test_Client_on_file_downloaded_api_failure(safe_tmpdir):
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.update_sources = mock.MagicMock()
    cl.api_runner = mock.MagicMock()
    test_filename = "my-file-location-msg.gpg"
    cl.api_runner.result = ("", test_filename)
    cl.call_reset = mock.MagicMock()
    cl.set_status = mock.MagicMock()
    result_data = Exception('error message')
    submission_db_object = mock.MagicMock()
    submission_db_object.uuid = 'myuuid'
    submission_db_object.filename = 'filename'
    cl.on_file_downloaded(result_data, current_object=submission_db_object)
    cl.set_status.assert_called_once_with(
        "The file download failed. Please try again.")
def test_Client_on_synced_with_result(homedir, config, mocker):
    """
    If there's a result to syncing, then update local storage.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.update_sources = mocker.MagicMock()
    cl.api_runner = mocker.MagicMock()
    cl.gpg = mocker.MagicMock()

    result_data = ('sources', 'submissions', 'replies')

    cl.update_sources = mocker.MagicMock()
    cl.api_runner = mocker.MagicMock()

    mock_source = mocker.MagicMock()
    mock_source.key = {
        'type': 'PGP',
        'public': PUB_KEY,
    }

    mock_sources = [mock_source]

    result_data = (mock_sources, 'submissions', 'replies')

    cl.call_reset = mocker.MagicMock()
    mock_storage = mocker.patch('securedrop_client.logic.storage')
    cl.on_synced(result_data)
    mock_storage.update_local_storage. \
        assert_called_once_with(mock_session, mock_sources, "submissions",
                                "replies",
                                os.path.join(homedir, 'data'))

    cl.update_sources.assert_called_once_with()
예제 #13
0
def test_Client_completed_api_call_with_current_object(safe_tmpdir):
    """
    Ensure that cleanup is performed if an API call returns in the expected
    time.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.timer = mock.MagicMock()
    cl.api_thread = mock.MagicMock()
    cl.api_runner = mock.MagicMock()
    cl.api_runner.current_object = mock.MagicMock()
    cl.call_reset = mock.MagicMock()
    mock_user_callback = mock.MagicMock()
    cl.result = mock.MagicMock()

    cl.completed_api_call(mock_user_callback)

    cl.call_reset.assert_called_once_with()
    mock_user_callback.call_count == 1
    cl.timer.stop.assert_called_once_with()
예제 #14
0
def test_Client_on_authenticate_ok(safe_tmpdir):
    """
    Ensure the client syncs when the user successfully logs in.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.sync_api = mock.MagicMock()
    cl.api = mock.MagicMock()
    cl.start_message_thread = mock.MagicMock()
    cl.start_reply_thread = mock.MagicMock()
    cl.api.username = '******'
    cl.on_authenticate(True)
    cl.sync_api.assert_called_once_with()
    cl.start_message_thread.assert_called_once_with()
    cl.gui.set_logged_in_as.assert_called_once_with('test')
    # Error status bar should be cleared
    cl.gui.update_error_status.assert_called_once_with("")
예제 #15
0
def test_Client_sync_api_not_authenticated(safe_tmpdir):
    """
    If the API isn't authenticated, don't sync.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.authenticated = mock.MagicMock(return_value=False)
    cl.call_api = mock.MagicMock()
    cl.sync_api()
    assert cl.call_api.call_count == 0
예제 #16
0
def test_Client_update_sync(safe_tmpdir):
    """
    Cause the UI to update with the result of self.last_sync().
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.last_sync = mock.MagicMock()
    cl.update_sync()
    assert cl.last_sync.call_count == 1
    cl.gui.show_sync.assert_called_once_with(cl.last_sync())
def test_Client_update_sync(homedir, config, mocker):
    """
    Cause the UI to update with the result of self.last_sync().
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.last_sync = mocker.MagicMock()
    cl.update_sync()
    assert cl.last_sync.call_count == 1
    cl.gui.show_sync.assert_called_once_with(cl.last_sync())
def test_Client_sync_api_not_authenticated(homedir, config, mocker):
    """
    If the API isn't authenticated, don't sync.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.authenticated = mocker.MagicMock(return_value=False)
    cl.call_api = mocker.MagicMock()
    cl.sync_api()
    assert cl.call_api.call_count == 0
예제 #19
0
def test_Client_sync_api(safe_tmpdir):
    """
    Sync the API is authenticated.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.authenticated = mock.MagicMock(return_value=True)
    cl.call_api = mock.MagicMock()
    cl.sync_api()
    cl.call_api.assert_called_once_with(storage.get_remote_data, cl.on_synced,
                                        cl.on_sync_timeout, cl.api)
예제 #20
0
def test_Client_call_reset_no_thread():
    """
    The client will ignore an attempt to reset an API call is there's no such
    call "in flight".
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session)
    cl.finish_api_call = mock.MagicMock()
    cl.api_thread = None
    cl.call_reset()
    assert cl.finish_api_call.emit.call_count == 0
예제 #21
0
def test_Client_clean_thread_no_thread(safe_tmpdir):
    """
    The client will ignore an attempt to reset an API call if there's no such
    call "in flight".
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.finish_api_call = mock.MagicMock()
    cl.api_threads = {'a': 'b'}
    cl.clean_thread('foo')
    assert len(cl.api_threads) == 1
def test_Client_on_authenticate_ok(homedir, config, mocker):
    """
    Ensure the client syncs when the user successfully logs in.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.sync_api = mocker.MagicMock()
    cl.api = mocker.MagicMock()
    cl.start_message_thread = mocker.MagicMock()
    cl.start_reply_thread = mocker.MagicMock()
    cl.api.username = '******'
    cl.on_authenticate(True)
    cl.sync_api.assert_called_once_with()
    cl.start_message_thread.assert_called_once_with()
    cl.gui.set_logged_in_as.assert_called_once_with('test')
    # Error status bar should be cleared
    cl.gui.update_error_status.assert_called_once_with("")
def test_Client_start_reply_thread_if_already_running(homedir, config, mocker):
    """
    Ensure that when starting the reply thread, we don't start another thread
    if it's already running. Instead, we just authenticate the existing thread.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.api = 'api object'
    cl.reply_sync = mocker.MagicMock()
    cl.reply_thread = mocker.MagicMock()
    cl.reply_thread.api = None
    cl.start_reply_thread()
    cl.reply_sync.api = cl.api
    cl.reply_thread.start.assert_not_called()
def test_Client_clean_thread_no_thread(homedir, config, mocker):
    """
    The client will ignore an attempt to reset an API call if there's no such
    call "in flight".
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.finish_api_call = mocker.MagicMock()
    cl.api_threads = {'a': 'b'}
    cl.clean_thread('foo')
    assert len(cl.api_threads) == 1
예제 #25
0
def test_Client_update_star_not_logged_in(safe_tmpdir):
    """
    Ensure that starring/unstarring a source when not logged in calls
    the method that displays an error status in the left sidebar.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    source_db_object = mock.MagicMock()
    cl.on_action_requiring_login = mock.MagicMock()
    cl.api = None
    cl.update_star(source_db_object)
    cl.on_action_requiring_login.assert_called_with()
def test_Client_on_file_downloaded_api_failure(homedir, config, mocker):
    '''
    Using the `config` fixture to ensure the config is written to disk.
    '''
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.update_sources = mocker.MagicMock()
    cl.api_runner = mocker.MagicMock()
    test_filename = "my-file-location-msg.gpg"
    cl.api_runner.result = ("", test_filename)
    cl.call_reset = mocker.MagicMock()
    cl.set_status = mocker.MagicMock()
    result_data = Exception('error message')
    submission_db_object = mocker.MagicMock()
    submission_db_object.uuid = 'myuuid'
    submission_db_object.filename = 'filename'
    cl.on_file_downloaded(result_data, current_object=submission_db_object)
    cl.set_status.assert_called_once_with(
        "The file download failed. Please try again.")
def test_Client_sync_api(homedir, config, mocker):
    """
    Sync the API is authenticated.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.authenticated = mocker.MagicMock(return_value=True)
    cl.call_api = mocker.MagicMock()
    cl.sync_api()
    cl.call_api.assert_called_once_with(storage.get_remote_data, cl.on_synced,
                                        cl.on_sync_timeout, cl.api)
예제 #28
0
def test_Client_on_authenticate_ok():
    """
    Ensure the client syncs when the user successfully logs in.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session)
    cl.sync_api = mock.MagicMock()
    cl.api = mock.MagicMock()
    cl.api.username = '******'
    cl.on_authenticate(True)
    cl.sync_api.assert_called_once_with()
    cl.gui.set_logged_in_as.assert_called_once_with('test')
예제 #29
0
def test_Client_on_synced_with_result(safe_tmpdir):
    """
    If there's a result to syncing, then update local storage.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.update_sources = mock.MagicMock()
    cl.api_runner = mock.MagicMock()
    result_data = ('sources', 'submissions', 'replies')
    cl.call_reset = mock.MagicMock()
    with mock.patch('securedrop_client.logic.storage') as mock_storage:
        cl.on_synced(result_data)
        mock_storage.update_local_storage.\
            assert_called_once_with(mock_session, "sources", "submissions",
                                    "replies")
    cl.update_sources.assert_called_once_with()
예제 #30
0
def test_Client_on_synced_with_result():
    """
    If there's a result to syncing, then update local storage.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session)
    cl.update_sources = mock.MagicMock()
    cl.api_runner = mock.MagicMock()
    cl.api_runner.result = (1, 2, 3, )
    cl.call_reset = mock.MagicMock()
    with mock.patch('securedrop_client.logic.storage') as mock_storage:
        cl.on_synced(True)
        cl.call_reset.assert_called_once_with()
        mock_storage.update_local_storage.assert_called_once_with(mock_session,
                                                                  1, 2, 3)
    cl.update_sources.assert_called_once_with()