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)
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
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
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)
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