Skip to content

Commit a7e4e5e

Browse files
committed
Python: Add rest_framework Response modeling
1 parent 13815fe commit a7e4e5e

File tree

5 files changed

+87
-2
lines changed

5 files changed

+87
-2
lines changed

python/ql/lib/semmle/python/frameworks/RestFramework.qll

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,44 @@ private module RestFramework {
259259
}
260260
}
261261
}
262+
263+
// ---------------------------------------------------------------------------
264+
// response modeling
265+
// ---------------------------------------------------------------------------
266+
/**
267+
* Provides models for the `rest_framework.response.Response` class
268+
*
269+
* See https://www.django-rest-framework.org/api-guide/responses/.
270+
*/
271+
module Response {
272+
/** Gets a reference to the `rest_framework.response.Response` class. */
273+
private API::Node classRef() {
274+
result = API::moduleImport("rest_framework").getMember("response").getMember("Response")
275+
}
276+
277+
/**
278+
* A source of instances of `rest_framework.response.Response`, extend this class to model new instances.
279+
*
280+
* This can include instantiations of the class, return values from function
281+
* calls, or a special parameter that will be set when functions are called by an external
282+
* library.
283+
*
284+
* Use the predicate `Response::instance()` to get references to instances of `rest_framework.response.Response`.
285+
*/
286+
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
287+
288+
/** A direct instantiation of `rest_framework.response.Response`. */
289+
private class ClassInstantiation extends PrivateDjango::django::http::response::HttpResponse::InstanceSource,
290+
DataFlow::CallCfgNode {
291+
ClassInstantiation() { this = classRef().getACall() }
292+
293+
override DataFlow::Node getBody() { result in [this.getArg(0), this.getArgByName("data")] }
294+
295+
override DataFlow::Node getMimetypeOrContentTypeArg() {
296+
result in [this.getArg(5), this.getArgByName("content_type")]
297+
}
298+
299+
override string getMimetypeDefault() { none() }
300+
}
301+
}
262302
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from rest_framework.decorators import api_view
2+
from rest_framework.response import Response
3+
4+
@api_view()
5+
def normal_response(request): # $ requestHandler
6+
# has no pre-defined content type, since that will be negotiated
7+
# see https://www.django-rest-framework.org/api-guide/responses/
8+
data = "data"
9+
resp = Response(data) # $ HttpResponse responseBody=data
10+
return resp
11+
12+
@api_view()
13+
def plain_text_response(request): # $ requestHandler
14+
# this response is not the standard way to use the Djagno REST framework, but it
15+
# certainly is possible -- notice that the response contains double quotes
16+
data = 'this response will contain double quotes since it was a string'
17+
resp = Response(data, None, None, None, None, "text/plain") # $ HttpResponse mimetype=text/plain responseBody=data
18+
resp = Response(data=data, content_type="text/plain") # $ HttpResponse mimetype=text/plain responseBody=data
19+
return resp
20+
21+
################################################################################
22+
# Cookies
23+
################################################################################
24+
25+
@api_view
26+
def setting_cookie(request):
27+
resp = Response() # $ HttpResponse
28+
resp.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
29+
resp.set_cookie(key="key4", value="value") # $ CookieWrite CookieName="key4" CookieValue="value"
30+
resp.headers["Set-Cookie"] = "key2=value2" # $ MISSING: CookieWrite CookieRawHeader="key2=value2"
31+
resp.cookies["key3"] = "value3" # $ CookieWrite CookieName="key3" CookieValue="value3"
32+
resp.delete_cookie("key4") # $ CookieWrite CookieName="key4"
33+
resp.delete_cookie(key="key4") # $ CookieWrite CookieName="key4"
34+
return resp

python/ql/test/library-tests/frameworks/rest_framework/taint_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def test_taint(request: Request, routed_param): # $ requestHandler routedParamet
8181
)
8282
ensure_not_tainted(request.user.password)
8383

84-
return Response("ok")
84+
return Response("ok") # $ HttpResponse responseBody="ok"
8585

8686

8787
# class based view
@@ -105,7 +105,7 @@ def get(self, request: Request, routed_param): # $ requestHandler routedParamete
105105
# same as for standard Django view
106106
ensure_tainted(self.args, self.kwargs) # $ tainted
107107

108-
return Response("ok")
108+
return Response("ok") # $ HttpResponse responseBody="ok"
109109

110110

111111

python/ql/test/library-tests/frameworks/rest_framework/testapp/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
1414
path("class-based-view/", views.MyClass.as_view()), # $routeSetup="lcass-based-view/"
1515
path("function-based-view/", views.function_based_view), # $routeSetup="function-based-view/"
16+
path("cookie-test/", views.cookie_test), # $routeSetup="function-based-view/"
1617
]

python/ql/test/library-tests/frameworks/rest_framework/testapp/views.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,13 @@ def post(self, request):
4040
@api_view(["GET", "POST"])
4141
def function_based_view(request: Request):
4242
return Response({"message": "Hello, world!"})
43+
44+
45+
@api_view(["GET", "POST"])
46+
def cookie_test(request: Request):
47+
resp = Response("wat")
48+
resp.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
49+
resp.set_cookie(key="key4", value="value") # $ CookieWrite CookieName="key" CookieValue="value"
50+
resp.headers["Set-Cookie"] = "key2=value2" # $ MISSING: CookieWrite CookieRawHeader="key2=value2"
51+
resp.cookies["key3"] = "value3" # $ CookieWrite CookieName="key3" CookieValue="value3"
52+
return resp

0 commit comments

Comments
 (0)