From f1dd9ec2c61dbf9e0512ebced5f39ff81c1b2395 Mon Sep 17 00:00:00 2001 From: Kairo de Araujo Date: Thu, 23 Feb 2023 09:42:35 +0100 Subject: [PATCH] remove the pinned starlette version It removes the pinned starlette dependency version This pinned version was applied as a workaround for issue #183 The startelle version is updated to the latest version, which fixes a security issue https://github.com/advisories/GHSA-74m5-2c7w-9w3x The solution requires to install the new `httpx` development dependency to support the unit tests using the FastAPI TestClient as described in this link: https://fastapi.tiangolo.com/tutorial/testing/ A workaround needs to be applied to the tests which uses `DELETE` method. Signed-off-by: Kairo de Araujo --- Pipfile | 2 +- Pipfile.lock | 66 ++++++++++++++++++++++++++++---- requirements-dev.txt | 16 +++++--- requirements.txt | 4 +- tests/unit/api/test_bootstrap.py | 13 +++---- tests/unit/api/test_targets.py | 36 ++++++++++++++--- tests/unit/test_app.py | 4 +- 7 files changed, 109 insertions(+), 32 deletions(-) diff --git a/Pipfile b/Pipfile index fe7b6945..dea23ed7 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,6 @@ python-jose = "*" sqlalchemy = "*" redis = "*" bcrypt = "*" -starlette = "==0.20.4" [dev-packages] black = "*" @@ -40,6 +39,7 @@ mistune = "==0.8.4" myst-parser = "*" pre-commit = "*" bandit = "*" +httpx = "*" [requires] python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock index 0e61c0f8..07d8dc0d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "255e248594a4508591ee52218c927217bdbb818f5a372654d173685a9c3e88aa" + "sha256": "7806a372cdcab0ffcd7f901ad9cf907793d4107367f4b7dbfdb4ff70525132e2" }, "pipfile-spec": 6, "requires": { @@ -139,11 +139,11 @@ }, "fastapi": { "hashes": [ - "sha256:1020d7ca205d8b95813881fb3282e9c3656e47993531af3aa4ae11065b61dd2c", - "sha256:cdcaff84ecf7ae939b9579f0c98b0a0989ee3dd855710a32bc985260d92612f6" + "sha256:023a0f5bd2c8b2609014d3bba1e14a1d7df96c6abea0a73070621c9862b9a4de", + "sha256:ae7b97c778e2f2ec3fb3cb4fb14162129411d99907fb71920f6d69a524340ebf" ], "index": "pypi", - "version": "==0.86.0" + "version": "==0.92.0" }, "greenlet": { "hashes": [ @@ -406,11 +406,11 @@ }, "starlette": { "hashes": [ - "sha256:42fcf3122f998fefce3e2c5ad7e5edbf0f02cf685d646a83a08d404726af5084", - "sha256:c0414d5a56297d37f3db96a84034d61ce29889b9eaccf65eb98a0b39441fcaa3" + "sha256:774f1df1983fd594b9b6fb3ded39c2aa1979d10ac45caac0f4255cbe2acb8628", + "sha256:854c71e73736c429c2bdb07801f2c76c9cba497e7c3cf4988fde5e95fe4cdb3c" ], - "index": "pypi", - "version": "==0.20.4" + "markers": "python_version >= '3.7'", + "version": "==0.25.0" }, "typing-extensions": { "hashes": [ @@ -453,6 +453,14 @@ "markers": "python_version >= '3.6'", "version": "==0.7.13" }, + "anyio": { + "hashes": [ + "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421", + "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3" + ], + "markers": "python_full_version >= '3.6.2'", + "version": "==3.6.2" + }, "attrs": { "hashes": [ "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", @@ -860,6 +868,30 @@ "markers": "python_version >= '3.7'", "version": "==3.1.31" }, + "h11": { + "hashes": [ + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" + ], + "markers": "python_version >= '3.7'", + "version": "==0.14.0" + }, + "httpcore": { + "hashes": [ + "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb", + "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0" + ], + "markers": "python_version >= '3.7'", + "version": "==0.16.3" + }, + "httpx": { + "hashes": [ + "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9", + "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6" + ], + "index": "pypi", + "version": "==0.23.3" + }, "identify": { "hashes": [ "sha256:89e144fa560cc4cffb6ef2ab5e9fb18ed9f9b3cb054384bab4b95c12f6c309fe", @@ -1239,6 +1271,16 @@ "markers": "python_version >= '3.7' and python_version < '4'", "version": "==2.28.2" }, + "rfc3986": { + "extras": [ + "idna2008" + ], + "hashes": [ + "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835", + "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97" + ], + "version": "==1.5.0" + }, "setuptools": { "hashes": [ "sha256:e5fd0a713141a4a105412233c63dc4e17ba0090c8e8334594ac790ec97792330", @@ -1263,6 +1305,14 @@ "markers": "python_version >= '3.6'", "version": "==5.0.0" }, + "sniffio": { + "hashes": [ + "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", + "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.0" + }, "snowballstemmer": { "hashes": [ "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", diff --git a/requirements-dev.txt b/requirements-dev.txt index ff16ed29..17551fb5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,6 @@ -i https://pypi.org/simple alabaster==0.7.13 ; python_version >= '3.6' +anyio==3.6.2 ; python_full_version >= '3.6.2' attrs==22.2.0 ; python_version >= '3.6' babel==2.11.0 ; python_version >= '3.6' bandit==1.7.4 @@ -21,6 +22,9 @@ filelock==3.9.0 ; python_version >= '3.7' flake8==6.0.0 gitdb==4.0.10 ; python_version >= '3.7' gitpython==3.1.31 ; python_version >= '3.7' +h11==0.14.0 ; python_version >= '3.7' +httpcore==0.16.3 ; python_version >= '3.7' +httpx==0.23.3 identify==2.5.18 ; python_version >= '3.7' idna==3.4 ; python_version >= '3.5' imagesize==1.4.1 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' @@ -55,9 +59,14 @@ pytest==7.2.1 pytz==2022.7.1 pyyaml==6.0 ; python_version >= '3.6' requests==2.28.2 ; python_version >= '3.7' and python_version < '4' +<<<<<<< HEAD +======= +rfc3986[idna2008]==1.5.0 +>>>>>>> c536857 (remove the pinned starlette version) setuptools==67.4.0 ; python_version >= '3.7' six==1.16.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' smmap==5.0.0 ; python_version >= '3.6' +sniffio==1.3.0 ; python_version >= '3.7' snowballstemmer==2.2.0 sphinx==5.3.0 sphinx-rtd-theme==1.2.0 @@ -79,7 +88,6 @@ urllib3==1.26.14 ; python_version >= '2.7' and python_version not in '3.0, 3.1, virtualenv==20.19.0 ; python_version >= '3.7' voluptuous==0.13.1 amqp==5.1.1 ; python_version >= '3.6' -anyio==3.6.2 ; python_full_version >= '3.6.2' async-timeout==4.0.2 ; python_version >= '3.6' bcrypt==4.0.1 billiard==3.6.4.0 @@ -90,9 +98,8 @@ click-repl==0.2.0 configobj==5.0.8 dynaconf==3.1.11 ecdsa==0.18.0 ; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' -fastapi==0.86.0 +fastapi==0.92.0 greenlet==2.0.2 ; platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32'))))) -h11==0.14.0 ; python_version >= '3.7' kombu==5.2.4 ; python_version >= '3.7' prompt-toolkit==3.0.37 ; python_full_version >= '3.7.0' pyasn1==0.4.8 @@ -101,9 +108,8 @@ python-jose==3.3.0 python-multipart==0.0.5 redis==4.5.1 rsa==4.9 ; python_version >= '3.6' and python_version < '4' -sniffio==1.3.0 ; python_version >= '3.7' sqlalchemy==2.0.4 -starlette==0.20.4 +starlette==0.25.0 ; python_version >= '3.7' uvicorn==0.20.0 vine==5.0.0 ; python_version >= '3.6' wcwidth==0.2.6 diff --git a/requirements.txt b/requirements.txt index 78fdf49b..6ab373a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ click-repl==0.2.0 configobj==5.0.8 dynaconf==3.1.11 ecdsa==0.18.0 ; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' -fastapi==0.86.0 +fastapi==0.92.0 greenlet==2.0.2 ; platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32'))))) h11==0.14.0 ; python_version >= '3.7' idna==3.4 ; python_version >= '3.5' @@ -28,7 +28,7 @@ rsa==4.9 ; python_version >= '3.6' and python_version < '4' six==1.16.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' sniffio==1.3.0 ; python_version >= '3.7' sqlalchemy==2.0.4 -starlette==0.20.4 +starlette==0.25.0 ; python_version >= '3.7' typing-extensions==4.5.0 ; python_version >= '3.7' uvicorn==0.20.0 vine==5.0.0 ; python_version >= '3.6' diff --git a/tests/unit/api/test_bootstrap.py b/tests/unit/api/test_bootstrap.py index 30144ede..8c060174 100644 --- a/tests/unit/api/test_bootstrap.py +++ b/tests/unit/api/test_bootstrap.py @@ -12,7 +12,6 @@ class TestGetBoostrap: def test_get_boostrap_available( self, test_client, token_headers, monkeypatch ): - url = "/api/v1/bootstrap/" mocked_check_metadata = pretend.call_recorder(lambda: False) monkeypatch.setattr( @@ -22,7 +21,7 @@ def test_get_boostrap_available( response = test_client.get(url, headers=token_headers) assert response.status_code == status.HTTP_200_OK - assert response.url == test_client.base_url + url + assert response.url == f"{test_client.base_url}{url}" assert response.json() == { "data": {"bootstrap": False}, "message": "System available for bootstrap.", @@ -32,7 +31,6 @@ def test_get_boostrap_available( def test_get_boostrap_not_available( self, test_client, monkeypatch, token_headers ): - url = "/api/v1/bootstrap/" mocked_check_metadata = pretend.call_recorder(lambda: True) @@ -43,7 +41,7 @@ def test_get_boostrap_not_available( response = test_client.get(url, headers=token_headers) assert response.status_code == status.HTTP_200_OK - assert response.url == test_client.base_url + url + assert response.url == f"{test_client.base_url}{url}" assert response.json() == { "data": {"bootstrap": True}, "message": "System LOCKED for bootstrap.", @@ -51,7 +49,6 @@ def test_get_boostrap_not_available( assert mocked_check_metadata.calls == [pretend.call()] def test_get_boostrap_invalid_token(self, test_client, monkeypatch): - url = "/api/v1/bootstrap/" mocked_check_metadata = pretend.call_recorder(lambda: False) monkeypatch.setattr( @@ -129,7 +126,7 @@ def test_post_bootstrap(self, test_client, monkeypatch, token_headers): response = test_client.post(url, json=payload, headers=token_headers) assert response.status_code == status.HTTP_202_ACCEPTED - assert response.url == test_client.base_url + url + assert response.url == f"{test_client.base_url}{url}" assert response.json() == { "message": "Bootstrap accepted.", "data": {"task_id": "123"}, @@ -153,7 +150,7 @@ def test_post_bootstrap_already_bootstrap( response = test_client.post(url, json=payload, headers=token_headers) assert response.status_code == status.HTTP_200_OK - assert response.url == test_client.base_url + url + assert response.url == f"{test_client.base_url}{url}" assert response.json() == { "detail": {"error": "System already has a Metadata."} } @@ -164,7 +161,7 @@ def test_post_bootstrap_empty_payload(self, test_client, token_headers): response = test_client.post(url, json={}, headers=token_headers) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - assert response.url == test_client.base_url + url + assert response.url == f"{test_client.base_url}{url}" assert response.json() == { "detail": [ { diff --git a/tests/unit/api/test_targets.py b/tests/unit/api/test_targets.py index 36251486..3d899039 100644 --- a/tests/unit/api/test_targets.py +++ b/tests/unit/api/test_targets.py @@ -303,7 +303,11 @@ def test_delete(self, monkeypatch, test_client, token_headers): "repository_service_tuf_api.targets.datetime", fake_datetime ) - response = test_client.delete(url, json=payload, headers=token_headers) + # https://github.com/tiangolo/fastapi/issues/5649 + response = test_client.request( + "DELETE", url, json=payload, headers=token_headers + ) + assert response.status_code == status.HTTP_202_ACCEPTED assert response.json() == { "data": { @@ -359,7 +363,11 @@ def test_delete_publish_targets_false( "repository_service_tuf_api.targets.datetime", fake_datetime ) - response = test_client.delete(url, json=payload, headers=token_headers) + # https://github.com/tiangolo/fastapi/issues/5649 + response = test_client.request( + "DELETE", url, json=payload, headers=token_headers + ) + assert response.status_code == status.HTTP_202_ACCEPTED msg = ( "Remove Target(s) successfully submitted. " @@ -397,7 +405,11 @@ def test_delete_without_bootstrap( "repository_service_tuf_api.targets.is_bootstrap_done", lambda: False, ) - response = test_client.delete(url, json=payload, headers=token_headers) + # https://github.com/tiangolo/fastapi/issues/5649 + response = test_client.request( + "DELETE", url, json=payload, headers=token_headers + ) + assert response.status_code == status.HTTP_200_OK assert response.json() == { "detail": {"error": "System has not a Repository Metadata"} @@ -408,7 +420,11 @@ def test_delete_missing_required_field(self, test_client, token_headers): payload = {"paths": ["file-v1.0.0_i683.tar.gz", "v0.4.1/file.tar.gz"]} - response = test_client.delete(url, json=payload, headers=token_headers) + # https://github.com/tiangolo/fastapi/issues/5649 + response = test_client.request( + "DELETE", url, json=payload, headers=token_headers + ) + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY def test_delete_unauthorized_invalid_token(self, test_client): @@ -421,7 +437,11 @@ def test_delete_unauthorized_invalid_token(self, test_client): "targets": ["file-v1.0.0_i683.tar.gz", "v0.4.1/file.tar.gz"] } - response = test_client.delete(url, json=payload, headers=headers) + # https://github.com/tiangolo/fastapi/issues/5649 + response = test_client.request( + "DELETE", url, json=payload, headers=headers + ) + assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.json() == { "detail": {"error": "Failed to validate token"} @@ -444,7 +464,11 @@ def test_post_forbidden_user_incorrect_scope_token(self, test_client): "targets": ["file-v1.0.0_i683.tar.gz", "v0.4.1/file.tar.gz"] } - response = test_client.delete(url, json=payload, headers=headers) + # https://github.com/tiangolo/fastapi/issues/5649 + response = test_client.request( + "DELETE", url, json=payload, headers=headers + ) + assert response.status_code == status.HTTP_403_FORBIDDEN assert response.json() == { "detail": {"error": "scope 'delete:targets' not allowed"} diff --git a/tests/unit/test_app.py b/tests/unit/test_app.py index 542844b3..20460037 100644 --- a/tests/unit/test_app.py +++ b/tests/unit/test_app.py @@ -8,7 +8,7 @@ def test_root(test_client): response = test_client.get("/") - assert response.url == test_client.base_url + "/" + assert response.url == f"{test_client.base_url}/" assert response.status_code == status.HTTP_200_OK assert "Repository Service for TUF API" in response.text @@ -16,6 +16,6 @@ def test_root(test_client): def test_default_notfound(test_client): response = test_client.get("/invalid_url") - assert response.url == test_client.base_url + "/invalid_url" + assert response.url == f"{test_client.base_url}/invalid_url" assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json() == {"detail": "Not Found"}