diff --git a/lib/tool_shed/test/functional/test_shed_cors.py b/lib/tool_shed/test/functional/test_shed_cors.py new file mode 100644 index 00000000000..2c96d5000d8 --- /dev/null +++ b/lib/tool_shed/test/functional/test_shed_cors.py @@ -0,0 +1,37 @@ +import requests + +from ..base.api import ShedApiTestCase + +ORIGIN = "https://vscode.dev" + + +class TestShedCorsApi(ShedApiTestCase): + def test_preflight_allows_cross_origin_get(self): + response = requests.options( + f"{self.url}/api/repositories", + headers={ + "Origin": ORIGIN, + "Access-Control-Request-Method": "GET", + }, + ) + assert response.status_code == 200 + assert response.headers.get("access-control-allow-origin") == ORIGIN + + def test_get_response_reflects_origin(self): + response = requests.get(f"{self.url}/api/repositories", headers={"Origin": ORIGIN}) + response.raise_for_status() + assert response.headers.get("access-control-allow-origin") == ORIGIN + + def test_credentials_not_exposed_cross_origin(self): + # No Access-Control-Allow-Credentials keeps the cookie-authenticated surface + # unreadable cross-origin: a browser will not expose a credentialed response + # without that header. + response = requests.get(f"{self.url}/api/repositories", headers={"Origin": ORIGIN}) + response.raise_for_status() + assert "access-control-allow-credentials" not in response.headers + + def test_uncovered_endpoint_has_no_cors(self): + # CORS is opt-in per route; an endpoint not marked allow_cors emits no header. + response = requests.get(f"{self.url}/api/ga4gh/trs/v2/service-info", headers={"Origin": ORIGIN}) + response.raise_for_status() + assert "access-control-allow-origin" not in response.headers diff --git a/lib/tool_shed/webapp/api2/repositories.py b/lib/tool_shed/webapp/api2/repositories.py index 6c00fdea005..7586177e8a2 100644 --- a/lib/tool_shed/webapp/api2/repositories.py +++ b/lib/tool_shed/webapp/api2/repositories.py @@ -131,6 +131,7 @@ class FastAPIRepositories: "/api/repositories", description="Get a list of repositories or perform a search.", operation_id="repositories__index", + allow_cors=True, ) def index( self, @@ -271,6 +272,7 @@ class FastAPIRepositories: "/api/repositories/get_ordered_installable_revisions", description="Get an ordered list of the repository changeset revisions that are installable", operation_id="repositories__get_ordered_installable_revisions", + allow_cors=True, ) def get_ordered_installable_revisions( self, diff --git a/lib/tool_shed/webapp/api2/tools.py b/lib/tool_shed/webapp/api2/tools.py index 6c099b1253e..bbc9ace773c 100644 --- a/lib/tool_shed/webapp/api2/tools.py +++ b/lib/tool_shed/webapp/api2/tools.py @@ -68,6 +68,7 @@ class FastAPITools: @router.get( "/api/tools", operation_id="tools__index", + allow_cors=True, ) def index( self, @@ -146,6 +147,7 @@ class FastAPITools: "/api/tools/{tool_id}/versions/{tool_version}", operation_id="tools__parameter_model", summary="Return Galaxy's meta model description of the tool's inputs", + allow_cors=True, ) def show_tool( self,