Skip to content
Snippets Groups Projects
Commit eb1d4019 authored by Antoine Lambert's avatar Antoine Lambert
Browse files

api/graph: Fix error responses for invalid request Accept header

When setting an unsupported value for the request Accept header when
querying the /api/1/graph/ endpoint, an error was raised as the Django
DRF response does not have the accepted_media_type attribute set in
that case, leading to a 500 HTTP status code.

So ensure a proper JSON error response will be generated with the
406 HTTP status code when submitting such requests.
parent 1edcf35c
No related branches found
No related tags found
No related merge requests found
# Copyright (C) 2017-2019 The Software Heritage developers
# Copyright (C) 2017-2021 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
......@@ -136,10 +136,12 @@ def make_api_response(
# get request status code
doc_data["status_code"] = options.get("status", 200)
accepted_media_type = getattr(request, "accepted_media_type", "application/json")
# when requesting HTML, typically when browsing the API through its
# documented views, we need to enrich the input data with documentation
# and render the apidoc HTML template
if request.accepted_media_type == "text/html":
if accepted_media_type == "text/html":
doc_data["response_data"] = data
if data is not None:
doc_data["response_data"] = json.dumps(
......@@ -166,7 +168,7 @@ def make_api_response(
response = Response(
data,
headers=headers,
content_type=request.accepted_media_type,
content_type=accepted_media_type,
status=doc_data["status_code"],
)
......@@ -209,7 +211,7 @@ def error_response(
"reason": str(exception),
}
if request.accepted_media_type == "text/html":
if getattr(request, "accepted_media_type", None) == "text/html":
error_data["reason"] = escape(error_data["reason"])
if get_config()["debug"]:
......
# Copyright (C) 2020 The Software Heritage developers
# Copyright (C) 2020-2021 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
......@@ -11,6 +11,7 @@ import requests
from django.http.response import StreamingHttpResponse
from rest_framework.decorators import renderer_classes
from rest_framework.renderers import JSONRenderer
from rest_framework.request import Request
from rest_framework.response import Response
......@@ -123,7 +124,7 @@ def api_graph(request: Request) -> None:
@api_route(r"/graph/(?P<graph_query>.+)/", "api-1-graph")
@renderer_classes([PlainTextRenderer])
@renderer_classes([JSONRenderer, PlainTextRenderer])
def api_graph_proxy(
request: Request, graph_query: str
) -> Union[Response, StreamingHttpResponse]:
......@@ -142,7 +143,7 @@ def api_graph_proxy(
# graph stats and counter endpoint responses are not streamed
if response.headers.get("Transfer-Encoding") != "chunked":
return Response(
response.text,
response.json(),
status=response.status_code,
content_type=response.headers["Content-Type"],
)
......
......@@ -4,7 +4,6 @@
# See top-level LICENSE file for more information
import hashlib
import json
import textwrap
from django.http.response import StreamingHttpResponse
......@@ -126,7 +125,7 @@ def test_graph_json_response(api_client, keycloak_oidc, requests_mock):
resp = check_http_get_response(api_client, url, status_code=200)
assert resp.content_type == "application/json"
assert resp.content == json.dumps(_response_json).encode()
assert resp.data == _response_json
def test_graph_ndjson_response(api_client, keycloak_oidc, requests_mock):
......@@ -254,4 +253,18 @@ def test_graph_response_resolve_origins_nothing_to_do(
resp = check_http_get_response(api_client, url, status_code=200)
assert resp.content_type == "application/json"
assert resp.content == json.dumps(_response_json).encode()
assert resp.data == _response_json
def test_graph_response_invalid_accept_header(api_client):
url = reverse(
"api-1-graph",
url_args={"graph_query": "stats"},
query_params={"resolve_origins": "true"},
)
resp = api_client.get(url, HTTP_ACCEPT="text/html")
assert resp.status_code == 406
assert resp.content_type == "application/json"
assert resp.data["exception"] == "NotAcceptable"
assert resp.data["reason"] == "Could not satisfy the request Accept header."
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment