Skip to content

Commit 222db37

Browse files
committed
Python: Add initial rest_framework modeling
I had to make the Django and PrivateDjango modeling non-private :O
1 parent a64e939 commit 222db37

File tree

5 files changed

+80
-6
lines changed

5 files changed

+80
-6
lines changed

docs/codeql/support/reusables/frameworks.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ Python built-in support
155155
Name, Category
156156
aiohttp.web, Web framework
157157
Django, Web framework
158+
djangorestframework, Web framework
158159
Flask, Web framework
159160
Tornado, Web framework
160161
Twisted, Web framework

python/ql/lib/semmle/python/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ private import semmle.python.frameworks.MySQLdb
2525
private import semmle.python.frameworks.Peewee
2626
private import semmle.python.frameworks.Psycopg2
2727
private import semmle.python.frameworks.PyMySQL
28+
private import semmle.python.frameworks.RestFramework
2829
private import semmle.python.frameworks.Rsa
2930
private import semmle.python.frameworks.RuamelYaml
3031
private import semmle.python.frameworks.Simplejson

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ private import semmle.python.frameworks.internal.SelfRefMixin
1717
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
1818

1919
/**
20+
* INTERNAL: Do not use.
21+
*
2022
* Provides models for the `django` PyPI package.
2123
* See https://www.djangoproject.com/.
2224
*/
23-
private module Django {
25+
module Django {
2426
/** Provides models for the `django.views` module */
2527
module Views {
2628
/**
@@ -466,10 +468,12 @@ private module Django {
466468
}
467469

468470
/**
471+
* INTERNAL: Do not use.
472+
*
469473
* Provides models for the `django` PyPI package (that we are not quite ready to publicly expose yet).
470474
* See https://www.djangoproject.com/.
471475
*/
472-
private module PrivateDjango {
476+
module PrivateDjango {
473477
// ---------------------------------------------------------------------------
474478
// django
475479
// ---------------------------------------------------------------------------
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `djangorestframework` PyPI package
3+
* (imported as `rest_framework`)
4+
*
5+
* See
6+
* - https://www.django-rest-framework.org/
7+
* - https://pypi.org/project/djangorestframework/
8+
*/
9+
10+
private import python
11+
private import semmle.python.dataflow.new.DataFlow
12+
private import semmle.python.dataflow.new.RemoteFlowSources
13+
private import semmle.python.dataflow.new.TaintTracking
14+
private import semmle.python.Concepts
15+
private import semmle.python.ApiGraphs
16+
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
17+
private import semmle.python.frameworks.Django
18+
private import semmle.python.frameworks.Stdlib
19+
20+
/**
21+
* INTERNAL: Do not use.
22+
*
23+
* Provides models for the `djangorestframework` PyPI package
24+
* (imported as `rest_framework`)
25+
*
26+
* See
27+
* - https://www.django-rest-framework.org/
28+
* - https://pypi.org/project/djangorestframework/
29+
*/
30+
private module RestFramework {
31+
/**
32+
* An `API::Node` representing the `rest_framework.views.APIView` class or any subclass
33+
* that has explicitly been modeled in the CodeQL libraries.
34+
*/
35+
private class ModeledApiViewClasses extends Django::Views::View::ModeledSubclass {
36+
ModeledApiViewClasses() {
37+
this = API::moduleImport("rest_framework").getMember("views").getMember("APIView")
38+
// TODO: Need to model all known subclasses
39+
}
40+
}
41+
42+
/**
43+
* A class that has a super-type which is a rest_framework APIView class, therefore also
44+
* becoming a APIView class.
45+
*/
46+
class RestFrameworkApiViewClass extends PrivateDjango::DjangoViewClassFromSuperClass {
47+
RestFrameworkApiViewClass() {
48+
this.getABase() = any(ModeledApiViewClasses c).getASubclass*().getAUse().asExpr()
49+
}
50+
51+
override Function getARequestHandler() {
52+
result = super.getARequestHandler()
53+
or
54+
// TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
55+
// points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
56+
result = this.getAMethod() and
57+
result.getName() in [
58+
// these method names where found by looking through the APIView
59+
// implementation in
60+
// https://github.com/encode/django-rest-framework/blob/master/rest_framework/views.py#L104
61+
"initial", "http_method_not_allowed", "permission_denied", "throttled",
62+
"get_authenticate_header", "perform_content_negotiation", "perform_authentication",
63+
"check_permissions", "check_object_permissions", "check_throttles", "determine_version",
64+
"initialize_request", "finalize_response", "dispatch", "options"
65+
]
66+
}
67+
}
68+
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ def test_taint(request: Request, routed_param): # $ requestHandler routedParamet
8989

9090

9191
class MyClass(APIView):
92-
def initial(self, request, *args, **kwargs):
92+
def initial(self, request, *args, **kwargs): # $ requestHandler
9393
# this method will be called before processing any request
94-
ensure_tainted(request) # $ MISSING: tainted
94+
ensure_tainted(request) # $ tainted
9595

9696
def get(self, request: Request, routed_param): # $ requestHandler routedParameter=routed_param
9797
ensure_tainted(routed_param) # $ tainted
@@ -124,5 +124,5 @@ def function_based_no_route(request: Request, possible_routed_param):
124124

125125

126126
class ClassBasedNoRoute(APIView):
127-
def get(self, request: Request, possible_routed_param):
128-
ensure_tainted(request, possible_routed_param) # $ MISSING: tainted
127+
def get(self, request: Request, possible_routed_param): # $ requestHandler routedParameter=possible_routed_param
128+
ensure_tainted(request, possible_routed_param) # $ tainted

0 commit comments

Comments
 (0)