コード例 #1
0
def test_request_timeout(mocker):
    class MockedPopen:
        def __init__(self, *nargs, **kwargs) -> None:
            self.stdin = mocker.MagicMock()

        def communicate(self, *nargs, **kwargs) -> None:
            raise TimeoutExpired(["mock"], 123)

    api = API("mock", "mock", "mock", "mock", proxy=True)
    mocker.patch("sdclientapi.Popen", MockedPopen)

    with pytest.raises(RequestTimeoutError):
        api.authenticate()
コード例 #2
0
ファイル: test_api.py プロジェクト: creviera/securedrop-sdk
def test_request_read_timeout(mocker):
    api = API("mock", "mock", "mock", "mock", proxy=False)
    mocker.patch("sdclientapi.requests.request", side_effect=ReadTimeout)
    with pytest.raises(RequestTimeoutError):
        api.authenticate()
コード例 #3
0
ファイル: test_api.py プロジェクト: creviera/securedrop-sdk
class TestAPI(unittest.TestCase):
    @vcr.use_cassette("data/test-setup.yml")
    def setUp(self):
        self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L")
        self.username = "******"
        self.password = "******"
        self.server = "http://127.0.0.1:8081/"
        self.api = API(self.server, self.username, self.password,
                       str(self.totp.now()))
        for i in range(3):
            try:
                self.api.authenticate()
            except BaseError:
                token = load_auth()
                if token:
                    self.api.token = token
                    self.api.update_auth_header()
                    break
                time.sleep(31)

            save_auth(self.api.token)
            break

    @vcr.use_cassette("data/test-baduser.yml")
    def test_auth_baduser(self):
        self.api = API(self.server, "no", self.password, str(self.totp.now()))
        with self.assertRaises(AuthError):
            self.api.authenticate()

    @vcr.use_cassette("data/test-badpassword.yml")
    def test_auth_badpassword(self):
        self.api = API(self.server, self.username, "no", str(self.totp.now()))
        with self.assertRaises(AuthError):
            self.api.authenticate()

    @vcr.use_cassette("data/test-badotp.yml")
    def test_auth_badotp(self):
        self.api = API(self.server, self.username, self.password, "no")
        with self.assertRaises(AuthError):
            self.api.authenticate()

    def test_api_auth(self):
        self.assertTrue(isinstance(self.api.token, str))
        self.assertTrue(
            isinstance(self.api.token_expiration, datetime.datetime))
        self.assertTrue(isinstance(self.api.token_journalist_uuid, str))
        self.assertTrue(
            isinstance(self.api.journalist_first_name, (str, type(None))))
        self.assertTrue(
            isinstance(self.api.journalist_last_name, (str, type(None))))

    @vcr.use_cassette("data/test-get-sources.yml")
    def test_get_sources(self):
        sources = self.api.get_sources()
        for source in sources:
            # Assert expected fields are present
            assert source.journalist_designation
            assert source.uuid
            assert source.last_updated

    @vcr.use_cassette("data/test-star-add-remove.yml")
    def test_star_add_remove(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.add_star(s))
        self.assertTrue(self.api.remove_star(s))
        for source in self.api.get_sources():
            if source.uuid == s.uuid:
                self.assertFalse(source.is_starred)

    @vcr.use_cassette("data/test-get-single-source.yml")
    def test_get_single_source(self):
        s = self.api.get_sources()[0]
        # Now we will try to get the same source again
        s2 = self.api.get_source(s)

        self.assertEqual(s.journalist_designation, s2.journalist_designation)
        self.assertEqual(s.uuid, s2.uuid)

    @vcr.use_cassette("data/test-get-single-source.yml")
    def test_get_single_source_from_string(self):
        s = self.api.get_sources()[0]
        # Now we will try to get the same source again using uuid
        s2 = self.api.get_source_from_string(s.uuid)

        self.assertEqual(s.journalist_designation, s2.journalist_designation)
        self.assertEqual(s.uuid, s2.uuid)

    @vcr.use_cassette("data/test-failed-single-source.yml")
    def test_failed_single_source(self):
        with self.assertRaises(WrongUUIDError):
            self.api.get_source(Source(uuid="not there"))

    @vcr.use_cassette("data/test-get-submissions.yml")
    def test_get_submissions(self):
        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)
        for submission in subs:
            assert submission.filename

    @vcr.use_cassette("data/test-get-submission.yml")
    def test_get_submission(self):
        # Get a source with submissions
        source_uuid = self.api.get_all_submissions()[0].source_uuid
        s = self.api.get_source(Source(uuid=source_uuid))

        subs = self.api.get_submissions(s)
        sub = self.api.get_submission(subs[0])
        self.assertEqual(sub.filename, subs[0].filename)

    @vcr.use_cassette("data/test-get-submission.yml")
    def test_get_submission_from_string(self):
        # Get a source with submissions
        source_uuid = self.api.get_all_submissions()[0].source_uuid
        s = self.api.get_source(Source(uuid=source_uuid))

        subs = self.api.get_submissions(s)
        sub = self.api.get_submission_from_string(subs[0].uuid, s.uuid)
        self.assertEqual(sub.filename, subs[0].filename)

    @vcr.use_cassette("data/test-get-wrong-submissions.yml")
    def test_get_wrong_submissions(self):
        s = self.api.get_sources()[0]
        s.uuid = "rofl-missing"
        with self.assertRaises(WrongUUIDError):
            self.api.get_submissions(s)

    @vcr.use_cassette("data/test-get-all-submissions.yml")
    def test_get_all_submissions(self):
        subs = self.api.get_all_submissions()
        for submission in subs:
            assert submission.filename

    @vcr.use_cassette("data/test-flag-source.yml")
    def test_flag_source(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.flag_source(s))
        # Now we will try to get the same source again
        s2 = self.api.get_source(s)
        self.assertTrue(s2.is_flagged)

    @vcr.use_cassette("data/test-delete-source.yml")
    def test_delete_source(self):
        number_of_sources_before = len(self.api.get_sources())

        s = self.api.get_sources()[0]
        self.assertTrue(self.api.delete_source(s))

        # Now there should be one less source
        sources = self.api.get_sources()
        self.assertEqual(len(sources), number_of_sources_before - 1)

    @vcr.use_cassette("data/test-delete-source.yml")
    def test_delete_source_from_string(self):
        number_of_sources_before = len(self.api.get_sources())

        s = self.api.get_sources()[0]
        self.assertTrue(self.api.delete_source_from_string(s.uuid))

        # Now there should be one less source
        sources = self.api.get_sources()
        self.assertEqual(len(sources), number_of_sources_before - 1)

    @vcr.use_cassette("data/test-delete-submission.yml")
    def test_delete_submission(self):
        number_of_submissions_before = len(self.api.get_all_submissions())

        subs = self.api.get_all_submissions()
        self.assertTrue(self.api.delete_submission(subs[0]))
        new_subs = self.api.get_all_submissions()
        # We now should have 1 less submission
        self.assertEqual(len(new_subs), number_of_submissions_before - 1)

        # Let us make sure that sub[0] is not there
        for s in new_subs:
            self.assertNotEqual(s.uuid, subs[0].uuid)

    @vcr.use_cassette("data/test-delete-submission-from-string.yml")
    def test_delete_submission_from_string(self):
        number_of_submissions_before = len(self.api.get_all_submissions())

        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)

        self.assertTrue(self.api.delete_submission(subs[0]))
        new_subs = self.api.get_all_submissions()
        # We now should have 1 less submission
        self.assertEqual(len(new_subs), number_of_submissions_before - 1)

        # Let us make sure that sub[0] is not there
        for s in new_subs:
            self.assertNotEqual(s.uuid, subs[0].uuid)

    @vcr.use_cassette("data/test-get-current-user.yml")
    def test_get_current_user(self):
        user = self.api.get_current_user()
        self.assertTrue(user["is_admin"])
        self.assertEqual(user["username"], "journalist")
        self.assertTrue("first_name" in user)
        self.assertTrue("last_name" in user)

    @vcr.use_cassette("data/test-error-unencrypted-reply.yml")
    def test_error_unencrypted_reply(self):
        s = self.api.get_sources()[0]
        with self.assertRaises(ReplyError) as err:
            self.api.reply_source(s, "hello")

        self.assertEqual(err.exception.msg,
                         "You must encrypt replies client side")

    @vcr.use_cassette("data/test-reply-source.yml")
    def test_reply_source(self):
        s = self.api.get_sources()[0]
        dirname = os.path.dirname(__file__)
        with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj:
            data = fobj.read()

        reply = self.api.reply_source(s, data)
        assert isinstance(reply, Reply)
        assert reply.uuid
        assert reply.filename

    @vcr.use_cassette("data/test-reply-source-with-uuid.yml")
    def test_reply_source_with_uuid(self):
        s = self.api.get_sources()[0]
        dirname = os.path.dirname(__file__)
        with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj:
            data = fobj.read()

        msg_uuid = "e467868c-1fbb-4b5e-bca2-87944ea83855"
        reply = self.api.reply_source(s, data, msg_uuid)
        assert reply.uuid == msg_uuid

    @vcr.use_cassette("data/test-download-submission.yml")
    def test_download_submission(self):
        s = self.api.get_all_submissions()[0]

        self.assertFalse(s.is_read)

        # We need a temporary directory to download
        tmpdir = tempfile.mkdtemp()
        etag, filepath = self.api.download_submission(s, tmpdir)

        # now let us read the downloaded file
        with open(filepath, "rb") as fobj:
            content = fobj.read()

        # Verify the ETag contains the algorithm and the hash is correct
        hasher = hashlib.sha256()
        hasher.update(content)

        assert etag == "sha256:{}".format(hasher.hexdigest())

        # Now the submission should have is_read as True.
        s = self.api.get_submission(s)
        self.assertTrue(s.is_read)

        # Let us remove the temporary directory
        shutil.rmtree(tmpdir)

    @vcr.use_cassette("data/test-get-replies-from-source.yml")
    def test_get_replies_from_source(self):
        s = self.api.get_sources()[0]
        replies = self.api.get_replies_from_source(s)
        self.assertEqual(len(replies), NUM_REPLIES_PER_SOURCE)

    @vcr.use_cassette("data/test-get-reply-from-source.yml")
    def test_get_reply_from_source(self):
        s = self.api.get_sources()[0]
        replies = self.api.get_replies_from_source(s)
        reply = replies[0]

        r = self.api.get_reply_from_source(s, reply.uuid)

        self.assertEqual(reply.filename, r.filename)
        self.assertEqual(reply.size, r.size)
        self.assertEqual(reply.reply_url, r.reply_url)
        self.assertEqual(reply.journalist_username, r.journalist_username)

    @vcr.use_cassette("data/test-get-all-replies.yml")
    def test_get_all_replies(self):
        num_sources = len(self.api.get_sources())
        replies = self.api.get_all_replies()
        self.assertEqual(len(replies), NUM_REPLIES_PER_SOURCE * num_sources)

    @vcr.use_cassette("data/test-download-reply.yml")
    def test_download_reply(self):
        r = self.api.get_all_replies()[0]

        # We need a temporary directory to download
        tmpdir = tempfile.mkdtemp()
        etag, filepath = self.api.download_reply(r, tmpdir)

        # now let us read the downloaded file
        with open(filepath, "rb") as fobj:
            content = fobj.read()

        # Verify the ETag contains the algorithm and the hash is correct
        hasher = hashlib.sha256()
        hasher.update(content)

        assert etag == "sha256:{}".format(hasher.hexdigest())

        # Let us remove the temporary directory
        shutil.rmtree(tmpdir)

    @vcr.use_cassette("data/test-delete-reply.yml")
    def test_delete_reply(self):
        r = self.api.get_all_replies()[0]

        number_of_replies_before = len(self.api.get_all_replies())

        self.assertTrue(self.api.delete_reply(r))

        # We deleted one, so there must be 1 less reply now
        self.assertEqual(len(self.api.get_all_replies()),
                         number_of_replies_before - 1)

    @vcr.use_cassette("data/test-logout.yml")
    def test_zlogout(self):
        r = self.api.logout()
        self.assertTrue(r)
コード例 #4
0
class TestAPI(TestShared):
    """
    Note that TestShared contains most of the test code, which is shared between
    API and API Proxy tests.
    """
    def setup_method(self):
        self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L")
        self.username = "******"
        self.password = "******"
        self.server = "http://127.0.0.1:8081/"

        # Because we may be using a TOTP code from a previous run that has since
        # been invalidated (or that may be invalid because of bad timing),
        # we retry repeatedly to get the token with a new TOTP code.
        #
        # It doesn't matter if these intermittent 403s are captured in the
        # cassette as we ignore them during playback.
        auth_result = None
        with vcr.use_cassette("data/test-setup.yml") as cassette:
            for i in range(3):
                totp = self.totp.now()
                self.api = API(self.server, self.username, self.password,
                               str(totp))
                try:
                    auth_result = self.api.authenticate()
                except AuthError:
                    # Don't sleep on final retry attempt or during playback
                    if i < 2 and cassette.play_count == 0:
                        time.sleep(31)
                    continue
                # No error, let's move on
                break

        if auth_result is None:
            raise AuthError("Could not obtain API token during test setup.")

    @vcr.use_cassette("data/test-baduser.yml")
    def test_auth_baduser(self):
        self.api = API(self.server, "no", self.password, str(self.totp.now()))
        with pytest.raises(AuthError):
            self.api.authenticate()

    @vcr.use_cassette("data/test-badpassword.yml")
    def test_auth_badpassword(self):
        self.api = API(self.server, self.username, "no", str(self.totp.now()))
        with pytest.raises(AuthError):
            self.api.authenticate()

    @vcr.use_cassette("data/test-badotp.yml")
    def test_auth_badotp(self):
        self.api = API(self.server, self.username, self.password, "no")
        with pytest.raises(AuthError):
            self.api.authenticate()

    def test_api_auth(self):
        super().api_auth()

    # This test is order-sensitive and must be run before the "seen"
    # state of files is altered.
    @vcr.use_cassette("data/test-download-submission.yml")
    def test_download_submission(self):
        submissions = self.api.get_all_submissions()
        unread_submission = None
        for s in submissions:
            if not s.is_read:
                unread_submission = s
                break

        if not unread_submission:
            assert False, "There must be an unread submission in the db for this test to work."

        assert not unread_submission.is_read

        # We need a temporary directory to download
        tmpdir = tempfile.mkdtemp()
        etag, filepath = self.api.download_submission(s, tmpdir)

        # now let us read the downloaded file
        with open(filepath, "rb") as fobj:
            content = fobj.read()

        # Verify the ETag contains the algorithm and the hash is correct
        hasher = hashlib.sha256()
        hasher.update(content)

        assert etag == "sha256:{}".format(hasher.hexdigest())

        # is_read should still be False as of SecureDrop 1.6.0 or later
        submission = self.api.get_submission(unread_submission)
        assert not submission.is_read

        # Let us remove the temporary directory
        shutil.rmtree(tmpdir)

    @vcr.use_cassette("data/test-seen.yml")
    def test_seen(self):
        super().seen()

    @vcr.use_cassette("data/test-get-sources.yml")
    def test_get_sources(self):
        super().get_sources()

    @vcr.use_cassette("data/test-star-add-remove.yml")
    def test_star_add_remove(self):
        super().star_add_remove()

    @vcr.use_cassette("data/test-get-single-source.yml")
    def test_get_single_source(self):
        super().get_single_source()

    @vcr.use_cassette("data/test-get-single-source.yml")
    def test_get_single_source_from_string(self):
        super().get_single_source(from_string=True)

    @vcr.use_cassette("data/test-failed-single-source.yml")
    def test_failed_single_source(self):
        super().failed_single_source()

    @vcr.use_cassette("data/test-get-submissions.yml")
    def test_get_submissions(self):
        super().get_submissions()

    @vcr.use_cassette("data/test-get-submission.yml")
    def test_get_submission(self):
        super().get_submission()

    @vcr.use_cassette("data/test-get-submission.yml")
    def test_get_submission_from_string(self):
        super().get_submission(from_string=True)

    @vcr.use_cassette("data/test-get-wrong-submissions.yml")
    def test_get_wrong_submissions(self):
        super().get_wrong_submissions()

    @vcr.use_cassette("data/test-get-all-submissions.yml")
    def test_get_all_submissions(self):
        super().get_all_submissions()

    @vcr.use_cassette("data/test-flag-source.yml")
    def test_flag_source(self):
        super().flag_source()

    @vcr.use_cassette("data/test-get-current-user.yml")
    def test_get_current_user(self):
        super().get_current_user()

    @vcr.use_cassette("data/test-get-users.yml")
    def test_get_users(self):
        super().get_users()

    @vcr.use_cassette("data/test-error-unencrypted-reply.yml")
    def test_error_unencrypted_reply(self):
        super().error_unencrypted_reply()

    @vcr.use_cassette("data/test-get-replies-from-source.yml")
    def test_get_replies_from_source(self):
        super().get_replies_from_source()

    @vcr.use_cassette("data/test-get-reply-from-source.yml")
    def test_get_reply_from_source(self):
        super().get_reply_from_source()

    @vcr.use_cassette("data/test-get-all-replies.yml")
    def test_get_all_replies(self):
        super().get_all_replies()

    # This test is materially different in the API & API Proxy versions.
    @vcr.use_cassette("data/test-download-reply.yml")
    def test_download_reply(self):
        r = self.api.get_all_replies()[0]

        # We need a temporary directory to download
        tmpdir = tempfile.mkdtemp()
        etag, filepath = self.api.download_reply(r, tmpdir)

        # now let us read the downloaded file
        with open(filepath, "rb") as fobj:
            content = fobj.read()

        # Verify the ETag contains the algorithm and the hash is correct
        hasher = hashlib.sha256()
        hasher.update(content)

        assert etag == "sha256:{}".format(hasher.hexdigest())

        # Let us remove the temporary directory
        shutil.rmtree(tmpdir)

    # ORDER MATTERS: The following tests add or delete data, and should
    # not be run before other tests, which may rely on the original fixture
    # state.

    @vcr.use_cassette("data/test-reply-source.yml")
    def test_reply_source(self):
        super().reply_source()

    @vcr.use_cassette("data/test-reply-source-with-uuid.yml")
    def test_reply_source_with_uuid(self):
        super().reply_source_with_uuid()

    @vcr.use_cassette("data/test-delete-conversation.yml")
    def test_delete_conversation(self):
        super().delete_conversation()

    @vcr.use_cassette("data/test-delete-source.yml")
    def test_delete_source(self):
        super().delete_source()

    @vcr.use_cassette("data/test-delete-source.yml")
    def test_delete_source_from_string(self):
        super().delete_source(from_string=True)

    @vcr.use_cassette("data/test-delete-submission.yml")
    def test_delete_submission(self):
        super().delete_submission()

    @vcr.use_cassette("data/test-delete-submission-from-string.yml")
    def test_delete_submission_from_string(self):
        super().delete_submission(from_string=True)

    @vcr.use_cassette("data/test-delete-reply.yml")
    def test_delete_reply(self):
        super().delete_reply()

    @vcr.use_cassette("data/test-logout.yml")
    def test_zlogout(self):
        r = self.api.logout()
        assert r
コード例 #5
0
class TestAPIProxy(unittest.TestCase):
    @dastollervey_datasaver
    def setUp(self):
        self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L")
        self.username = "******"
        self.password = "******"
        self.server = "http://localhost:8081/"
        self.api = API(self.server,
                       self.username,
                       self.password,
                       str(self.totp.now()),
                       proxy=True)
        for i in range(3):
            try:
                self.api.authenticate()
            except BaseError:
                token = load_auth()
                if token:
                    self.api.token = token
                    self.api.update_auth_header()
                    break
                time.sleep(31)

            save_auth(self.api.token)
            break

    def test_api_auth(self):
        self.assertTrue(self.api.token)

    @dastollervey_datasaver
    def test_get_sources(self):
        sources = self.api.get_sources()
        self.assertEqual(len(sources), 2)

    @dastollervey_datasaver
    def test_star_add_remove(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.add_star(s))
        self.assertTrue(self.api.remove_star(s))
        for source in self.api.get_sources():
            if source.uuid == s.uuid:
                self.assertFalse(source.is_starred)

    @dastollervey_datasaver
    def test_get_single_source(self):
        s = self.api.get_sources()[0]
        # Now we will try to get the same source again
        s2 = self.api.get_source(s)

        self.assertEqual(s.journalist_designation, s2.journalist_designation)
        self.assertEqual(s.uuid, s2.uuid)

    @dastollervey_datasaver
    def test_get_single_source_from_string(self):
        s = self.api.get_sources()[0]
        # Now we will try to get the same source again using uuid
        s2 = self.api.get_source_from_string(s.uuid)

        self.assertEqual(s.journalist_designation, s2.journalist_designation)
        self.assertEqual(s.uuid, s2.uuid)

    @dastollervey_datasaver
    def test_failed_single_source(self):
        with self.assertRaises(WrongUUIDError):
            self.api.get_source(Source(uuid="not there"))

    @dastollervey_datasaver
    def test_get_submissions(self):
        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)
        self.assertEqual(len(subs), 2)

    @dastollervey_datasaver
    def test_get_submission(self):
        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)
        sub = self.api.get_submission(subs[0])
        self.assertEqual(sub.filename, subs[0].filename)

    @dastollervey_datasaver
    def test_get_submission_from_string(self):
        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)
        sub = self.api.get_submission_from_string(subs[0].uuid, s.uuid)
        self.assertEqual(sub.filename, subs[0].filename)

    @dastollervey_datasaver
    def test_get_wrong_submissions(self):
        s = self.api.get_sources()[0]
        s.submissions_url = "/api/v1/sources/rofl-missing/submissions/2334"
        s.uuid = "rofl-missing"
        with self.assertRaises(WrongUUIDError):
            self.api.get_submissions(s)

    @dastollervey_datasaver
    def test_get_all_submissions(self):
        subs = self.api.get_all_submissions()
        self.assertEqual(len(subs), 4)

    @dastollervey_datasaver
    def test_flag_source(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.flag_source(s))
        # Now we will try to get the same source again
        s2 = self.api.get_source(s)
        self.assertTrue(s2.is_flagged)

    @dastollervey_datasaver
    def test_delete_source(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.delete_source(s))

        # Now there should be one source left
        sources = self.api.get_sources()
        self.assertEqual(len(sources), 1)

    @dastollervey_datasaver
    def test_delete_source_from_string(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.delete_source_from_string(s.uuid))

        # Now there should be one source left
        sources = self.api.get_sources()
        self.assertEqual(len(sources), 1)

    @dastollervey_datasaver
    def test_delete_submission(self):
        subs = self.api.get_all_submissions()
        self.assertTrue(self.api.delete_submission(subs[0]))
        new_subs = self.api.get_all_submissions()
        # We now should have 3 submissions
        self.assertEqual(len(new_subs), 3)

        # Let us make sure that sub[0] is not there
        for s in new_subs:
            self.assertNotEqual(s.uuid, subs[0].uuid)

    @dastollervey_datasaver
    def test_delete_submission_from_string(self):
        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)

        self.assertTrue(self.api.delete_submission(subs[0]))
        new_subs = self.api.get_all_submissions()
        # We now should have 3 submissions
        self.assertEqual(len(new_subs), 3)

        # Let us make sure that sub[0] is not there
        for s in new_subs:
            self.assertNotEqual(s.uuid, subs[0].uuid)

    @dastollervey_datasaver
    def test_get_current_user(self):
        user = self.api.get_current_user()
        self.assertTrue(user["is_admin"])
        self.assertEqual(user["username"], "journalist")

    @dastollervey_datasaver
    def test_error_unencrypted_reply(self):
        s = self.api.get_sources()[0]
        with self.assertRaises(ReplyError) as err:
            self.api.reply_source(s, "hello")

        self.assertEqual(err.exception.msg,
                         "You must encrypt replies client side")

    @dastollervey_datasaver
    def test_reply_source(self):
        s = self.api.get_sources()[0]
        dirname = os.path.dirname(__file__)
        with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj:
            data = fobj.read()

        self.assertTrue(self.api.reply_source(s, data))

    @dastollervey_datasaver
    def test_get_replies_from_source(self):
        s = self.api.get_sources()[0]
        replies = self.api.get_replies_from_source(s)
        self.assertEqual(len(replies), 2)

    @dastollervey_datasaver
    def test_get_reply_from_source(self):
        s = self.api.get_sources()[0]
        replies = self.api.get_replies_from_source(s)
        reply = replies[0]

        r = self.api.get_reply_from_source(s, reply.uuid)

        self.assertEqual(reply.filename, r.filename)
        self.assertEqual(reply.size, r.size)
        self.assertEqual(reply.reply_url, r.reply_url)
        self.assertEqual(reply.journalist_username, r.journalist_username)

    @dastollervey_datasaver
    def test_get_all_replies(self):
        replies = self.api.get_all_replies()
        self.assertEqual(len(replies), 4)

    @dastollervey_datasaver
    def test_delete_reply(self):
        r = self.api.get_all_replies()[0]

        self.assertTrue(self.api.delete_reply(r))

        # We deleted one, so there must be 3 replies left
        self.assertEqual(len(self.api.get_all_replies()), 3)

    @dastollervey_datasaver
    def test_download_reply(self):
        r = self.api.get_all_replies()[0]

        _, filepath = self.api.download_reply(r)

    @dastollervey_datasaver
    def test_download_submission(self):
        s = self.api.get_all_submissions()[0]

        self.assertFalse(s.is_read)

        _, filepath = self.api.download_submission(s)

        # Now the submission should have is_read as True.
        s = self.api.get_submission(s)
        self.assertTrue(s.is_read)
コード例 #6
0
class TestAPIProxy(TestShared):
    """
    Note that TestShared contains most of the test code, which is shared between
    API and API Proxy tests.
    """
    @qrexec_datasaver
    def setup_method(self):
        self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L")
        self.username = "******"
        self.password = "******"
        self.server = "http://localhost:8081/"
        self.api = API(self.server,
                       self.username,
                       self.password,
                       str(self.totp.now()),
                       proxy=True)
        try:
            self.api.authenticate()
        except BaseError as err:
            raise AuthError(
                "Could not obtain API token during test setup. "
                "TOTP code may have expired or proxy may not be reachable. "
                "Error was: {}".format(err.msg))

    @qrexec_datasaver
    def test_api_auth(self):
        super().api_auth()

    # This test is order-sensitive and must be run before the "seen"
    # state of files is altered.
    # This test is materially different in the API & API Proxy versions.
    @qrexec_datasaver
    def test_download_submission(self):
        s = self.api.get_all_submissions()[0]

        assert not s.is_read

        # We need a temporary directory to download
        tmpdir = tempfile.mkdtemp()
        _, filepath = self.api.download_submission(s, tmpdir)

        # Uncomment the following part only on QubesOS
        # for testing against real server.
        # now let us read the downloaded file
        # with open(filepath, "rb") as fobj:
        #    fobj.read()

        # is_read should still be False as of SecureDrop 1.6.0 or later.

        s = self.api.get_submission(s)
        assert not s.is_read

        # Let us remove the temporary directory
        shutil.rmtree(tmpdir)

    @qrexec_datasaver
    def test_seen(self):
        super().seen()

    @qrexec_datasaver
    def test_get_sources(self):
        super().get_sources()

    @qrexec_datasaver
    def test_star_add_remove(self):
        super().star_add_remove()

    @qrexec_datasaver
    def test_get_single_source(self):
        super().get_single_source()

    @qrexec_datasaver
    def test_get_single_source_from_string(self):
        super().get_single_source(from_string=True)

    @qrexec_datasaver
    def test_failed_single_source(self):
        super().failed_single_source()

    @qrexec_datasaver
    def test_get_submissions(self):
        super().get_submissions()

    @qrexec_datasaver
    def test_get_submission(self):
        super().get_submission()

    @qrexec_datasaver
    def test_get_submission_from_string(self):
        super().get_submission(from_string=True)

    @qrexec_datasaver
    def test_get_wrong_submissions(self):
        super().get_wrong_submissions()

    @qrexec_datasaver
    def test_get_all_submissions(self):
        super().get_all_submissions()

    @qrexec_datasaver
    def test_flag_source(self):
        super().flag_source()

    @qrexec_datasaver
    def test_get_current_user(self):
        super().get_current_user()

    @qrexec_datasaver
    def test_get_users(self):
        super().get_users()

    @qrexec_datasaver
    def test_error_unencrypted_reply(self):
        super().error_unencrypted_reply()

    @qrexec_datasaver
    def test_get_replies_from_source(self):
        super().get_replies_from_source()

    @qrexec_datasaver
    def test_get_reply_from_source(self):
        super().get_reply_from_source()

    @qrexec_datasaver
    def test_get_all_replies(self):
        super().get_all_replies()

    @qrexec_datasaver
    def test_download_reply(self):
        r = self.api.get_all_replies()[0]

        # We need a temporary directory to download
        tmpdir = tempfile.mkdtemp()
        _, filepath = self.api.download_reply(r, tmpdir)

        # Uncomment the following part only on QubesOS
        # for testing against real server.
        # now let us read the downloaded file
        # with open(filepath, "rb") as fobj:
        #     fobj.read()

        # Let us remove the temporary directory
        shutil.rmtree(tmpdir)

    # ORDER MATTERS: The following tests add or delete data, and should
    # not be run before other tests which may rely on the original fixture
    # state.

    @qrexec_datasaver
    def test_reply_source(self):
        super().reply_source()

    @qrexec_datasaver
    def test_reply_source_with_uuid(self):
        super().reply_source_with_uuid()

    @qrexec_datasaver
    def test_delete_conversation(self):
        super().delete_conversation()

    @qrexec_datasaver
    def test_delete_source(self):
        super().delete_source()

    @qrexec_datasaver
    def test_delete_source_from_string(self):
        super().delete_source(from_string=True)

    @qrexec_datasaver
    def test_delete_submission(self):
        super().delete_submission()

    @qrexec_datasaver
    def test_delete_submission_from_string(self):
        super().delete_submission(from_string=True)

    @qrexec_datasaver
    def test_delete_reply(self):
        super().delete_reply()

    @qrexec_datasaver
    def test_logout(self):
        r = self.api.logout()
        assert r
コード例 #7
0
class TestAPI(unittest.TestCase):
    def setUp(self):
        self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L")
        self.username = "******"
        self.password = "******"
        self.server = "http://127.0.0.1:8081/"

        # Because we may be using a TOTP code from a previous run that has since
        # been invalidated (or that may be invalid because of bad timing),
        # we retry repeatedly to get the token with a new TOTP code.
        #
        # It doesn't matter if these intermittent 403s are captured in the
        # cassette as we ignore them during playback.
        auth_result = None
        with vcr.use_cassette("data/test-setup.yml") as cassette:
            for i in range(3):
                totp = self.totp.now()
                self.api = API(self.server, self.username, self.password,
                               str(totp))
                try:
                    auth_result = self.api.authenticate()
                except AuthError:
                    # Don't sleep on final retry attempt or during playback
                    if i < 2 and cassette.play_count == 0:
                        time.sleep(31)
                    continue
                # No error, let's move on
                break

        if auth_result is None:
            raise AuthError("Could not obtain API token during test setup.")

    @vcr.use_cassette("data/test-baduser.yml")
    def test_auth_baduser(self):
        self.api = API(self.server, "no", self.password, str(self.totp.now()))
        with self.assertRaises(AuthError):
            self.api.authenticate()

    @vcr.use_cassette("data/test-badpassword.yml")
    def test_auth_badpassword(self):
        self.api = API(self.server, self.username, "no", str(self.totp.now()))
        with self.assertRaises(AuthError):
            self.api.authenticate()

    @vcr.use_cassette("data/test-badotp.yml")
    def test_auth_badotp(self):
        self.api = API(self.server, self.username, self.password, "no")
        with self.assertRaises(AuthError):
            self.api.authenticate()

    def test_api_auth(self):
        self.assertTrue(isinstance(self.api.token, str))
        self.assertTrue(
            isinstance(self.api.token_expiration, datetime.datetime))
        self.assertTrue(isinstance(self.api.token_journalist_uuid, str))
        self.assertTrue(isinstance(self.api.first_name, (str, type(None))))
        self.assertTrue(isinstance(self.api.last_name, (str, type(None))))

    @vcr.use_cassette("data/test-seen.yml")
    def test_seen(self):
        submissions = self.api.get_all_submissions()
        replies = self.api.get_all_replies()

        file_uuids = []
        message_uuids = []
        reply_uuids = []

        for submission in submissions:
            if submission.is_file():
                file_uuids.append(submission.uuid)
            else:
                message_uuids.append(submission.uuid)

        for reply in replies:
            reply_uuids.append(reply.uuid)

        self.assertTrue(
            self.api.seen(files=file_uuids,
                          messages=message_uuids,
                          replies=reply_uuids))

    @vcr.use_cassette("data/test-get-sources.yml")
    def test_get_sources(self):
        sources = self.api.get_sources()
        for source in sources:
            # Assert expected fields are present
            assert source.journalist_designation
            assert source.uuid
            assert source.last_updated

    @vcr.use_cassette("data/test-star-add-remove.yml")
    def test_star_add_remove(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.add_star(s))
        self.assertTrue(self.api.remove_star(s))
        for source in self.api.get_sources():
            if source.uuid == s.uuid:
                self.assertFalse(source.is_starred)

    @vcr.use_cassette("data/test-get-single-source.yml")
    def test_get_single_source(self):
        s = self.api.get_sources()[0]
        # Now we will try to get the same source again
        s2 = self.api.get_source(s)

        self.assertEqual(s.journalist_designation, s2.journalist_designation)
        self.assertEqual(s.uuid, s2.uuid)

    @vcr.use_cassette("data/test-get-single-source.yml")
    def test_get_single_source_from_string(self):
        s = self.api.get_sources()[0]
        # Now we will try to get the same source again using uuid
        s2 = self.api.get_source_from_string(s.uuid)

        self.assertEqual(s.journalist_designation, s2.journalist_designation)
        self.assertEqual(s.uuid, s2.uuid)

    @vcr.use_cassette("data/test-failed-single-source.yml")
    def test_failed_single_source(self):
        with self.assertRaises(WrongUUIDError):
            self.api.get_source(Source(uuid="not there"))

    @vcr.use_cassette("data/test-get-submissions.yml")
    def test_get_submissions(self):
        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)
        for submission in subs:
            assert submission.filename

    @vcr.use_cassette("data/test-get-submission.yml")
    def test_get_submission(self):
        # Get a source with submissions
        source_uuid = self.api.get_all_submissions()[0].source_uuid
        s = self.api.get_source(Source(uuid=source_uuid))

        subs = self.api.get_submissions(s)
        sub = self.api.get_submission(subs[0])
        self.assertEqual(sub.filename, subs[0].filename)

    @vcr.use_cassette("data/test-get-submission.yml")
    def test_get_submission_from_string(self):
        # Get a source with submissions
        source_uuid = self.api.get_all_submissions()[0].source_uuid
        s = self.api.get_source(Source(uuid=source_uuid))

        subs = self.api.get_submissions(s)
        sub = self.api.get_submission_from_string(subs[0].uuid, s.uuid)
        self.assertEqual(sub.filename, subs[0].filename)

    @vcr.use_cassette("data/test-get-wrong-submissions.yml")
    def test_get_wrong_submissions(self):
        s = self.api.get_sources()[0]
        s.uuid = "rofl-missing"
        with self.assertRaises(WrongUUIDError):
            self.api.get_submissions(s)

    @vcr.use_cassette("data/test-get-all-submissions.yml")
    def test_get_all_submissions(self):
        subs = self.api.get_all_submissions()
        for submission in subs:
            assert submission.filename

    @vcr.use_cassette("data/test-flag-source.yml")
    def test_flag_source(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.flag_source(s))
        # Now we will try to get the same source again
        s2 = self.api.get_source(s)
        self.assertTrue(s2.is_flagged)

    @vcr.use_cassette("data/test-delete-source.yml")
    def test_delete_source(self):
        number_of_sources_before = len(self.api.get_sources())

        s = self.api.get_sources()[0]
        self.assertTrue(self.api.delete_source(s))

        # Now there should be one less source
        sources = self.api.get_sources()
        self.assertEqual(len(sources), number_of_sources_before - 1)

    @vcr.use_cassette("data/test-delete-source.yml")
    def test_delete_source_from_string(self):
        number_of_sources_before = len(self.api.get_sources())

        s = self.api.get_sources()[0]
        self.assertTrue(self.api.delete_source_from_string(s.uuid))

        # Now there should be one less source
        sources = self.api.get_sources()
        self.assertEqual(len(sources), number_of_sources_before - 1)

    @vcr.use_cassette("data/test-delete-submission.yml")
    def test_delete_submission(self):
        number_of_submissions_before = len(self.api.get_all_submissions())

        subs = self.api.get_all_submissions()
        self.assertTrue(self.api.delete_submission(subs[0]))
        new_subs = self.api.get_all_submissions()
        # We now should have 1 less submission
        self.assertEqual(len(new_subs), number_of_submissions_before - 1)

        # Let us make sure that sub[0] is not there
        for s in new_subs:
            self.assertNotEqual(s.uuid, subs[0].uuid)

    @vcr.use_cassette("data/test-delete-submission-from-string.yml")
    def test_delete_submission_from_string(self):
        number_of_submissions_before = len(self.api.get_all_submissions())

        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)

        self.assertTrue(self.api.delete_submission(subs[0]))
        new_subs = self.api.get_all_submissions()
        # We now should have 1 less submission
        self.assertEqual(len(new_subs), number_of_submissions_before - 1)

        # Let us make sure that sub[0] is not there
        for s in new_subs:
            self.assertNotEqual(s.uuid, subs[0].uuid)

    @vcr.use_cassette("data/test-get-current-user.yml")
    def test_get_current_user(self):
        user = self.api.get_current_user()
        self.assertTrue(user["is_admin"])
        self.assertEqual(user["username"], "journalist")
        self.assertTrue("first_name" in user)
        self.assertTrue("last_name" in user)

    @vcr.use_cassette("data/test-get-users.yml")
    def test_get_users(self):
        users = self.api.get_users()
        for user in users:
            # Assert expected fields are present
            assert hasattr(user, "first_name")
            assert hasattr(user, "last_name")
            # Every user has a non-empty name and UUID
            assert user.username
            assert user.uuid
            # The API should never return these fields
            assert not hasattr(user, "last_login")
            assert not hasattr(user, "is_admin")

    @vcr.use_cassette("data/test-error-unencrypted-reply.yml")
    def test_error_unencrypted_reply(self):
        s = self.api.get_sources()[0]
        with self.assertRaises(ReplyError) as err:
            self.api.reply_source(s, "hello")

        self.assertEqual(err.exception.msg, "bad request")

    @vcr.use_cassette("data/test-reply-source.yml")
    def test_reply_source(self):
        s = self.api.get_sources()[0]
        dirname = os.path.dirname(__file__)
        with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj:
            data = fobj.read()

        reply = self.api.reply_source(s, data)
        assert isinstance(reply, Reply)
        assert reply.uuid
        assert reply.filename

    @vcr.use_cassette("data/test-reply-source-with-uuid.yml")
    def test_reply_source_with_uuid(self):
        s = self.api.get_sources()[0]
        dirname = os.path.dirname(__file__)
        with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj:
            data = fobj.read()

        msg_uuid = "e467868c-1fbb-4b5e-bca2-87944ea83855"
        reply = self.api.reply_source(s, data, msg_uuid)
        assert reply.uuid == msg_uuid

    @vcr.use_cassette("data/test-download-submission.yml")
    def test_download_submission(self):
        submissions = self.api.get_all_submissions()
        unread_submission = None
        for s in submissions:
            if not s.is_read:
                unread_submission = s
                break

        if not unread_submission:
            self.assertFalse(
                "There must be an unread submission in the db for this test to work."
            )

        self.assertFalse(unread_submission.is_read)

        # We need a temporary directory to download
        tmpdir = tempfile.mkdtemp()
        etag, filepath = self.api.download_submission(s, tmpdir)

        # now let us read the downloaded file
        with open(filepath, "rb") as fobj:
            content = fobj.read()

        # Verify the ETag contains the algorithm and the hash is correct
        hasher = hashlib.sha256()
        hasher.update(content)

        assert etag == "sha256:{}".format(hasher.hexdigest())

        # is_read should still be False as of SecureDrop 1.6.0 or later
        submission = self.api.get_submission(unread_submission)
        self.assertFalse(submission.is_read)

        # Let us remove the temporary directory
        shutil.rmtree(tmpdir)

    @vcr.use_cassette("data/test-get-replies-from-source.yml")
    def test_get_replies_from_source(self):
        s = self.api.get_sources()[0]
        replies = self.api.get_replies_from_source(s)
        self.assertEqual(len(replies), NUM_REPLIES_PER_SOURCE)

    @vcr.use_cassette("data/test-get-reply-from-source.yml")
    def test_get_reply_from_source(self):
        s = self.api.get_sources()[0]
        replies = self.api.get_replies_from_source(s)
        reply = replies[0]

        r = self.api.get_reply_from_source(s, reply.uuid)

        self.assertEqual(reply.filename, r.filename)
        self.assertEqual(reply.size, r.size)
        self.assertEqual(reply.reply_url, r.reply_url)
        self.assertEqual(reply.journalist_username, r.journalist_username)

    @vcr.use_cassette("data/test-get-all-replies.yml")
    def test_get_all_replies(self):
        num_sources = len(self.api.get_sources())
        replies = self.api.get_all_replies()
        self.assertEqual(len(replies), NUM_REPLIES_PER_SOURCE * num_sources)

    @vcr.use_cassette("data/test-download-reply.yml")
    def test_download_reply(self):
        r = self.api.get_all_replies()[0]

        # We need a temporary directory to download
        tmpdir = tempfile.mkdtemp()
        etag, filepath = self.api.download_reply(r, tmpdir)

        # now let us read the downloaded file
        with open(filepath, "rb") as fobj:
            content = fobj.read()

        # Verify the ETag contains the algorithm and the hash is correct
        hasher = hashlib.sha256()
        hasher.update(content)

        assert etag == "sha256:{}".format(hasher.hexdigest())

        # Let us remove the temporary directory
        shutil.rmtree(tmpdir)

    @vcr.use_cassette("data/test-delete-reply.yml")
    def test_delete_reply(self):
        r = self.api.get_all_replies()[0]

        number_of_replies_before = len(self.api.get_all_replies())

        self.assertTrue(self.api.delete_reply(r))

        # We deleted one, so there must be 1 less reply now
        self.assertEqual(len(self.api.get_all_replies()),
                         number_of_replies_before - 1)

    @vcr.use_cassette("data/test-logout.yml")
    def test_zlogout(self):
        r = self.api.logout()
        self.assertTrue(r)
コード例 #8
0
class TestAPIProxy(unittest.TestCase):
    @dastollervey_datasaver
    def setUp(self):
        self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L")
        self.username = "******"
        self.password = "******"
        self.server = "http://localhost:8081/"
        self.api = API(self.server,
                       self.username,
                       self.password,
                       str(self.totp.now()),
                       proxy=True)

        for i in range(3):
            if os.path.exists("testtoken.json"):
                token = load_auth()
                self.api.token = token
                self.api.update_auth_header()
                break

            # The following is True when we did a logout
            if os.path.exists("logout.txt"):
                os.unlink("logout.txt")
                time.sleep(31)
            # Now let us try to login
            try:
                self.api = API(self.server,
                               self.username,
                               self.password,
                               str(self.totp.now()),
                               proxy=True)
                self.api.authenticate()
                with open("login.txt", "w") as fobj:
                    fobj.write("in")
            except Exception as err:
                print(err)
                time.sleep(31)
                continue

            save_auth(self.api.token)
            break

    def test_api_auth(self):

        self.assertTrue(isinstance(self.api.token, str))
        self.assertTrue(
            isinstance(self.api.token_expiration, datetime.datetime))
        self.assertTrue(isinstance(self.api.token_journalist_uuid, str))
        self.assertTrue(isinstance(self.api.first_name, (str, type(None))))
        self.assertTrue(isinstance(self.api.last_name, (str, type(None))))

    @dastollervey_datasaver
    def test_seen(self):
        submissions = self.api.get_all_submissions()
        replies = self.api.get_all_replies()

        file_uuids = []
        message_uuids = []
        reply_uuids = []

        for submission in submissions:
            if submission.is_file():
                file_uuids.append(submission.uuid)
            else:
                message_uuids.append(submission.uuid)

        for reply in replies:
            reply_uuids.append(reply.uuid)

        self.assertTrue(
            self.api.seen(files=file_uuids,
                          messages=message_uuids,
                          replies=reply_uuids))

    @dastollervey_datasaver
    def test_get_sources(self):
        sources = self.api.get_sources()
        for source in sources:
            # Assert expected fields are present
            assert source.journalist_designation
            assert source.uuid
            assert source.last_updated

    @dastollervey_datasaver
    def test_star_add_remove(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.add_star(s))
        self.assertTrue(self.api.remove_star(s))
        for source in self.api.get_sources():
            if source.uuid == s.uuid:
                self.assertFalse(source.is_starred)

    @dastollervey_datasaver
    def test_get_single_source(self):
        s = self.api.get_sources()[0]
        # Now we will try to get the same source again
        s2 = self.api.get_source(s)

        self.assertEqual(s.journalist_designation, s2.journalist_designation)
        self.assertEqual(s.uuid, s2.uuid)

    @dastollervey_datasaver
    def test_get_single_source_from_string(self):
        s = self.api.get_sources()[0]
        # Now we will try to get the same source again using uuid
        s2 = self.api.get_source_from_string(s.uuid)

        self.assertEqual(s.journalist_designation, s2.journalist_designation)
        self.assertEqual(s.uuid, s2.uuid)

    @dastollervey_datasaver
    def test_failed_single_source(self):
        with self.assertRaises(WrongUUIDError):
            self.api.get_source(Source(uuid="not there"))

    @dastollervey_datasaver
    def test_get_submissions(self):
        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)
        for submission in subs:
            assert submission.filename

    @dastollervey_datasaver
    def test_get_submission(self):
        # Get a source with submissions
        source_uuid = self.api.get_all_submissions()[0].source_uuid
        s = self.api.get_source(Source(uuid=source_uuid))

        subs = self.api.get_submissions(s)
        sub = self.api.get_submission(subs[0])
        self.assertEqual(sub.filename, subs[0].filename)

    @dastollervey_datasaver
    def test_get_submission_from_string(self):
        # Get a source with submissions
        source_uuid = self.api.get_all_submissions()[0].source_uuid
        s = self.api.get_source(Source(uuid=source_uuid))

        subs = self.api.get_submissions(s)
        sub = self.api.get_submission_from_string(subs[0].uuid, s.uuid)
        self.assertEqual(sub.filename, subs[0].filename)

    @dastollervey_datasaver
    def test_get_wrong_submissions(self):
        s = self.api.get_sources()[0]
        s.uuid = "rofl-missing"
        with self.assertRaises(WrongUUIDError):
            self.api.get_submissions(s)

    @dastollervey_datasaver
    def test_get_all_submissions(self):
        subs = self.api.get_all_submissions()
        for submission in subs:
            assert submission.filename

    @dastollervey_datasaver
    def test_flag_source(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.flag_source(s))
        # Now we will try to get the same source again
        s2 = self.api.get_source(s)
        self.assertTrue(s2.is_flagged)

    @dastollervey_datasaver
    def test_delete_source(self):
        number_of_sources_before = len(self.api.get_sources())

        s = self.api.get_sources()[0]
        self.assertTrue(self.api.delete_source(s))

        # Now there should be one less source
        sources = self.api.get_sources()
        self.assertEqual(len(sources), number_of_sources_before - 1)

    @dastollervey_datasaver
    def test_delete_source_from_string(self):
        number_of_sources_before = len(self.api.get_sources())

        s = self.api.get_sources()[0]
        self.assertTrue(self.api.delete_source_from_string(s.uuid))

        # Now there should be one less source
        sources = self.api.get_sources()
        self.assertEqual(len(sources), number_of_sources_before - 1)

    @dastollervey_datasaver
    def test_delete_submission(self):
        number_of_submissions_before = len(self.api.get_all_submissions())

        subs = self.api.get_all_submissions()
        self.assertTrue(self.api.delete_submission(subs[0]))
        new_subs = self.api.get_all_submissions()
        # We now should have 1 less submission
        self.assertEqual(len(new_subs), number_of_submissions_before - 1)

        # Let us make sure that sub[0] is not there
        for s in new_subs:
            self.assertNotEqual(s.uuid, subs[0].uuid)

    @dastollervey_datasaver
    def test_delete_submission_from_string(self):
        number_of_submissions_before = len(self.api.get_all_submissions())

        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)

        self.assertTrue(self.api.delete_submission(subs[0]))
        new_subs = self.api.get_all_submissions()
        # We now should have 1 less submission
        self.assertEqual(len(new_subs), number_of_submissions_before - 1)

        # Let us make sure that sub[0] is not there
        for s in new_subs:
            self.assertNotEqual(s.uuid, subs[0].uuid)

    @dastollervey_datasaver
    def test_get_current_user(self):
        user = self.api.get_current_user()
        self.assertTrue(user["is_admin"])
        self.assertEqual(user["username"], "journalist")
        self.assertTrue("first_name" in user)
        self.assertTrue("last_name" in user)

    @dastollervey_datasaver
    def test_get_users(self):
        users = self.api.get_users()
        for user in users:
            # Assert expected fields are present
            assert hasattr(user, "first_name")
            assert hasattr(user, "last_name")
            # Every user has a non-empty name and UUID
            assert user.username
            assert user.uuid
            # The API should never return these fields
            assert not hasattr(user, "last_login")
            assert not hasattr(user, "is_admin")

    @dastollervey_datasaver
    def test_error_unencrypted_reply(self):
        s = self.api.get_sources()[0]
        with self.assertRaises(ReplyError) as err:
            self.api.reply_source(s, "hello")

        self.assertEqual(err.exception.msg, "bad request")

    @dastollervey_datasaver
    def test_reply_source(self):
        s = self.api.get_sources()[0]
        dirname = os.path.dirname(__file__)
        with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj:
            data = fobj.read()

        reply = self.api.reply_source(s, data)
        assert isinstance(reply, Reply)
        assert reply.uuid
        assert reply.filename

    @dastollervey_datasaver
    def test_reply_source_with_uuid(self):
        s = self.api.get_sources()[0]
        dirname = os.path.dirname(__file__)
        with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj:
            data = fobj.read()

        msg_uuid = "e467868c-1fbb-4b5e-bca2-87944ea83855"
        reply = self.api.reply_source(s, data, msg_uuid)
        assert reply.uuid == msg_uuid

    @dastollervey_datasaver
    def test_download_submission(self):
        s = self.api.get_all_submissions()[0]

        self.assertFalse(s.is_read)

        # We need a temporary directory to download
        tmpdir = tempfile.mkdtemp()
        _, filepath = self.api.download_submission(s, tmpdir)

        # Uncomment the following part only on QubesOS
        # for testing against real server.
        # now let us read the downloaded file
        # with open(filepath, "rb") as fobj:
        #    fobj.read()

        # is_read should still be False as of SecureDrop 1.6.0 or later.

        s = self.api.get_submission(s)
        self.assertFalse(s.is_read)

        # Let us remove the temporary directory
        shutil.rmtree(tmpdir)

    @dastollervey_datasaver
    def test_get_replies_from_source(self):
        s = self.api.get_sources()[0]
        replies = self.api.get_replies_from_source(s)
        self.assertEqual(len(replies), NUM_REPLIES_PER_SOURCE)

    @dastollervey_datasaver
    def test_get_reply_from_source(self):
        s = self.api.get_sources()[0]
        replies = self.api.get_replies_from_source(s)
        reply = replies[0]

        r = self.api.get_reply_from_source(s, reply.uuid)

        self.assertEqual(reply.filename, r.filename)
        self.assertEqual(reply.size, r.size)
        self.assertEqual(reply.reply_url, r.reply_url)
        self.assertEqual(reply.journalist_username, r.journalist_username)

    @dastollervey_datasaver
    def test_get_all_replies(self):
        num_sources = len(self.api.get_sources())
        replies = self.api.get_all_replies()
        self.assertEqual(len(replies), NUM_REPLIES_PER_SOURCE * num_sources)

    @dastollervey_datasaver
    def test_download_reply(self):
        r = self.api.get_all_replies()[0]

        # We need a temporary directory to download
        tmpdir = tempfile.mkdtemp()
        _, filepath = self.api.download_reply(r, tmpdir)

        # Uncomment the following part only on QubesOS
        # for testing against real server.
        # now let us read the downloaded file
        # with open(filepath, "rb") as fobj:
        #     fobj.read()

        # Let us remove the temporary directory
        shutil.rmtree(tmpdir)

    @dastollervey_datasaver
    def test_delete_reply(self):
        r = self.api.get_all_replies()[0]

        number_of_replies_before = len(self.api.get_all_replies())

        self.assertTrue(self.api.delete_reply(r))

        # We deleted one, so there must be 1 less reply now
        self.assertEqual(len(self.api.get_all_replies()),
                         number_of_replies_before - 1)

    @dastollervey_datasaver
    def test_logout(self):
        r = self.api.logout()
        self.assertTrue(r)
        if os.path.exists("login.txt"):
            os.unlink("login.txt")
        if os.path.exists("testtoken.json"):
            os.unlink("testtoken.json")
        with open("logout.txt", "w") as fobj:
            fobj.write("out")
コード例 #9
0
class TestAPI(unittest.TestCase):
    @vcr.use_cassette("data/test-setup.yml")
    def setUp(self):
        self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L")
        self.username = "******"
        self.password = "******"
        self.server = "http://*****:*****@vcr.use_cassette("data/test-get-sources.yml")
    def test_get_sources(self):
        sources = self.api.get_sources()
        self.assertEqual(len(sources), 2)

    @vcr.use_cassette("data/test-star-add-remove.yml")
    def test_star_add_remove(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.add_star(s))
        self.assertTrue(self.api.remove_star(s))
        for source in self.api.get_sources():
            if source.uuid == s.uuid:
                self.assertFalse(source.is_starred)

    @vcr.use_cassette("data/test-get-single-source.yml")
    def test_get_single_source(self):
        s = self.api.get_sources()[0]
        # Now we will try to get the same source again
        s2 = self.api.get_source(s)

        self.assertEqual(s.journalist_designation, s2.journalist_designation)
        self.assertEqual(s.uuid, s2.uuid)

    @vcr.use_cassette("data/test-get-single-source.yml")
    def test_get_single_source_from_string(self):
        s = self.api.get_sources()[0]
        # Now we will try to get the same source again using uuid
        s2 = self.api.get_source_from_string(s.uuid)

        self.assertEqual(s.journalist_designation, s2.journalist_designation)
        self.assertEqual(s.uuid, s2.uuid)

    @vcr.use_cassette("data/test-failed-single-source.yml")
    def test_failed_single_source(self):
        with self.assertRaises(WrongUUIDError):
            self.api.get_source(Source(uuid="not there"))

    @vcr.use_cassette("data/test-get-submissions.yml")
    def test_get_submissions(self):
        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)
        self.assertEqual(len(subs), 2)

    @vcr.use_cassette("data/test-get-submission.yml")
    def test_get_submission(self):
        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)
        sub = self.api.get_submission(subs[0])
        self.assertEqual(sub.filename, subs[0].filename)

    @vcr.use_cassette("data/test-get-submission.yml")
    def test_get_submission_from_string(self):
        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)
        sub = self.api.get_submission_from_string(subs[0].uuid, s.uuid)
        self.assertEqual(sub.filename, subs[0].filename)

    @vcr.use_cassette("data/test-get-wrong-submissions.yml")
    def test_get_wrong_submissions(self):
        s = self.api.get_sources()[0]
        s.uuid = "rofl-missing"
        with self.assertRaises(WrongUUIDError):
            self.api.get_submissions(s)

    @vcr.use_cassette("data/test-get-all-submissions.yml")
    def test_get_all_submissions(self):
        subs = self.api.get_all_submissions()
        self.assertEqual(len(subs), 4)

    @vcr.use_cassette("data/test-flag-source.yml")
    def test_flag_source(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.flag_source(s))
        # Now we will try to get the same source again
        s2 = self.api.get_source(s)
        self.assertTrue(s2.is_flagged)

    @vcr.use_cassette("data/test-delete-source.yml")
    def test_delete_source(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.delete_source(s))

        # Now there should be one source left
        sources = self.api.get_sources()
        self.assertEqual(len(sources), 1)

    @vcr.use_cassette("data/test-delete-source.yml")
    def test_delete_source_from_string(self):
        s = self.api.get_sources()[0]
        self.assertTrue(self.api.delete_source_from_string(s.uuid))

        # Now there should be one source left
        sources = self.api.get_sources()
        self.assertEqual(len(sources), 1)

    @vcr.use_cassette("data/test-delete-submission.yml")
    def test_delete_submission(self):
        subs = self.api.get_all_submissions()
        self.assertTrue(self.api.delete_submission(subs[0]))
        new_subs = self.api.get_all_submissions()
        # We now should have 3 submissions
        self.assertEqual(len(new_subs), 3)

        # Let us make sure that sub[0] is not there
        for s in new_subs:
            self.assertNotEqual(s.uuid, subs[0].uuid)

    @vcr.use_cassette("data/test-delete-submission-from-string.yml")
    def test_delete_submission_from_string(self):
        s = self.api.get_sources()[0]

        subs = self.api.get_submissions(s)

        self.assertTrue(self.api.delete_submission(subs[0]))
        new_subs = self.api.get_all_submissions()
        # We now should have 3 submissions
        self.assertEqual(len(new_subs), 3)

        # Let us make sure that sub[0] is not there
        for s in new_subs:
            self.assertNotEqual(s.uuid, subs[0].uuid)

    @vcr.use_cassette("data/test-get-current-user.yml")
    def test_get_current_user(self):
        user = self.api.get_current_user()
        self.assertTrue(user["is_admin"])
        self.assertEqual(user["username"], "journalist")

    @vcr.use_cassette("data/test-error-unencrypted-reply.yml")
    def test_error_unencrypted_reply(self):
        s = self.api.get_sources()[0]
        with self.assertRaises(ReplyError) as err:
            self.api.reply_source(s, "hello")

        self.assertEqual(err.exception.msg,
                         "You must encrypt replies client side")

    @vcr.use_cassette("data/test-reply-source.yml")
    def test_reply_source(self):
        s = self.api.get_sources()[0]
        dirname = os.path.dirname(__file__)
        with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj:
            data = fobj.read()

        reply = self.api.reply_source(s, data)
        assert isinstance(reply, Reply)
        assert reply.uuid
        assert reply.filename

    @vcr.use_cassette("data/test-reply-source-with-uuid.yml")
    def test_reply_source_with_uuid(self):
        s = self.api.get_sources()[0]
        dirname = os.path.dirname(__file__)
        with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj:
            data = fobj.read()

        msg_uuid = "e467868c-1fbb-4b5e-bca2-87944ea83855"
        reply = self.api.reply_source(s, data, msg_uuid)
        assert reply.uuid == msg_uuid

    @vcr.use_cassette("data/test-download-submission.yml")
    def test_download_submission(self):
        s = self.api.get_all_submissions()[0]

        self.assertFalse(s.is_read)

        # We need a temporary directory to download
        tmpdir = tempfile.mkdtemp()
        _, filepath = self.api.download_submission(s, tmpdir)

        # now let us read the downloaded file
        with open(filepath, "rb") as fobj:
            fobj.read()

        # Now the submission should have is_read as True.

        s = self.api.get_submission(s)
        self.assertTrue(s.is_read)

        # Let us remove the temporary directory
        shutil.rmtree(tmpdir)

    @vcr.use_cassette("data/test-get-replies-from-source.yml")
    def test_get_replies_from_source(self):
        s = self.api.get_sources()[0]
        replies = self.api.get_replies_from_source(s)
        self.assertEqual(len(replies), 2)

    @vcr.use_cassette("data/test-get-reply-from-source.yml")
    def test_get_reply_from_source(self):
        s = self.api.get_sources()[0]
        replies = self.api.get_replies_from_source(s)
        reply = replies[0]

        r = self.api.get_reply_from_source(s, reply.uuid)

        self.assertEqual(reply.filename, r.filename)
        self.assertEqual(reply.size, r.size)
        self.assertEqual(reply.reply_url, r.reply_url)
        self.assertEqual(reply.journalist_username, r.journalist_username)

    @vcr.use_cassette("data/test-get-all-replies.yml")
    def test_get_all_replies(self):
        replies = self.api.get_all_replies()
        self.assertEqual(len(replies), 4)

    @vcr.use_cassette("data/test-download-reply.yml")
    def test_download_reply(self):
        r = self.api.get_all_replies()[0]

        # We need a temporary directory to download
        tmpdir = tempfile.mkdtemp()
        _, filepath = self.api.download_reply(r, tmpdir)

        # now let us read the downloaded file
        with open(filepath, "rb") as fobj:
            fobj.read()

        # Let us remove the temporary directory
        shutil.rmtree(tmpdir)

    @vcr.use_cassette("data/test-delete-reply.yml")
    def test_delete_reply(self):
        r = self.api.get_all_replies()[0]

        self.assertTrue(self.api.delete_reply(r))

        # We deleted one, so there must be 3 replies left
        self.assertEqual(len(self.api.get_all_replies()), 3)