コード例 #1
0
ファイル: test_api.py プロジェクト: creviera/securedrop-sdk
def test_download_reply_timeout(mocker):
    api = API("mock", "mock", "mock", "mock", proxy=False)
    mocker.patch("sdclientapi.requests.request",
                 side_effect=RequestTimeoutError)
    with pytest.raises(RequestTimeoutError):
        r = Reply(uuid="humanproblem", filename="secret.txt")
        api.download_reply(r)
コード例 #2
0
ファイル: test_api.py プロジェクト: creviera/securedrop-sdk
def test_download_submission_timeout(mocker):
    api = API("mock", "mock", "mock", "mock", proxy=False)
    mocker.patch("sdclientapi.requests.request",
                 side_effect=RequestTimeoutError)
    with pytest.raises(RequestTimeoutError):
        s = Submission(uuid="climateproblem")
        api.download_submission(s)
コード例 #3
0
def get_remote_data(
        api: API
) -> Tuple[List[SDKSource], List[SDKSubmission], List[SDKReply]]:
    """
    Given an authenticated connection to the SecureDrop API, get sources,
    submissions and replies from the remote server and return a tuple
    containing lists of objects representing this data:

    (remote_sources, remote_submissions, remote_replies)
    """
    remote_submissions = []  # type: List[SDKSubmission]
    try:
        remote_sources = api.get_sources()
        for source in remote_sources:
            remote_submissions.extend(api.get_submissions(source))
        remote_replies = api.get_all_replies()
    except Exception as ex:
        # Log any errors but allow the caller to handle the exception.
        logger.error(ex)
        raise (ex)

    logger.info('Fetched {} remote sources.'.format(len(remote_sources)))
    logger.info('Fetched {} remote submissions.'.format(
        len(remote_submissions)))
    logger.info('Fetched {} remote replies.'.format(len(remote_replies)))

    return (remote_sources, remote_submissions, remote_replies)
コード例 #4
0
    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.")
コード例 #5
0
ファイル: updatestar.py プロジェクト: ntoll/securedrop-client
    def call_api(self, api_client: API, session: Session) -> str:
        '''
        Override ApiJob.

        Star or Unstar an user on the server
        '''
        try:
            source_sdk_object = sdclientapi.Source(uuid=self.source_uuid)

            # TODO: Once https://github.com/freedomofpress/securedrop-client/issues/648, we will
            # want to pass the default request timeout to remove_star and add_star instead of
            # setting it on the api object directly.
            api_client.default_request_timeout = 5
            if self.is_starred:
                api_client.remove_star(source_sdk_object)
            else:
                api_client.add_star(source_sdk_object)

            return self.source_uuid
        except (RequestTimeoutError, ServerConnectionError) as e:
            error_message = f'Failed to update star on source {self.source_uuid} due to error: {e}'
            raise UpdateStarJobTimeoutError(error_message, self.source_uuid)
        except Exception as e:
            error_message = f'Failed to update star on source {self.source_uuid} due to {e}'
            raise UpdateStarJobError(error_message, self.source_uuid)
コード例 #6
0
    def _make_call(self, encrypted_reply: str,
                   api_client: API) -> sdclientapi.Reply:
        sdk_source = sdclientapi.Source(uuid=self.source_uuid)

        # TODO: Once https://github.com/freedomofpress/securedrop-client/issues/648, we will want to
        # pass the default request timeout to reply_source instead of setting it on the api object
        # directly.
        api_client.default_request_timeout = 5
        return api_client.reply_source(sdk_source, encrypted_reply,
                                       self.reply_uuid)
コード例 #7
0
ファイル: downloads.py プロジェクト: ntoll/securedrop-client
    def call_download_api(self, api: API, db_object: Reply) -> Tuple[str, str]:
        '''
        Override DownloadJob.
        '''
        sdk_object = SdkReply(uuid=db_object.uuid, filename=db_object.filename)
        sdk_object.source_uuid = db_object.source.uuid

        # TODO: Once https://github.com/freedomofpress/securedrop-sdk/issues/108 is implemented, we
        # will want to pass the default request timeout to download_reply instead of setting it on
        # the api object directly.
        api.default_request_timeout = 20
        return api.download_reply(sdk_object)
コード例 #8
0
def test_download_get_sources_error_request_timeout(mocker):
    api = API("mock", "mock", "mock", "mock", True)
    mocker.patch(
        "sdclientapi.json_query",
        return_value=(json.dumps({
            "body": json.dumps({"error": "wah"}),
            "status": http.HTTPStatus.GATEWAY_TIMEOUT,
            "headers": "foo",
        })),
    )
    with pytest.raises(RequestTimeoutError):
        api.get_sources()
コード例 #9
0
def test_filename_key_not_in_download_response(mocker):
    api = API("mock", "mock", "mock", "mock", True)
    s = Submission(uuid="foobar")
    mocker.patch(
        "sdclientapi.json_query",
        return_value=(json.dumps({
            "body": json.dumps({"error": "wah"}),
            "status": 200,
            "headers": "foo"
        })),
    )
    with pytest.raises(BaseError):
        api.download_submission(s)
コード例 #10
0
def test_download_submission_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):
        s = Submission(uuid="climateproblem")
        api.download_submission(s)
コード例 #11
0
def test_download_reply_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):
        r = Reply(uuid="humanproblem", filename="secret.txt")
        api.download_reply(r)
コード例 #12
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()
コード例 #13
0
 def call_download_api(self, api: API, db_object: Reply) -> Tuple[str, str]:
     '''
     Override DownloadJob.
     '''
     sdk_object = SdkReply(uuid=db_object.uuid, filename=db_object.filename)
     sdk_object.source_uuid = db_object.source.uuid
     return api.download_reply(sdk_object)
コード例 #14
0
ファイル: sources.py プロジェクト: creviera/pyqt-sandbox
    def call_api(self, api_client: API, session: Session) -> str:
        '''
        Override ApiJob.

        Delete a source on the server
        '''
        try:
            source_sdk_object = sdclientapi.Source(uuid=self.source_uuid)
            api_client.delete_source(source_sdk_object)

            return self.source_uuid
        except (RequestTimeoutError, ServerConnectionError):
            raise
        except Exception as e:
            error_message = "Failed to delete source {uuid} due to {exception}".format(
                uuid=self.source_uuid, exception=repr(e))
            raise DeleteSourceJobException(error_message, self.source_uuid)
コード例 #15
0
 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))
コード例 #16
0
ファイル: storage.py プロジェクト: rmol/securedrop-client
def get_remote_data(api: API) -> Tuple[List[SDKSource], List[SDKSubmission], List[SDKReply]]:
    """
    Given an authenticated connection to the SecureDrop API, get sources,
    submissions and replies from the remote server and return a tuple
    containing lists of objects representing this data:

    (remote_sources, remote_submissions, remote_replies)
    """
    remote_sources = api.get_sources()
    remote_submissions = api.get_all_submissions()
    remote_replies = api.get_all_replies()

    logger.info("Fetched {} remote sources.".format(len(remote_sources)))
    logger.info("Fetched {} remote submissions.".format(len(remote_submissions)))
    logger.info("Fetched {} remote replies.".format(len(remote_replies)))

    return (remote_sources, remote_submissions, remote_replies)
コード例 #17
0
 def call_download_api(self, api: API, db_object: File) -> Tuple[str, str]:
     '''
     Override DownloadJob.
     '''
     sdk_object = SdkSubmission(uuid=db_object.uuid)
     sdk_object.source_uuid = db_object.source.uuid
     sdk_object.filename = db_object.filename
     return api.download_submission(sdk_object,
                                    timeout=self._get_realistic_timeout(db_object.size))
コード例 #18
0
    def call_api(self, api_client: API, session: Session) -> str:
        '''
        Override ApiJob.

        Star or Unstar an user on the server
        '''
        try:
            source_sdk_object = sdclientapi.Source(uuid=self.source_uuid)

            if self.star_status:
                api_client.remove_star(source_sdk_object)
            else:
                api_client.add_star(source_sdk_object)

            return self.source_uuid
        except Exception as e:
            error_message = "Failed to update star on source {uuid} due to {exception}".format(
                uuid=self.source_uuid, exception=repr(e))
            raise UpdateStarJobException(error_message, self.source_uuid)
コード例 #19
0
ファイル: test_api.py プロジェクト: creviera/securedrop-sdk
    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
コード例 #20
0
    def call_api(self, api_client: API, session: Session) -> str:
        """
        Override ApiJob.

        Star or Unstar an user on the server
        """
        try:
            source_sdk_object = sdclientapi.Source(uuid=self.uuid)

            if self.is_starred:
                api_client.remove_star(source_sdk_object)
            else:
                api_client.add_star(source_sdk_object)

            return self.uuid
        except (RequestTimeoutError, ServerConnectionError) as e:
            error_message = f"Failed to update star on source {self.uuid} due to error: {e}"
            raise UpdateStarJobTimeoutError(error_message, self.uuid)
        except Exception as e:
            error_message = f"Failed to update star on source {self.uuid} due to {e}"
            raise UpdateStarJobError(error_message, self.uuid)
コード例 #21
0
    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
コード例 #22
0
ファイル: sources.py プロジェクト: ntoll/securedrop-client
    def call_api(self, api_client: API, session: Session) -> str:
        '''
        Override ApiJob.

        Delete a source on the server
        '''
        try:
            source_sdk_object = sdclientapi.Source(uuid=self.source_uuid)

            # TODO: After https://github.com/freedomofpress/securedrop-client/issues/648 is
            # merged, we will want to pass the timeout to delete_source instead of setting
            # it on the api object
            api_client.default_request_timeout = 5
            api_client.delete_source(source_sdk_object)

            return self.source_uuid
        except (RequestTimeoutError, ServerConnectionError):
            raise
        except Exception as e:
            error_message = "Failed to delete source {uuid} due to {exception}".format(
                uuid=self.source_uuid, exception=repr(e))
            raise DeleteSourceJobException(error_message, self.source_uuid)
コード例 #23
0
ファイル: sync.py プロジェクト: ntoll/securedrop-client
    def call_api(self, api_client: API, session: Session) -> Any:
        '''
        Override ApiJob.

        Download new metadata, update the local database, import new keys, and
        then the success signal will let the controller know to add any new download
        jobs.
        '''

        # TODO: Once https://github.com/freedomofpress/securedrop-client/issues/648, we will want to
        # pass the default request timeout to api calls instead of setting it on the api object
        # directly.
        api_client.default_request_timeout = 40
        remote_sources, remote_submissions, remote_replies = get_remote_data(
            api_client)

        update_local_storage(session, self.gpg, remote_sources,
                             remote_submissions, remote_replies, self.data_dir)
コード例 #24
0
 def test_auth_baduser(self):
     self.api = API(self.server, "no", self.password, str(self.totp.now()))
     with pytest.raises(AuthError):
         self.api.authenticate()
コード例 #25
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)
コード例 #26
0
ファイル: test_api.py プロジェクト: creviera/securedrop-sdk
 def test_auth_badpassword(self):
     self.api = API(self.server, self.username, "no", str(self.totp.now()))
     with self.assertRaises(AuthError):
         self.api.authenticate()
コード例 #27
0
ファイル: test_api.py プロジェクト: creviera/securedrop-sdk
 def test_auth_badotp(self):
     self.api = API(self.server, self.username, self.password, "no")
     with self.assertRaises(AuthError):
         self.api.authenticate()
コード例 #28
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()
コード例 #29
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
コード例 #30
0
 def _make_call(self, encrypted_reply: str,
                api_client: API) -> sdclientapi.Reply:
     sdk_source = sdclientapi.Source(uuid=self.source_uuid)
     return api_client.reply_source(sdk_source, encrypted_reply,
                                    self.reply_uuid)