Skip to content

Commit 5e4b866

Browse files
committed
Python: Model rest_framework.exceptions.APIException
1 parent 62e58b5 commit 5e4b866

File tree

4 files changed

+71
-0
lines changed

4 files changed

+71
-0
lines changed

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,51 @@ private module RestFramework {
299299
override string getMimetypeDefault() { none() }
300300
}
301301
}
302+
303+
// ---------------------------------------------------------------------------
304+
// Exception response modeling
305+
// ---------------------------------------------------------------------------
306+
/**
307+
* Provides models for the `rest_framework.exceptions.APIException` class and subclasses
308+
*
309+
* See https://www.django-rest-framework.org/api-guide/exceptions/#api-reference
310+
*/
311+
module APIException {
312+
/** A direct instantiation of `rest_framework.exceptions.APIException` or subclass. */
313+
private class ClassInstantiation extends HTTP::Server::HttpResponse::Range,
314+
DataFlow::CallCfgNode {
315+
string className;
316+
317+
ClassInstantiation() {
318+
className in [
319+
"APIException", "ValidationError", "ParseError", "AuthenticationFailed",
320+
"NotAuthenticated", "PermissionDenied", "NotFound", "MethodNotAllowed", "NotAcceptable",
321+
"UnsupportedMediaType", "Throttled"
322+
] and
323+
this =
324+
API::moduleImport("rest_framework")
325+
.getMember("exceptions")
326+
.getMember(className)
327+
.getACall()
328+
}
329+
330+
override DataFlow::Node getBody() {
331+
className in [
332+
"APIException", "ValidationError", "ParseError", "AuthenticationFailed",
333+
"NotAuthenticated", "PermissionDenied", "NotFound", "NotAcceptable"
334+
] and
335+
result = this.getArg(0)
336+
or
337+
className in ["MethodNotAllowed", "UnsupportedMediaType", "Throttled"] and
338+
result = this.getArg(1)
339+
or
340+
result = this.getArgByName("detail")
341+
}
342+
343+
// How to support the `headers` argument here?
344+
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
345+
346+
override string getMimetypeDefault() { none() }
347+
}
348+
}
302349
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from rest_framework.decorators import api_view
22
from rest_framework.response import Response
3+
from rest_framework.exceptions import APIException
34

45
@api_view()
56
def normal_response(request): # $ requestHandler
@@ -32,3 +33,18 @@ def setting_cookie(request):
3233
resp.delete_cookie("key4") # $ CookieWrite CookieName="key4"
3334
resp.delete_cookie(key="key4") # $ CookieWrite CookieName="key4"
3435
return resp
36+
37+
################################################################################
38+
# Exceptions
39+
################################################################################
40+
41+
# see https://www.django-rest-framework.org/api-guide/exceptions/
42+
43+
@api_view(["GET", "POST"])
44+
def exception_test(request): # $ requestHandler
45+
data = "exception details"
46+
# note: `code details` not exposed by default
47+
code = "code details"
48+
e1 = APIException(data, code) # $ HttpResponse responseBody=data
49+
e2 = APIException(detail=data, code=code) # $ HttpResponse responseBody=data
50+
raise e2

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
@@ -14,4 +14,5 @@
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/"
1616
path("cookie-test/", views.cookie_test), # $routeSetup="function-based-view/"
17+
path("exception-test/", views.exception_test), # $routeSetup="exception-test/"
1718
]

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from rest_framework.views import APIView
77
from rest_framework.request import Request
88
from rest_framework.response import Response
9+
from rest_framework.exceptions import APIException
910

1011
# Viewsets
1112
# see https://www.django-rest-framework.org/tutorial/quickstart/
@@ -50,3 +51,9 @@ def cookie_test(request: Request):
5051
resp.headers["Set-Cookie"] = "key2=value2" # $ MISSING: CookieWrite CookieRawHeader="key2=value2"
5152
resp.cookies["key3"] = "value3" # $ CookieWrite CookieName="key3" CookieValue="value3"
5253
return resp
54+
55+
@api_view(["GET", "POST"])
56+
def exception_test(request: Request):
57+
# see https://www.django-rest-framework.org/api-guide/exceptions/
58+
# note: `code details` not exposed by default
59+
raise APIException("exception details", "code details")

0 commit comments

Comments
 (0)