Skip to content

Commit 297b807

Browse files
authored
Merge pull request #508 from danpalmer/graphiql-no-querystring
Improve Security of GraphiQL
2 parents 8759239 + 2b08e59 commit 297b807

File tree

4 files changed

+108
-109
lines changed

4 files changed

+108
-109
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pip install "graphene-django>=2.0"
2020
```python
2121
INSTALLED_APPS = (
2222
# ...
23+
'django.contrib.staticfiles', # Required for GraphiQL
2324
'graphene_django',
2425
)
2526

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
(function() {
2+
3+
// Parse the cookie value for a CSRF token
4+
var csrftoken;
5+
var cookies = ('; ' + document.cookie).split('; csrftoken=');
6+
if (cookies.length == 2)
7+
csrftoken = cookies.pop().split(';').shift();
8+
9+
// Collect the URL parameters
10+
var parameters = {};
11+
window.location.hash.substr(1).split('&').forEach(function (entry) {
12+
var eq = entry.indexOf('=');
13+
if (eq >= 0) {
14+
parameters[decodeURIComponent(entry.slice(0, eq))] =
15+
decodeURIComponent(entry.slice(eq + 1));
16+
}
17+
});
18+
// Produce a Location fragment string from a parameter object.
19+
function locationQuery(params) {
20+
return '#' + Object.keys(params).map(function (key) {
21+
return encodeURIComponent(key) + '=' +
22+
encodeURIComponent(params[key]);
23+
}).join('&');
24+
}
25+
// Derive a fetch URL from the current URL, sans the GraphQL parameters.
26+
var graphqlParamNames = {
27+
query: true,
28+
variables: true,
29+
operationName: true
30+
};
31+
var otherParams = {};
32+
for (var k in parameters) {
33+
if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) {
34+
otherParams[k] = parameters[k];
35+
}
36+
}
37+
38+
var fetchURL = locationQuery(otherParams);
39+
40+
// Defines a GraphQL fetcher using the fetch API.
41+
function graphQLFetcher(graphQLParams) {
42+
var headers = {
43+
'Accept': 'application/json',
44+
'Content-Type': 'application/json'
45+
};
46+
if (csrftoken) {
47+
headers['X-CSRFToken'] = csrftoken;
48+
}
49+
return fetch(fetchURL, {
50+
method: 'post',
51+
headers: headers,
52+
body: JSON.stringify(graphQLParams),
53+
credentials: 'include',
54+
}).then(function (response) {
55+
return response.text();
56+
}).then(function (responseBody) {
57+
try {
58+
return JSON.parse(responseBody);
59+
} catch (error) {
60+
return responseBody;
61+
}
62+
});
63+
}
64+
// When the query and variables string is edited, update the URL bar so
65+
// that it can be easily shared.
66+
function onEditQuery(newQuery) {
67+
parameters.query = newQuery;
68+
updateURL();
69+
}
70+
function onEditVariables(newVariables) {
71+
parameters.variables = newVariables;
72+
updateURL();
73+
}
74+
function onEditOperationName(newOperationName) {
75+
parameters.operationName = newOperationName;
76+
updateURL();
77+
}
78+
function updateURL() {
79+
history.replaceState(null, null, locationQuery(parameters));
80+
}
81+
var options = {
82+
fetcher: graphQLFetcher,
83+
onEditQuery: onEditQuery,
84+
onEditVariables: onEditVariables,
85+
onEditOperationName: onEditOperationName,
86+
query: parameters.query,
87+
}
88+
if (parameters.variables) {
89+
options.variables = parameters.variables;
90+
}
91+
if (parameters.operation_name) {
92+
options.operationName = parameters.operation_name;
93+
}
94+
// Render <GraphiQL /> into the body.
95+
ReactDOM.render(
96+
React.createElement(GraphiQL, options),
97+
document.body
98+
);
99+
})();

graphene_django/templates/graphene/graphiql.html

Lines changed: 2 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
If you wish to receive JSON, provide the header "Accept: application/json" or
66
add "&raw" to the end of the URL within a browser.
77
-->
8+
{% load static %}
89
<!DOCTYPE html>
910
<html>
1011
<head>
@@ -23,101 +24,6 @@
2324
<script src="//cdn.jsdelivr.net/npm/graphiql@{{graphiql_version}}/graphiql.min.js"></script>
2425
</head>
2526
<body>
26-
<script>
27-
// Parse the cookie value for a CSRF token
28-
var csrftoken;
29-
var cookies = ('; ' + document.cookie).split('; csrftoken=');
30-
if (cookies.length == 2)
31-
csrftoken = cookies.pop().split(';').shift();
32-
33-
// Collect the URL parameters
34-
var parameters = {};
35-
window.location.search.substr(1).split('&').forEach(function (entry) {
36-
var eq = entry.indexOf('=');
37-
if (eq >= 0) {
38-
parameters[decodeURIComponent(entry.slice(0, eq))] =
39-
decodeURIComponent(entry.slice(eq + 1));
40-
}
41-
});
42-
// Produce a Location query string from a parameter object.
43-
function locationQuery(params) {
44-
return '?' + Object.keys(params).map(function (key) {
45-
return encodeURIComponent(key) + '=' +
46-
encodeURIComponent(params[key]);
47-
}).join('&');
48-
}
49-
// Derive a fetch URL from the current URL, sans the GraphQL parameters.
50-
var graphqlParamNames = {
51-
query: true,
52-
variables: true,
53-
operationName: true
54-
};
55-
var otherParams = {};
56-
for (var k in parameters) {
57-
if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) {
58-
otherParams[k] = parameters[k];
59-
}
60-
}
61-
var fetchURL = locationQuery(otherParams);
62-
// Defines a GraphQL fetcher using the fetch API.
63-
function graphQLFetcher(graphQLParams) {
64-
var headers = {
65-
'Accept': 'application/json',
66-
'Content-Type': 'application/json'
67-
};
68-
if (csrftoken) {
69-
headers['X-CSRFToken'] = csrftoken;
70-
}
71-
return fetch(fetchURL, {
72-
method: 'post',
73-
headers: headers,
74-
body: JSON.stringify(graphQLParams),
75-
credentials: 'include',
76-
}).then(function (response) {
77-
return response.text();
78-
}).then(function (responseBody) {
79-
try {
80-
return JSON.parse(responseBody);
81-
} catch (error) {
82-
return responseBody;
83-
}
84-
});
85-
}
86-
// When the query and variables string is edited, update the URL bar so
87-
// that it can be easily shared.
88-
function onEditQuery(newQuery) {
89-
parameters.query = newQuery;
90-
updateURL();
91-
}
92-
function onEditVariables(newVariables) {
93-
parameters.variables = newVariables;
94-
updateURL();
95-
}
96-
function onEditOperationName(newOperationName) {
97-
parameters.operationName = newOperationName;
98-
updateURL();
99-
}
100-
function updateURL() {
101-
history.replaceState(null, null, locationQuery(parameters));
102-
}
103-
// Render <GraphiQL /> into the body.
104-
ReactDOM.render(
105-
React.createElement(GraphiQL, {
106-
fetcher: graphQLFetcher,
107-
onEditQuery: onEditQuery,
108-
onEditVariables: onEditVariables,
109-
onEditOperationName: onEditOperationName,
110-
query: '{{ query|escapejs }}',
111-
response: '{{ result|escapejs }}',
112-
{% if variables %}
113-
variables: '{{ variables|escapejs }}',
114-
{% endif %}
115-
{% if operation_name %}
116-
operationName: '{{ operation_name|escapejs }}',
117-
{% endif %}
118-
}),
119-
document.body
120-
);
121-
</script>
27+
<script src="{% static 'graphene_django/graphiql.js' %}"></script>
12228
</body>
12329
</html>

graphene_django/views.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ def dispatch(self, request, *args, **kwargs):
124124
data = self.parse_body(request)
125125
show_graphiql = self.graphiql and self.can_display_graphiql(request, data)
126126

127+
if show_graphiql:
128+
return self.render_graphiql(
129+
request,
130+
graphiql_version=self.graphiql_version,
131+
)
132+
127133
if self.batch:
128134
responses = [self.get_response(request, entry) for entry in data]
129135
result = "[{}]".format(
@@ -137,19 +143,6 @@ def dispatch(self, request, *args, **kwargs):
137143
else:
138144
result, status_code = self.get_response(request, data, show_graphiql)
139145

140-
if show_graphiql:
141-
query, variables, operation_name, id = self.get_graphql_params(
142-
request, data
143-
)
144-
return self.render_graphiql(
145-
request,
146-
graphiql_version=self.graphiql_version,
147-
query=query or "",
148-
variables=json.dumps(variables) or "",
149-
operation_name=operation_name or "",
150-
result=result or "",
151-
)
152-
153146
return HttpResponse(
154147
status=status_code, content=result, content_type="application/json"
155148
)

0 commit comments

Comments
 (0)