Exemplo n.º 1
0
def test_ReplyDownloadJob_no_download_or_decrypt(mocker, homedir, session,
                                                 session_maker):
    """
    Test that an already-downloaded reply successfully decrypts.
    """
    reply_is_decrypted_false = factory.Reply(source=factory.Source(),
                                             is_downloaded=True,
                                             is_decrypted=False,
                                             content=None)
    reply_is_decrypted_none = factory.Reply(source=factory.Source(),
                                            is_downloaded=True,
                                            is_decrypted=None,
                                            content=None)
    session.add(reply_is_decrypted_false)
    session.add(reply_is_decrypted_none)
    session.commit()
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    job_1 = ReplyDownloadJob(reply_is_decrypted_false.uuid, homedir, gpg)
    job_2 = ReplyDownloadJob(reply_is_decrypted_none.uuid, homedir, gpg)
    mocker.patch.object(job_1.gpg, 'decrypt_submission_or_reply')
    mocker.patch.object(job_2.gpg, 'decrypt_submission_or_reply')
    api_client = mocker.MagicMock()
    api_client.default_request_timeout = mocker.MagicMock()
    path = os.path.join(homedir, 'data')
    api_client.download_submission = mocker.MagicMock(return_value=('', path))

    job_1.call_api(api_client, session)
    job_2.call_api(api_client, session)

    assert reply_is_decrypted_false.content is not None
    assert reply_is_decrypted_false.is_downloaded is True
    assert reply_is_decrypted_false.is_decrypted is True
    assert reply_is_decrypted_none.content is not None
    assert reply_is_decrypted_none.is_downloaded is True
    assert reply_is_decrypted_none.is_decrypted is True
Exemplo n.º 2
0
def test_find_new_replies(mocker, session):
    source = factory.Source()
    reply_not_downloaded = factory.Reply(source=source,
                                         is_downloaded=False,
                                         is_decrypted=None,
                                         content=None)
    reply_decrypt_not_attempted = factory.Reply(source=source,
                                                is_downloaded=True,
                                                is_decrypted=None,
                                                content=None)
    reply_decrypt_failed = factory.Reply(source=source,
                                         is_downloaded=True,
                                         is_decrypted=False,
                                         content=None)
    reply_decrypt_success = factory.Reply(source=source,
                                          is_downloaded=True,
                                          is_decrypted=True,
                                          content="teehee")
    session.add(source)
    session.add(reply_decrypt_not_attempted)
    session.add(reply_not_downloaded)
    session.add(reply_decrypt_failed)
    session.add(reply_decrypt_success)
    session.commit()

    replies = find_new_replies(session)
    assert len(replies) == 3

    for reply in replies:
        assert reply.is_downloaded is False or reply.is_decrypted is not True
Exemplo n.º 3
0
def main_window_no_key(mocker, homedir):
    # Setup
    app = QApplication([])
    gui = Window()
    app.setActiveWindow(gui)
    gui.show()
    controller = Controller("http://localhost", gui, mocker.MagicMock(), homedir, proxy=False)
    controller.qubes = False
    gui.setup(controller)

    # Create a source widget
    source_list = gui.main_view.source_list
    source = factory.Source(public_key=None)
    source_list.update([source])

    # Create a file widget, message widget, and reply widget
    mocker.patch("securedrop_client.gui.widgets.humanize_filesize", return_value="100")
    mocker.patch(
        "securedrop_client.gui.SecureQLabel.get_elided_text", return_value="1-yellow-doc.gz.gpg"
    )
    source.collection.append(
        [
            factory.File(source=source, filename="1-yellow-doc.gz.gpg"),
            factory.Message(source=source, filename="2-yellow-msg.gpg"),
            factory.Reply(source=source, filename="3-yellow-reply.gpg"),
        ]
    )
    source_list.setCurrentItem(source_list.item(0))
    gui.main_view.on_source_changed()

    yield gui

    # Teardown
    gui.login_dialog.close()
    app.exit()
Exemplo n.º 4
0
def test_ReplyDownloadJob_happiest_path(mocker, homedir, session,
                                        session_maker):
    """
    Test when a reply successfully downloads and decrypts. Use the `homedir` fixture to get a GPG
    keyring.
    """
    reply = factory.Reply(source=factory.Source(),
                          is_downloaded=False,
                          is_decrypted=None,
                          content=None)
    session.add(reply)
    session.commit()
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    job = ReplyDownloadJob(reply.uuid, homedir, gpg)
    mocker.patch.object(job.gpg, 'decrypt_submission_or_reply')
    api_client = mocker.MagicMock()
    api_client.default_request_timeout = mocker.MagicMock()
    data_dir = os.path.join(homedir, 'data')
    api_client.download_reply = mocker.MagicMock(return_value=('', data_dir))

    job.call_api(api_client, session)

    assert reply.content is not None
    assert reply.is_downloaded is True
    assert reply.is_decrypted is True
Exemplo n.º 5
0
def test_mark_reply_as_downloaded(mocker):
    session = mocker.MagicMock()
    reply = factory.Reply(source=factory.Source(), is_downloaded=False)
    session.query().filter_by().one.return_value = reply
    mark_as_downloaded(type(reply), "mock_uuid", session)
    assert reply.is_downloaded is True
    session.add.assert_called_once_with(reply)
    session.commit.assert_called_once_with()
Exemplo n.º 6
0
def test_get_reply(mocker, session):
    source = factory.Source()
    reply = factory.Reply(source=source)
    session.add(source)
    session.add(reply)

    result = get_reply(session, reply.uuid)

    assert result == reply
Exemplo n.º 7
0
def test_reply_with_download_error(session, download_error_codes):
    r = factory.Reply(is_decrypted=False, content=None)
    download_error = (session.query(DownloadError).filter_by(
        name=DownloadErrorCodes.DECRYPTION_ERROR.name).one())
    r.download_error = download_error
    session.commit()

    classname = r.__class__.__name__.lower()
    assert str(r) == f"cannot decrypt {classname}"
Exemplo n.º 8
0
def test_ReplySync_run_success(mocker, session, source):
    """Test when a reply successfully downloads and decrypts."""
    reply = factory.Reply(source=source['source'],
                          is_downloaded=False,
                          is_decrypted=None,
                          content=None)
    session.add(reply)
    session.commit()

    expected_content = 'foo'

    def set_object_decryption_status_with_content_side_effect(
            *nargs, **kwargs):
        reply.content = expected_content

    # mock the fetching of replies
    mocker.patch('securedrop_client.storage.find_new_replies',
                 return_value=[reply])
    mock_download_status = mocker.patch(
        'securedrop_client.message_sync.storage.mark_reply_as_downloaded')
    mock_decryption_status = mocker.patch(
        'securedrop_client.message_sync.storage.set_object_decryption_status_with_content',
        side_effect=set_object_decryption_status_with_content_side_effect)

    # don't create the signal
    mocker.patch('securedrop_client.message_sync.pyqtSignal')

    def mock_decrypt_submission_or_reply(filepath, plaintext_filename, is_doc):
        with open(plaintext_filename, 'w') as f:
            f.write(expected_content)

    # mock the GpgHelper creation since we don't have directories/keys setup
    mock_gpg_helper = mocker.MagicMock(
        decrypt_submission_or_reply=mock_decrypt_submission_or_reply)
    mocker.patch('securedrop_client.message_sync.GpgHelper',
                 return_value=mock_gpg_helper)

    api = mocker.MagicMock(session=session)
    rs = ReplySync(api, 'mock', True)
    rs.session = session  # "patch" it with a real session
    rs.api.download_reply = mocker.MagicMock(
        return_value=(1234, "/home/user/downloads/foo"))

    mock_reply_ready = mocker.patch.object(rs, 'reply_ready')

    # check that it runs without raising exceptions
    rs.run(False)

    mock_decryption_status.assert_called_once_with(reply, api.session, True,
                                                   expected_content)
    mock_download_status.called_once_with(reply, api.mock_session)
    mock_reply_ready.emit.assert_called_once_with(reply.uuid, expected_content)
Exemplo n.º 9
0
def test_reply_already_sent(homedir, mocker, session, session_maker, reply_status_codes):
    """
    Check that if a reply is already sent then we return the reply id.
    """
    source = factory.Source()
    session.add(source)
    reply = factory.Reply(source=source)
    session.add(reply)
    session.commit()

    job = SendReplyJob("mock_source_id", reply.uuid, "mock reply message", mocker.MagicMock())
    reply_uuid = job.call_api(mocker.MagicMock(), session)

    assert reply.uuid == reply_uuid
Exemplo n.º 10
0
def test_ReplySync_run_decryption_error(mocker, session, source):
    """Test when a reply successfully downloads, but does not successfully decrypt."""
    reply = factory.Reply(source=source['source'],
                          is_downloaded=False,
                          is_decrypted=None,
                          content=None)
    session.add(reply)
    session.commit()

    # mock the fetching of replies
    mocker.patch('securedrop_client.storage.find_new_replies',
                 return_value=[reply])
    mock_download_status = mocker.patch(
        'securedrop_client.message_sync.storage.mark_reply_as_downloaded')
    mock_decryption_status = mocker.patch(
        'securedrop_client.message_sync.storage.set_object_decryption_status_with_content'
    )

    # don't create the signal
    mocker.patch('securedrop_client.message_sync.pyqtSignal')
    # mock the GpgHelper creation since we don't have directories/keys setup
    mocker.patch('securedrop_client.message_sync.GpgHelper')

    api = mocker.MagicMock(session=session)

    rs = ReplySync(api, 'mock', True)
    rs.session = session  # "patch" it with a real session
    mocker.patch.object(rs.gpg,
                        'decrypt_submission_or_reply',
                        side_effect=CryptoError)

    rs.api.download_reply = mocker.MagicMock(
        return_value=(1234, "/home/user/downloads/foo"))

    mock_reply_ready = mocker.patch.object(rs, 'reply_ready')

    # check that it runs without raising exceptions
    rs.run(False)

    mock_download_status.assert_called_once_with(reply.uuid, session)
    mock_decryption_status.assert_called_once_with(reply, api.session, False)
    mock_reply_ready.emit.assert_called_once_with(reply.uuid,
                                                  '<Reply not yet available>')
Exemplo n.º 11
0
def test_ReplyDownloadJob_message_already_decrypted(mocker, homedir, session,
                                                    session_maker):
    """
    Test that call_api just returns uuid if already decrypted.
    """
    reply = factory.Reply(source=factory.Source(),
                          is_downloaded=True,
                          is_decrypted=True)
    session.add(reply)
    session.commit()
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    job = ReplyDownloadJob(reply.uuid, homedir, gpg)
    decrypt_fn = mocker.patch.object(job.gpg, 'decrypt_submission_or_reply')
    api_client = mocker.MagicMock()
    download_fn = mocker.patch.object(api_client, 'download_reply')

    return_uuid = job.call_api(api_client, session)

    assert reply.uuid == return_uuid
    decrypt_fn.assert_not_called()
    download_fn.assert_not_called()
Exemplo n.º 12
0
def test_ReplySync_exception(mocker):
    """
    Mostly here for code coverage- makes sure that if an exception is
    raised in the download thread, the code which catches it is actually
    run
    """
    reply = factory.Reply()
    api = mocker.MagicMock()
    home = "/home/user/.sd"
    is_qubes = False

    mocker.patch('securedrop_client.storage.find_new_replies',
                 return_value=[reply])
    mocker.patch('securedrop_client.message_sync.GpgHelper')
    mocker.patch("sdclientapi.sdlocalobjects.Reply",
                 mocker.MagicMock(side_effect=Exception()))

    rs = ReplySync(api, home, is_qubes)

    # check that it runs without raise exceptions
    rs.run(False)
Exemplo n.º 13
0
def test_ReplySync_run_failure(mocker, session, source):
    reply = factory.Reply(source=source['source'])
    session.add(reply)
    session.commit()

    # mock finding new replies
    mocker.patch('securedrop_client.storage.find_new_replies',
                 return_value=[reply])
    # mock handling the new reply
    mocker.patch(
        'securedrop_client.message_sync.storage.mark_reply_as_downloaded')
    mocker.patch('securedrop_client.message_sync.GpgHelper')

    api = mocker.MagicMock()
    home = "/home/user/.sd"
    is_qubes = False

    ms = ReplySync(api, home, is_qubes)
    ms.api.download_submission = mocker.MagicMock(
        return_value=(1234, "/home/user/downloads/foo"))

    # check that it runs without raise exceptions
    ms.run(False)
Exemplo n.º 14
0
def test_update_replies(homedir, mocker, session):
    """
    Check that:

    * Existing replies are updated in the local database.
    * New replies have an entry in the local database.
    * Local replies not returned by the remote server are deleted from the
      local database.
    * References to journalist's usernames are correctly handled.
    """
    data_dir = os.path.join(homedir, "data")

    journalist = factory.User(id=1)
    session.add(journalist)

    source = factory.Source()
    session.add(source)

    # Some local reply objects. One already exists in the API results
    # (this will be updated), one does NOT exist in the API results (this will
    # be deleted from the local database).
    local_reply_update = factory.Reply(
        source_id=source.id,
        source=source,
        journalist_id=journalist.id,
        filename="1-original-reply.gpg.",
        size=2,
    )
    session.add(local_reply_update)

    local_reply_delete = factory.Reply(source_id=source.id, source=source)
    session.add(local_reply_delete)

    local_replies = [local_reply_update, local_reply_delete]
    # Some remote reply objects from the API, one of which will exist in the
    # local database, the other will NOT exist in the local database
    # (this will be added to the database)
    remote_reply_update = factory.RemoteReply(
        journalist_uuid=journalist.uuid,
        uuid=local_reply_update.uuid,
        source_url="/api/v1/sources/{}".format(source.uuid),
        file_counter=local_reply_update.file_counter,
        filename=local_reply_update.filename,
    )

    remote_reply_create = factory.RemoteReply(
        journalist_uuid=journalist.uuid,
        source_url="/api/v1/sources/{}".format(source.uuid),
        file_counter=factory.REPLY_COUNT + 1,
        filename="{}-filename.gpg".format(factory.REPLY_COUNT + 1),
    )

    remote_replies = [remote_reply_update, remote_reply_create]

    session.commit()
    update_replies(remote_replies, local_replies, session, data_dir)
    session.commit()

    # Check the expected local reply object has been updated with values
    # from the API.
    assert local_reply_update.journalist_id == journalist.id
    assert local_reply_update.size == remote_reply_update.size
    assert local_reply_update.filename == remote_reply_update.filename

    new_reply = session.query(
        db.Reply).filter_by(uuid=remote_reply_create.uuid).one()
    assert new_reply.source_id == source.id
    assert new_reply.journalist_id == journalist.id
    assert new_reply.size == remote_reply_create.size
    assert new_reply.filename == remote_reply_create.filename

    # Ensure the local reply that is not in the API results is deleted.
    assert session.query(
        db.Reply).filter_by(uuid=local_reply_delete.uuid).count() == 0