Skip to content

Commit 03f8f3a

Browse files
authored
Merge pull request #21105 from mvdbeek/fix_cors_on_exceptions
[25.1] Test and fix CORS on exceptions
2 parents f91bca0 + 9b7062d commit 03f8f3a

File tree

5 files changed

+62
-1
lines changed

5 files changed

+62
-1
lines changed

lib/galaxy/tool_util/verify/interactor.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,22 @@ def _post(
982982
kwd["timeout"] = kwd.pop("timeout", util.DEFAULT_SOCKET_TIMEOUT)
983983
return requests.post(url, **kwd)
984984

985+
def _options(
986+
self,
987+
path: str,
988+
data: Optional[Dict[str, Any]] = None,
989+
key: Optional[str] = None,
990+
headers: Optional[Dict[str, Optional[str]]] = None,
991+
admin: bool = False,
992+
anon: bool = False,
993+
json: bool = False,
994+
) -> Response:
995+
headers = self.api_key_header(key=key, admin=admin, anon=anon, headers=headers)
996+
url = self.get_api_url(path)
997+
kwd = self._prepare_request_params(data=data, as_json=json, headers=headers)
998+
kwd["timeout"] = kwd.pop("timeout", util.DEFAULT_SOCKET_TIMEOUT)
999+
return requests.options(url, **kwd)
1000+
9851001
def _delete(self, path, data=None, key=None, headers=None, admin=False, anon=False, json=False, params=None):
9861002
headers = self.api_key_header(key=key, admin=admin, anon=anon, headers=headers)
9871003
url = self.get_api_url(path)

lib/galaxy/util/requests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def wrapper(*args: Param.args, **kwargs: Param.kwargs) -> RetType:
3939
head = default_user_agent_decorator(requests.head)
4040
patch = default_user_agent_decorator(requests.patch)
4141
post = default_user_agent_decorator(requests.post)
42+
options = default_user_agent_decorator(requests.options)
4243
put = default_user_agent_decorator(requests.put)
4344
session = default_user_agent_decorator(requests.session)
4445
Session = default_user_agent_decorator(requests.Session)

lib/galaxy/webapps/galaxy/api/__init__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,21 @@ def get_route_handler(self) -> Callable:
548548
original_route_handler = super().get_route_handler()
549549

550550
async def custom_route_handler(request: Request) -> Response:
551-
response: Response = await original_route_handler(request)
551+
try:
552+
response: Response = await original_route_handler(request)
553+
except Exception as exc:
554+
# Find and use FastAPI's exception handler
555+
handler = None
556+
for exc_class, exc_handler in request.app.exception_handlers.items():
557+
if isinstance(exc, exc_class):
558+
handler = exc_handler
559+
break
560+
561+
if handler is None:
562+
raise exc
563+
564+
# Call the handler - it's already a callable that takes (request, exc)
565+
response = await handler(request, exc)
552566
response.headers["Access-Control-Allow-Origin"] = request.headers.get("Origin", "*")
553567
response.headers["Access-Control-Max-Age"] = "600"
554568
return response

lib/galaxy_test/api/test_landing.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,30 @@ def test_create_private_workflow_landing_authenticated_user(self):
162162
_cannot_claim_request(self.dataset_populator, response)
163163
_cannot_use_request(self.dataset_populator, response)
164164

165+
def test_invalid_workflow_landing_creation_cors(self):
166+
request = _get_simple_landing_payload(self.workflow_populator, public=True).model_dump()
167+
# Make payload invalid.
168+
request.pop("workflow_id")
169+
cors_headers = {"Access-Control-Request-Method": "POST", "Origin": "https://foo.example"}
170+
response = self._options(
171+
"workflow_landings",
172+
data=request,
173+
headers=cors_headers,
174+
json=True,
175+
)
176+
# CORS preflight request should succeed, doesn't matter that the payload is invalid
177+
assert response.status_code == 200
178+
assert response.headers["Access-Control-Allow-Origin"] == "https://foo.example"
179+
response = self._post(
180+
"workflow_landings",
181+
data=request,
182+
headers=cors_headers,
183+
json=True,
184+
)
185+
assert response.status_code == 400
186+
assert response.headers["Access-Control-Allow-Origin"] == "https://foo.example"
187+
assert "Field required" in response.json()["err_msg"]
188+
165189
@skip_without_tool("cat1")
166190
def test_create_private_workflow_landing_anonymous_user(self):
167191
request = _get_simple_landing_payload(self.workflow_populator, public=False)

lib/galaxy_test/base/api.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,9 @@ def _head(self, *args, **kwds):
183183
def _post(self, *args, **kwds):
184184
return self.galaxy_interactor.post(*args, **kwds)
185185

186+
def _options(self, *args, **kwds):
187+
return self.galaxy_interactor.options(*args, **kwds)
188+
186189
def _delete(self, *args, **kwds):
187190
return self.galaxy_interactor.delete(*args, **kwds)
188191

@@ -237,6 +240,9 @@ def head(self, *args, **kwds):
237240
def post(self, *args, **kwds):
238241
return self._post(*args, **kwds)
239242

243+
def options(self, *args, **kwds):
244+
return self._options(*args, **kwds)
245+
240246
def delete(self, *args, **kwds):
241247
return self._delete(*args, **kwds)
242248

0 commit comments

Comments
 (0)