@@ -34,7 +34,7 @@ private module FlaskModel {
34
34
* WARNING: Only holds for a few predefined attributes.
35
35
*/
36
36
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
38
38
(
39
39
t .start ( ) and
40
40
result = DataFlow:: importNode ( "flask" + "." + attr_name )
@@ -187,6 +187,97 @@ private module FlaskModel {
187
187
*/
188
188
DataFlow:: Node response_class ( ) { result = response_class ( DataFlow:: TypeTracker:: end ( ) ) }
189
189
}
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
+ }
190
281
}
191
282
192
283
/**
@@ -261,6 +352,65 @@ private module FlaskModel {
261
352
// ---------------------------------------------------------------------------
262
353
// routing modeling
263
354
// ---------------------------------------------------------------------------
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
+
264
414
private string werkzeug_rule_re ( ) {
265
415
// since flask uses werkzeug internally, we are using its routing rules from
266
416
// https://github.com/pallets/werkzeug/blob/4dc8d6ab840d4b78cbd5789cef91b01e3bde01d5/src/werkzeug/routing.py#L138-L151
@@ -318,12 +468,36 @@ private module FlaskModel {
318
468
result .asCfgNode ( ) in [ node .getArg ( 0 ) , node .getArgByName ( "rule" ) ]
319
469
}
320
470
471
+ DataFlow:: Node getViewArg ( ) {
472
+ result .asCfgNode ( ) in [ node .getArg ( 2 ) , node .getArgByName ( "view_func" ) ]
473
+ }
474
+
321
475
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
325
478
func_src .asExpr ( ) .( CallableExpr ) = result .getDefinition ( )
326
479
)
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 )
327
501
}
328
502
}
329
503
0 commit comments