Example #1
0
    def on_file_download(self, source_db_object, message):
        """
        Download the file associated with the associated message (which may
        be a Submission or Reply).
        """
        if not self.api:  # Then we should tell the user they need to login.
            self.on_action_requiring_login()
            return

        if isinstance(message, db.File) or isinstance(message, db.Message):
            # Handle submissions.
            func = self.api.download_submission
            sdk_object = sdclientapi.Submission(uuid=message.uuid)
            sdk_object.filename = message.filename
            sdk_object.source_uuid = source_db_object.uuid
        elif isinstance(message, db.Reply):
            # Handle journalist's replies.
            func = self.api.download_reply
            sdk_object = sdclientapi.Reply(uuid=message.uuid)
            sdk_object.filename = message.filename
            sdk_object.source_uuid = source_db_object.uuid

        self.set_status(_('Downloading {}'.format(sdk_object.filename)))
        self.call_api(func,
                      self.on_file_download_success,
                      self.on_file_download_failure,
                      sdk_object,
                      self.data_dir,
                      current_object=message)
Example #2
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
Example #3
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
Example #4
0
 def on_file_click(self, source_db_object, message):
     """
     Download the file associated with the associated message (which may
     be a Submission or Reply).
     """
     if isinstance(message, models.Submission):
         # Handle submissions.
         func = self.api.download_submission
         sdk_object = sdclientapi.Submission(uuid=message.uuid)
         sdk_object.filename = message.filename
         sdk_object.source_uuid = source_db_object.uuid
     elif isinstance(message, models.Reply):
         # Handle journalist's replies.
         func = self.api.download_reply
         sdk_object = sdclientapi.Reply(uuid=message.uuid)
         sdk_object.filename = message.filename
         sdk_object.source_uuid = source_db_object.uuid
     self.call_api(func,
                   self.on_file_download,
                   self.on_download_timeout,
                   sdk_object,
                   self.data_dir,
                   current_object=message)
Example #5
0
def test_drafts_ordering(homedir, mocker, session, session_maker, reply_status_codes):
    """
    Check that if a reply is successful, drafts sent before and after
    continue to appear in the same order.
    """
    initial_interaction_count = 1
    source_uuid = "foo"
    source = factory.Source(uuid=source_uuid, interaction_count=initial_interaction_count)
    session.add(source)
    msg_uuid = "xyz456"

    draft_reply = factory.DraftReply(uuid=msg_uuid, file_counter=1)
    session.add(draft_reply)
    session.commit()

    # Draft reply from the previous queue job.
    draft_reply_before = factory.DraftReply(
        timestamp=draft_reply.timestamp - datetime.timedelta(minutes=1),
        source_id=source.id,
        file_counter=draft_reply.file_counter,
        uuid="foo",
    )
    session.add(draft_reply_before)

    # Draft reply that the queue will operate on next.
    draft_reply_after = factory.DraftReply(
        timestamp=draft_reply.timestamp + datetime.timedelta(minutes=1),
        source_id=source.id,
        file_counter=draft_reply.file_counter,
        uuid="bar",
    )

    session.add(draft_reply_after)
    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="2-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

    # We use the file_counter on each Reply, Message, File, and DraftReply
    # object to order the conversation view. We expect a unique file_counter
    # for Reply, Message, and File objects since they are retrieved from
    # the server.
    # For DraftReply, we don't have that unique constraint, and we instead expect
    # the file_counter to be the interaction_count at the time of send.
    # If we do not update the interaction_count after each successful reply send,
    # future drafts will have an interaction_count that is too low, leading
    # to incorrectly ordered drafts until a metadata sync completes (the metadata
    # sync is the place where the source object is updated from the server).
    source = session.query(db.Source).filter_by(uuid=source_uuid).one()
    assert source.interaction_count == initial_interaction_count + 1

    # Check the ordering displayed to the user
    assert source.collection[0] == draft_reply_before
    assert source.collection[1] == reply
    assert source.collection[2] == draft_reply_after