@@ -34,7 +34,7 @@ private module FlaskModel {
3434 * WARNING: Only holds for a few predefined attributes.
3535 */
3636 private DataFlow:: Node flask_attr ( DataFlow:: TypeTracker t , string attr_name ) {
37- attr_name in [ "request" , "make_response" , "Response" ] and
37+ attr_name in [ "request" , "make_response" , "Response" , "views" ] and
3838 (
3939 t .start ( ) and
4040 result = DataFlow:: importNode ( "flask" + "." + attr_name )
@@ -187,6 +187,97 @@ private module FlaskModel {
187187 */
188188 DataFlow:: Node response_class ( ) { result = response_class ( DataFlow:: TypeTracker:: end ( ) ) }
189189 }
190+
191+ // -------------------------------------------------------------------------
192+ // flask.views
193+ // -------------------------------------------------------------------------
194+ /** Gets a reference to the `flask.views` module. */
195+ DataFlow:: Node views ( ) { result = flask_attr ( "views" ) }
196+
197+ /** Provides models for the `flask.views` module */
198+ module views {
199+ /**
200+ * Gets a reference to the attribute `attr_name` of the `flask.views` module.
201+ * WARNING: Only holds for a few predefined attributes.
202+ */
203+ private DataFlow:: Node views_attr ( DataFlow:: TypeTracker t , string attr_name ) {
204+ attr_name in [ "View" , "MethodView" ] and
205+ (
206+ t .start ( ) and
207+ result = DataFlow:: importNode ( "flask.views" + "." + attr_name )
208+ or
209+ t .startInAttr ( attr_name ) and
210+ result = views ( )
211+ )
212+ or
213+ // Due to bad performance when using normal setup with `views_attr(t2, attr_name).track(t2, t)`
214+ // we have inlined that code and forced a join
215+ exists ( DataFlow:: TypeTracker t2 |
216+ exists ( DataFlow:: StepSummary summary |
217+ views_attr_first_join ( t2 , attr_name , result , summary ) and
218+ t = t2 .append ( summary )
219+ )
220+ )
221+ }
222+
223+ pragma [ nomagic]
224+ private predicate views_attr_first_join (
225+ DataFlow:: TypeTracker t2 , string attr_name , DataFlow:: Node res ,
226+ DataFlow:: StepSummary summary
227+ ) {
228+ DataFlow:: StepSummary:: step ( views_attr ( t2 , attr_name ) , res , summary )
229+ }
230+
231+ /**
232+ * Gets a reference to the attribute `attr_name` of the `flask.views` module.
233+ * WARNING: Only holds for a few predefined attributes.
234+ */
235+ private DataFlow:: Node views_attr ( string attr_name ) {
236+ result = views_attr ( DataFlow:: TypeTracker:: end ( ) , attr_name )
237+ }
238+
239+ /**
240+ * Provides models for the `flask.views.View` class and subclasses.
241+ *
242+ * See https://flask.palletsprojects.com/en/1.1.x/views/#basic-principle.
243+ */
244+ module View {
245+ /** Gets a reference to the `flask.views.View` class or any subclass. */
246+ private DataFlow:: Node subclassRef ( DataFlow:: TypeTracker t ) {
247+ t .start ( ) and
248+ result = views_attr ( [ "View" , "MethodView" ] )
249+ or
250+ // subclasses in project code
251+ result .asExpr ( ) .( ClassExpr ) .getABase ( ) = subclassRef ( t .continue ( ) ) .asExpr ( )
252+ or
253+ exists ( DataFlow:: TypeTracker t2 | result = subclassRef ( t2 ) .track ( t2 , t ) )
254+ }
255+
256+ /** Gets a reference to the `flask.views.View` class or any subclass. */
257+ DataFlow:: Node subclassRef ( ) { result = subclassRef ( DataFlow:: TypeTracker:: end ( ) ) }
258+ }
259+
260+ /**
261+ * Provides models for the `flask.views.MethodView` class and subclasses.
262+ *
263+ * See https://flask.palletsprojects.com/en/1.1.x/views/#method-based-dispatching.
264+ */
265+ module MethodView {
266+ /** Gets a reference to the `flask.views.View` class or any subclass. */
267+ private DataFlow:: Node subclassRef ( DataFlow:: TypeTracker t ) {
268+ t .start ( ) and
269+ result = views_attr ( "MethodView" )
270+ or
271+ // subclasses in project code
272+ result .asExpr ( ) .( ClassExpr ) .getABase ( ) = subclassRef ( t .continue ( ) ) .asExpr ( )
273+ or
274+ exists ( DataFlow:: TypeTracker t2 | result = subclassRef ( t2 ) .track ( t2 , t ) )
275+ }
276+
277+ /** Gets a reference to the `flask.views.View` class or any subclass. */
278+ DataFlow:: Node subclassRef ( ) { result = subclassRef ( DataFlow:: TypeTracker:: end ( ) ) }
279+ }
280+ }
190281 }
191282
192283 /**
@@ -261,6 +352,65 @@ private module FlaskModel {
261352 // ---------------------------------------------------------------------------
262353 // routing modeling
263354 // ---------------------------------------------------------------------------
355+ /** A flask View class defined in project code. */
356+ class FlaskViewClassDef extends Class {
357+ FlaskViewClassDef ( ) { this .getABase ( ) = flask:: views:: View:: subclassRef ( ) .asExpr ( ) }
358+
359+ /** Gets a function that could handle incoming requests, if any. */
360+ Function getARequestHandler ( ) {
361+ // TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
362+ // points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
363+ result = this .getAMethod ( ) and
364+ result .getName ( ) = "dispatch_request"
365+ }
366+
367+ /** Gets a reference to this class. */
368+ private DataFlow:: Node getARef ( DataFlow:: TypeTracker t ) {
369+ t .start ( ) and
370+ result .asExpr ( ) .( ClassExpr ) = this .getParent ( )
371+ or
372+ exists ( DataFlow:: TypeTracker t2 | result = this .getARef ( t2 ) .track ( t2 , t ) )
373+ }
374+
375+ /** Gets a reference to this class. */
376+ DataFlow:: Node getARef ( ) { result = this .getARef ( DataFlow:: TypeTracker:: end ( ) ) }
377+
378+ /** Gets a reference to the `as_view` classmethod of this class. */
379+ private DataFlow:: Node asViewRef ( DataFlow:: TypeTracker t ) {
380+ t .startInAttr ( "as_view" ) and
381+ result = this .getARef ( )
382+ or
383+ exists ( DataFlow:: TypeTracker t2 | result = this .asViewRef ( t2 ) .track ( t2 , t ) )
384+ }
385+
386+ /** Gets a reference to the `as_view` classmethod of this class. */
387+ DataFlow:: Node asViewRef ( ) { result = this .asViewRef ( DataFlow:: TypeTracker:: end ( ) ) }
388+
389+ /** Gets a reference to the result of calling the `as_view` classmethod of this class. */
390+ private DataFlow:: Node asViewResult ( DataFlow:: TypeTracker t ) {
391+ t .start ( ) and
392+ result .asCfgNode ( ) .( CallNode ) .getFunction ( ) = this .asViewRef ( ) .asCfgNode ( )
393+ or
394+ exists ( DataFlow:: TypeTracker t2 | result = asViewResult ( t2 ) .track ( t2 , t ) )
395+ }
396+
397+ /** Gets a reference to the result of calling the `as_view` classmethod of this class. */
398+ DataFlow:: Node asViewResult ( ) { result = asViewResult ( DataFlow:: TypeTracker:: end ( ) ) }
399+ }
400+
401+ class FlaskMethodViewClassDef extends FlaskViewClassDef {
402+ FlaskMethodViewClassDef ( ) { this .getABase ( ) = flask:: views:: MethodView:: subclassRef ( ) .asExpr ( ) }
403+
404+ override Function getARequestHandler ( ) {
405+ result = super .getARequestHandler ( )
406+ or
407+ // TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
408+ // points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
409+ result = this .getAMethod ( ) and
410+ result .getName ( ) = HTTP:: httpVerbLower ( )
411+ }
412+ }
413+
264414 private string werkzeug_rule_re ( ) {
265415 // since flask uses werkzeug internally, we are using its routing rules from
266416 // https://github.com/pallets/werkzeug/blob/4dc8d6ab840d4b78cbd5789cef91b01e3bde01d5/src/werkzeug/routing.py#L138-L151
@@ -318,12 +468,36 @@ private module FlaskModel {
318468 result .asCfgNode ( ) in [ node .getArg ( 0 ) , node .getArgByName ( "rule" ) ]
319469 }
320470
471+ DataFlow:: Node getViewArg ( ) {
472+ result .asCfgNode ( ) in [ node .getArg ( 2 ) , node .getArgByName ( "view_func" ) ]
473+ }
474+
321475 override Function getARequestHandler ( ) {
322- exists ( DataFlow:: Node view_func_arg , DataFlow:: LocalSourceNode func_src |
323- view_func_arg .asCfgNode ( ) in [ node .getArg ( 2 ) , node .getArgByName ( "view_func" ) ] and
324- func_src .flowsTo ( view_func_arg ) and
476+ exists ( DataFlow:: LocalSourceNode func_src |
477+ func_src .flowsTo ( getViewArg ( ) ) and
325478 func_src .asExpr ( ) .( CallableExpr ) = result .getDefinition ( )
326479 )
480+ or
481+ exists ( FlaskViewClassDef vc |
482+ getViewArg ( ) = vc .asViewResult ( ) and
483+ result = vc .getARequestHandler ( )
484+ )
485+ }
486+ }
487+
488+ /** A request handler defined in a django view class, that has no known route. */
489+ private class FlaskViewClassHandlerWithoutKnownRoute extends HTTP:: Server:: RequestHandler:: Range {
490+ FlaskViewClassHandlerWithoutKnownRoute ( ) {
491+ exists ( FlaskViewClassDef vc | vc .getARequestHandler ( ) = this ) and
492+ not exists ( FlaskRouteSetup setup | setup .getARequestHandler ( ) = this )
493+ }
494+
495+ override Parameter getARoutedParameter ( ) {
496+ // Since we don't know the URL pattern, we simply mark all parameters as a routed
497+ // parameter. This should give us more RemoteFlowSources but could also lead to
498+ // more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
499+ result in [ this .getArg ( _) , this .getArgByName ( _) ] and
500+ not result = this .getArg ( 0 )
327501 }
328502 }
329503
0 commit comments