示例#1
0
def test_send_reply_failure_unknown_error(
    homedir, mocker, session, session_maker, reply_status_codes
):
    """
    Check that if the SendReplyJob api call fails when sending a message that SendReplyJobError
    is raised and the reply is not added to the local database.
    """
    source = factory.Source()
    session.add(source)
    draft_reply = factory.DraftReply(uuid="mock_reply_uuid")
    session.add(draft_reply)
    session.commit()
    api_client = mocker.MagicMock()
    mocker.patch.object(api_client, "reply_source", side_effect=Exception)
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    encrypt_fn = mocker.patch.object(gpg, "encrypt_to_source")
    job = SendReplyJob(source.uuid, "mock_reply_uuid", "mock_message", gpg)

    with pytest.raises(Exception):
        job.call_api(api_client, session)

    encrypt_fn.assert_called_once_with(source.uuid, "mock_message")
    replies = session.query(db.Reply).filter_by(uuid="mock_reply_uuid").all()
    assert len(replies) == 0

    # Ensure that the draft reply is still in the db
    drafts = session.query(db.DraftReply).filter_by(uuid="mock_reply_uuid").all()
    assert len(drafts) == 1
示例#2
0
def test_send_reply_failure_timeout_error(
    homedir, mocker, session, session_maker, reply_status_codes, exception
):
    """
    Check that if the SendReplyJob api call fails because of a RequestTimeoutError or
    ServerConnectionError that a SendReplyJobTimeoutError is raised.
    """
    source = factory.Source()
    session.add(source)
    draft_reply = factory.DraftReply(uuid="mock_reply_uuid")
    session.add(draft_reply)
    session.commit()
    api_client = mocker.MagicMock()
    mocker.patch.object(api_client, "reply_source", side_effect=exception)
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    encrypt_fn = mocker.patch.object(gpg, "encrypt_to_source")
    job = SendReplyJob(source.uuid, "mock_reply_uuid", "mock_message", gpg)

    with pytest.raises(SendReplyJobTimeoutError):
        job.call_api(api_client, session)

    encrypt_fn.assert_called_once_with(source.uuid, "mock_message")
    replies = session.query(db.Reply).filter_by(uuid="mock_reply_uuid").all()
    assert len(replies) == 0

    # Ensure that the draft reply is still in the db
    drafts = session.query(db.DraftReply).filter_by(uuid="mock_reply_uuid").all()
    assert len(drafts) == 1
示例#3
0
def test_send_reply_failure_gpg_error(homedir, mocker, session, session_maker,
                                      reply_status_codes):
    '''
    Check that if gpg fails when sending a message, we do not call the API, and ensure that
    SendReplyJobError is raised when there is a CryptoError so we can handle it in
    ApiJob._do_call_api.
    '''
    source = factory.Source()
    session.add(source)
    msg_uuid = 'xyz456'
    draft_reply = factory.DraftReply(uuid=msg_uuid)
    session.add(draft_reply)
    session.commit()

    gpg = GpgHelper(homedir, session_maker, is_qubes=False)

    api_client = mocker.MagicMock()
    api_client.token_journalist_uuid = 'journalist ID sending the reply'

    mock_encrypt = mocker.patch.object(gpg,
                                       'encrypt_to_source',
                                       side_effect=CryptoError)
    msg = 'wat'

    mock_reply_response = sdclientapi.Reply(uuid=msg_uuid,
                                            filename='5-dummy-reply')
    api_client.reply_source = mocker.MagicMock()
    api_client.reply_source.return_value = mock_reply_response

    mock_sdk_source = mocker.Mock()
    mock_source_init = mocker.patch(
        'securedrop_client.logic.sdclientapi.Source',
        return_value=mock_sdk_source)

    job = SendReplyJob(
        source.uuid,
        msg_uuid,
        msg,
        gpg,
    )

    with pytest.raises(SendReplyJobError):
        job.call_api(api_client, session)

    # Ensure we attempted to encrypt the message
    mock_encrypt.assert_called_once_with(source.uuid, msg)
    assert mock_source_init.call_count == 0

    # Ensure reply did not get added to db
    replies = session.query(db.Reply).filter_by(uuid=msg_uuid).all()
    assert len(replies) == 0

    # Ensure that the draft reply is still in the db
    drafts = session.query(db.DraftReply).filter_by(uuid=msg_uuid).all()
    assert len(drafts) == 1
示例#4
0
def test_send_reply_success(homedir, mocker, session, session_maker,
                            reply_status_codes):
    '''
    Check that the "happy path" of encrypting a message and sending it to the
    server behaves as expected.
    '''
    source = factory.Source()
    session.add(source)
    msg_uuid = 'xyz456'
    draft_reply = factory.DraftReply(uuid=msg_uuid)
    session.add(draft_reply)
    session.commit()

    gpg = GpgHelper(homedir, session_maker, is_qubes=False)

    api_client = mocker.MagicMock()
    api_client.token_journalist_uuid = 'journalist ID sending the reply'

    encrypted_reply = 's3kr1t m3ss1dg3'
    mock_encrypt = mocker.patch.object(gpg,
                                       'encrypt_to_source',
                                       return_value=encrypted_reply)
    msg = 'wat'

    mock_reply_response = sdclientapi.Reply(uuid=msg_uuid,
                                            filename='5-dummy-reply')
    api_client.reply_source = mocker.MagicMock()
    api_client.reply_source.return_value = mock_reply_response

    mock_sdk_source = mocker.Mock()
    mock_source_init = mocker.patch(
        'securedrop_client.logic.sdclientapi.Source',
        return_value=mock_sdk_source)

    job = SendReplyJob(
        source.uuid,
        msg_uuid,
        msg,
        gpg,
    )

    job.call_api(api_client, session)

    # ensure message gets encrypted
    mock_encrypt.assert_called_once_with(source.uuid, msg)
    mock_source_init.assert_called_once_with(uuid=source.uuid)

    # assert reply got added to db
    reply = session.query(db.Reply).filter_by(uuid=msg_uuid).one()
    assert reply.journalist_id == api_client.token_journalist_uuid
示例#5
0
    def send_reply(self, source_uuid: str, reply_uuid: str, message: str) -> None:
        """
        Send a reply to a source.
        """
        # Before we send the reply, add the draft to the database with a PENDING
        # reply send status.
        source = self.session.query(db.Source).filter_by(uuid=source_uuid).one()
        reply_status = self.session.query(db.ReplySendStatus).filter_by(
            name=db.ReplySendStatusCodes.PENDING.value).one()
        draft_reply = db.DraftReply(
            uuid=reply_uuid,
            timestamp=datetime.datetime.utcnow(),
            source_id=source.id,
            journalist_id=self.api.token_journalist_uuid,
            file_counter=source.interaction_count,
            content=message,
            send_status_id=reply_status.id,
        )
        self.session.add(draft_reply)
        self.session.commit()

        job = SendReplyJob(source_uuid, reply_uuid, message, self.gpg)
        job.success_signal.connect(self.on_reply_success, type=Qt.QueuedConnection)
        job.failure_signal.connect(self.on_reply_failure, type=Qt.QueuedConnection)

        self.api_job_queue.enqueue(job)
示例#6
0
def test_send_reply_sql_exception_during_failure(
    homedir, mocker, session, session_maker, reply_status_codes
):
    """
    Check that we do not raise an unhandled exception when we set the draft reply
    status to failed in the except block if there is a SQL exception.
    """
    source = factory.Source()
    session.add(source)

    # Note that we do not add a DraftReply. An exception will occur when we try
    # to set the reply status to 'FAILED' for a non-existent reply, which we
    # expect to be handled.

    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    job = SendReplyJob(source.uuid, "mock_reply_uuid", "mock_message", gpg)

    # This should not raise an exception
    job._set_status_to_failed(session)
示例#7
0
def test_send_reply_unexpected_exception_during_failure(
        homedir, mocker, session, session_maker, reply_status_codes):
    '''
    Check that we do not raise an unhandled exception when we set the draft reply
    status to failed in the except block if there is an unexpected exception.
    '''
    source = factory.Source()
    session.add(source)
    draft_reply = factory.DraftReply(uuid='mock_reply_uuid')
    session.add(draft_reply)
    session.commit()

    # session.commit() is called when we try to set the status to failed.
    session.commit = mocker.MagicMock(side_effect=Exception("BOOM"))

    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    job = SendReplyJob(source.uuid, 'mock_reply_uuid', 'mock_message', gpg)

    # This should not raise an exception
    job._set_status_to_failed(session)
示例#8
0
def test_send_reply_failure_when_repr_is_none(
    homedir, mocker, session, session_maker, reply_status_codes
):
    """
    Check that the SendReplyJob api call results in a SendReplyJobError and nothing else, e.g.
    no TypeError, when an api call results in an exception that returns None for __repr__
    (regression test).
    """

    class MockException(Exception):
        def __repr__(self):
            return None

    source = factory.Source(uuid="mock_reply_uuid")
    session.add(source)
    draft_reply = factory.DraftReply(uuid="mock_reply_uuid")
    session.add(draft_reply)
    session.commit()
    api_client = mocker.MagicMock()
    mocker.patch.object(api_client, "reply_source", side_effect=MockException("mock"))
    gpg = GpgHelper(homedir, session_maker, is_qubes=False)
    encrypt_fn = mocker.patch.object(gpg, "encrypt_to_source")
    job = SendReplyJob(source.uuid, "mock_reply_uuid", "mock_message", gpg)

    error = "Failed to send reply mock_reply_uuid for source {} due to Exception: mock".format(
        source.uuid
    )
    with pytest.raises(SendReplyJobError, match=error):
        job.call_api(api_client, session)

    encrypt_fn.assert_called_once_with(source.uuid, "mock_message")
    replies = session.query(db.Reply).filter_by(uuid="mock_reply_uuid").all()
    assert len(replies) == 0

    # Ensure that the draft reply is still in the db
    drafts = session.query(db.DraftReply).filter_by(uuid="mock_reply_uuid").all()
    assert len(drafts) == 1