@@ -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,76 @@ 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
+ }
190
260
}
191
261
192
262
/**
@@ -261,6 +331,52 @@ private module FlaskModel {
261
331
// ---------------------------------------------------------------------------
262
332
// routing modeling
263
333
// ---------------------------------------------------------------------------
334
+ /** A flask View class defined in project code. */
335
+ class FlaskViewClassDef extends Class {
336
+ FlaskViewClassDef ( ) { this .getABase ( ) = flask:: views:: View:: subclassRef ( ) .asExpr ( ) }
337
+
338
+ /** Gets a function that could handle incoming requests, if any. */
339
+ Function getARequestHandler ( ) {
340
+ // TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
341
+ // points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
342
+ result = this .getAMethod ( ) and
343
+ result .getName ( ) = "dispatch_request"
344
+ }
345
+
346
+ /** Gets a reference to this class. */
347
+ private DataFlow:: Node getARef ( DataFlow:: TypeTracker t ) {
348
+ t .start ( ) and
349
+ result .asExpr ( ) .( ClassExpr ) = this .getParent ( )
350
+ or
351
+ exists ( DataFlow:: TypeTracker t2 | result = this .getARef ( t2 ) .track ( t2 , t ) )
352
+ }
353
+
354
+ /** Gets a reference to this class. */
355
+ DataFlow:: Node getARef ( ) { result = this .getARef ( DataFlow:: TypeTracker:: end ( ) ) }
356
+
357
+ /** Gets a reference to the `as_view` classmethod of this class. */
358
+ private DataFlow:: Node asViewRef ( DataFlow:: TypeTracker t ) {
359
+ t .startInAttr ( "as_view" ) and
360
+ result = this .getARef ( )
361
+ or
362
+ exists ( DataFlow:: TypeTracker t2 | result = this .asViewRef ( t2 ) .track ( t2 , t ) )
363
+ }
364
+
365
+ /** Gets a reference to the `as_view` classmethod of this class. */
366
+ DataFlow:: Node asViewRef ( ) { result = this .asViewRef ( DataFlow:: TypeTracker:: end ( ) ) }
367
+
368
+ /** Gets a reference to the result of calling the `as_view` classmethod of this class. */
369
+ private DataFlow:: Node asViewResult ( DataFlow:: TypeTracker t ) {
370
+ t .start ( ) and
371
+ result .asCfgNode ( ) .( CallNode ) .getFunction ( ) = this .asViewRef ( ) .asCfgNode ( )
372
+ or
373
+ exists ( DataFlow:: TypeTracker t2 | result = asViewResult ( t2 ) .track ( t2 , t ) )
374
+ }
375
+
376
+ /** Gets a reference to the result of calling the `as_view` classmethod of this class. */
377
+ DataFlow:: Node asViewResult ( ) { result = asViewResult ( DataFlow:: TypeTracker:: end ( ) ) }
378
+ }
379
+
264
380
private string werkzeug_rule_re ( ) {
265
381
// since flask uses werkzeug internally, we are using its routing rules from
266
382
// https://github.com/pallets/werkzeug/blob/4dc8d6ab840d4b78cbd5789cef91b01e3bde01d5/src/werkzeug/routing.py#L138-L151
@@ -318,12 +434,36 @@ private module FlaskModel {
318
434
result .asCfgNode ( ) in [ node .getArg ( 0 ) , node .getArgByName ( "rule" ) ]
319
435
}
320
436
437
+ DataFlow:: Node getViewArg ( ) {
438
+ result .asCfgNode ( ) in [ node .getArg ( 2 ) , node .getArgByName ( "view_func" ) ]
439
+ }
440
+
321
441
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
442
+ exists ( DataFlow:: LocalSourceNode func_src |
443
+ func_src .flowsTo ( getViewArg ( ) ) and
325
444
func_src .asExpr ( ) .( CallableExpr ) = result .getDefinition ( )
326
445
)
446
+ or
447
+ exists ( FlaskViewClassDef vc |
448
+ getViewArg ( ) = vc .asViewResult ( ) and
449
+ result = vc .getARequestHandler ( )
450
+ )
451
+ }
452
+ }
453
+
454
+ /** A request handler defined in a django view class, that has no known route. */
455
+ private class FlaskViewClassHandlerWithoutKnownRoute extends HTTP:: Server:: RequestHandler:: Range {
456
+ FlaskViewClassHandlerWithoutKnownRoute ( ) {
457
+ exists ( FlaskViewClassDef vc | vc .getARequestHandler ( ) = this ) and
458
+ not exists ( FlaskRouteSetup setup | setup .getARequestHandler ( ) = this )
459
+ }
460
+
461
+ override Parameter getARoutedParameter ( ) {
462
+ // Since we don't know the URL pattern, we simply mark all parameters as a routed
463
+ // parameter. This should give us more RemoteFlowSources but could also lead to
464
+ // more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
465
+ result in [ this .getArg ( _) , this .getArgByName ( _) ] and
466
+ not result = this .getArg ( 0 )
327
467
}
328
468
}
329
469
0 commit comments