Skip to content

Commit

Permalink
fix(sonarcloud): remove CSRF exempt from /graphql/ endpoint
Browse files Browse the repository at this point in the history
Fixes SonarCloud security hotspot python:S4502:
"Make sure disabling CSRF protection is safe here.
Disabling CSRF protections is security-sensitive":
https://rules.sonarsource.com/python/RSPEC-4502/

refs KK-1417
  • Loading branch information
karisal-anders committed Feb 26, 2025
1 parent 41ae1e7 commit 9648bb7
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 4 deletions.
27 changes: 27 additions & 0 deletions kukkuu/templates/csrf_tokenized_graphiql.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{% extends 'graphene/graphiql.html' %}

{% block body_extra %}
<script>
// Add CSRF token to all GraphiQL fetch requests to make CSRF work with GraphiQL.
//
// window.fetch function's `options` parameter is an optional RequestInit,
// which has an optional `headers` property which can be Headers or an object.
//
// See documentations:
// - https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
// - https://developer.mozilla.org/en-US/docs/Web/API/RequestInit
// - https://developer.mozilla.org/en-US/docs/Web/API/Headers
const originalFetch = window.fetch;
window.fetch = function(resource, options = undefined) {
if (options) {
options.headers = options.headers ?? {};
if (options.headers instanceof Headers) {
options.headers.set('{{ csrf_header_name }}', '{{ csrf_token }}');
} else {
options.headers['{{ csrf_header_name }}'] = '{{ csrf_token }}';
}
}
return originalFetch(resource, options);
};
</script>
{% endblock %}
13 changes: 12 additions & 1 deletion kukkuu/tests/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,18 @@ def set_authenticated_user(user):
yield


def graphql_request(live_server, query=MY_PROFILE_QUERY, headers=None):
def graphql_request(live_server, query=MY_PROFILE_QUERY, headers: dict | None = None):
# Get CSRF token by making a GET request to the GraphQL endpoint,
# see https://docs.djangoproject.com/en/4.2/ref/csrf/ for CSRF protection details
client = requests.session()
client.get(live_server.url + "/graphql")
csrf_token = client.cookies["csrftoken"]

# Set the CSRF token to the headers
headers = headers or {}
headers["X-CSRFToken"] = csrf_token
headers["Cookie"] = f"csrftoken={csrf_token}"

return requests.post(
live_server.url + "/graphql", json={"query": query}, headers=headers
)
Expand Down
2 changes: 0 additions & 2 deletions kukkuu/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from django.http import JsonResponse
from django.urls import include, path, re_path
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView
from helusers.admin_site import admin
Expand Down Expand Up @@ -35,7 +34,6 @@
SCRIPT_SRC=settings.CSP_SCRIPT_SRC
+ ([CSP.UNSAFE_INLINE] if IS_GRAPHIQL_ENABLED else [])
)
@csrf_exempt
def graphql_view(request, *args, **kwargs):
return SentryGraphQLView.as_view(graphiql=IS_GRAPHIQL_ENABLED)(
request, *args, **kwargs
Expand Down
19 changes: 18 additions & 1 deletion kukkuu/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,24 @@
error_codes = {**error_codes_shared, **error_codes_kukkuu}


class SentryGraphQLView(FileUploadGraphQLView):
class FileUploadGraphQLViewWithCSRF(FileUploadGraphQLView):
"""
FileUploadGraphQLView with CSRF token support
"""

graphiql_template = "csrf_tokenized_graphiql.html"

def render_graphiql(self, request, **data):
"""
Render GraphiQL interface with CSRF token and header name
"""
# Set CSRF token and header name to be used in GraphiQL
data["csrf_token"] = request.META.get("CSRF_COOKIE")
data["csrf_header_name"] = "X-CSRFToken"
return super().render_graphiql(request, **data)


class SentryGraphQLView(FileUploadGraphQLViewWithCSRF):
def __init__(self, *args, **kwargs):
super().__init__(
validation_rules=[
Expand Down

0 comments on commit 9648bb7

Please sign in to comment.