Python: Proper models of flask MethodView classes

This commit is contained in:
Rasmus Wriedt Larsen
2021-01-12 16:36:01 +01:00
parent e327fdb317
commit 4cb2f2ed1e
3 changed files with 40 additions and 6 deletions

View File

@@ -256,6 +256,27 @@ private module FlaskModel {
/** Gets a reference to the `flask.views.View` class or any subclass. */
DataFlow::Node subclassRef() { result = subclassRef(DataFlow::TypeTracker::end()) }
}
/**
* Provides models for the `flask.views.MethodView` class and subclasses.
*
* See https://flask.palletsprojects.com/en/1.1.x/views/#method-based-dispatching.
*/
module MethodView {
/** Gets a reference to the `flask.views.View` class or any subclass. */
private DataFlow::Node subclassRef(DataFlow::TypeTracker t) {
t.start() and
result = views_attr("MethodView")
or
// subclasses in project code
result.asExpr().(ClassExpr).getABase() = subclassRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = subclassRef(t2).track(t2, t))
}
/** Gets a reference to the `flask.views.View` class or any subclass. */
DataFlow::Node subclassRef() { result = subclassRef(DataFlow::TypeTracker::end()) }
}
}
}
@@ -377,6 +398,19 @@ private module FlaskModel {
DataFlow::Node asViewResult() { result = asViewResult(DataFlow::TypeTracker::end()) }
}
class FlaskMethodViewClassDef extends FlaskViewClassDef {
FlaskMethodViewClassDef() { this.getABase() = flask::views::MethodView::subclassRef().asExpr() }
override Function getARequestHandler() {
result = super.getARequestHandler()
or
// TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
// points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
result = this.getAMethod() and
result.getName() = HTTP::httpVerbLower()
}
}
private string werkzeug_rule_re() {
// since flask uses werkzeug internally, we are using its routing rules from
// https://github.com/pallets/werkzeug/blob/4dc8d6ab840d4b78cbd5789cef91b01e3bde01d5/src/werkzeug/routing.py#L138-L151

View File

@@ -11,7 +11,7 @@ from flask.views import MethodView
class MyView(MethodView):
def get(self, user_id): # $ MISSING: requestHandler
def get(self, user_id): # $ requestHandler
if user_id is None:
# return a list of users
pass

View File

@@ -59,7 +59,7 @@ from flask.views import MethodView
class UserAPI(MethodView):
def get(self, user_id): # $ MISSING: requestHandler routedParameter=user_id
def get(self, user_id): # $ requestHandler routedParameter=user_id
if user_id is None:
# return a list of users
pass
@@ -67,15 +67,15 @@ class UserAPI(MethodView):
# expose a single user
pass
def post(self): # $ MISSING: requestHandler
def post(self): # $ requestHandler
# create a new user
pass
def delete(self, user_id): # $ MISSING: requestHandler routedParameter=user_id
def delete(self, user_id): # $ requestHandler routedParameter=user_id
# delete a single user
pass
def put(self, user_id): # $ MISSING: requestHandler routedParameter=user_id
def put(self, user_id): # $ requestHandler routedParameter=user_id
# update a single user
pass
@@ -89,7 +89,7 @@ app.add_url_rule("/users/<int:user_id>", view_func=user_view, methods=["GET", "P
class WithoutKnownRoute2(MethodView):
# For handler without known route, treat all parameters as routed parameters
# (accepting that there might be a few FPs)
def get(self, foo, not_routed=42): # $ MISSING: requestHandler routedParameter=foo
def get(self, foo, not_routed=42): # $ requestHandler routedParameter=foo SPURIOUS: routedParameter=not_routed
pass