@@ -253,6 +253,46 @@ class GraphqlFieldDefinitionMethodCall extends GraphqlSchemaObjectClassMethodCal
253
253
254
254
/** Gets the name of this GraphQL field. */
255
255
string getFieldName ( ) { result = this .getArgument ( 0 ) .getConstantValue ( ) .getStringlikeValue ( ) }
256
+
257
+ /**
258
+ * Gets the type of this field.
259
+ */
260
+ GraphqlType getFieldType ( ) { result = this .getArgument ( 1 ) }
261
+
262
+ /**
263
+ * Gets an argument call inside this field definition.
264
+ */
265
+ GraphqlFieldArgumentDefinitionMethodCall getAnArgumentCall ( ) { result = this .getArgumentCall ( _) }
266
+
267
+ /**
268
+ * Gets the argument call for `name` inside this field definition.
269
+ */
270
+ GraphqlFieldArgumentDefinitionMethodCall getArgumentCall ( string name ) {
271
+ result .getEnclosingCallable ( ) = this .getBlock ( ) and result .getArgumentName ( ) = name
272
+ }
273
+ }
274
+
275
+ /**
276
+ * A call to `argument` in a GraphQL InputObject class.
277
+ */
278
+ class GraphqlInputObjectArgumentDefinitionCall extends DataFlow:: CallNode {
279
+ GraphqlInputObjectArgumentDefinitionCall ( ) {
280
+ this =
281
+ graphQlSchema ( )
282
+ .getMember ( "InputObject" )
283
+ .getADescendentModule ( )
284
+ .getAnOwnModuleSelf ( )
285
+ .getAMethodCall ( "argument" )
286
+ }
287
+
288
+ /** Gets the name of the argument (i.e. the first argument to this `argument` method call) */
289
+ string getArgumentName ( ) { result = this .getArgument ( 0 ) .getConstantValue ( ) .getStringlikeValue ( ) }
290
+
291
+ /** Gets the type of this argument */
292
+ GraphqlType getArgumentType ( ) { result = this .getArgument ( 1 ) .asExpr ( ) .getExpr ( ) }
293
+
294
+ /** Gets the class representing the receiver of this method. */
295
+ ClassDeclaration getReceiverClass ( ) { result = this .asExpr ( ) .getExpr ( ) .getEnclosingModule ( ) }
256
296
}
257
297
258
298
/**
@@ -289,6 +329,64 @@ private class GraphqlFieldArgumentDefinitionMethodCall extends GraphqlSchemaObje
289
329
290
330
/** Gets the name of the argument (i.e. the first argument to this `argument` method call) */
291
331
string getArgumentName ( ) { result = this .getArgument ( 0 ) .getConstantValue ( ) .getStringlikeValue ( ) }
332
+
333
+ /** Gets the type of this argument */
334
+ GraphqlType getArgumentType ( ) { result = this .getArgument ( 1 ) }
335
+
336
+ /**
337
+ * Gets the element type of this argument, if it is an array.
338
+ * For example if the argument type is `[String]`, this predicate yields `String`.
339
+ */
340
+ GraphqlType getArgumentElementType ( ) {
341
+ result =
342
+ any ( ArrayLiteral lit | lit = this .getArgument ( 1 ) and lit .getNumberOfElements ( ) = 1 )
343
+ .getElement ( 0 )
344
+ }
345
+ }
346
+
347
+ private class GraphqlType extends ConstantAccess {
348
+ /**
349
+ * Gets the module corresponding to this type, if it exists.
350
+ */
351
+ Module getModule ( ) { result .getAnImmediateReference ( ) = this }
352
+
353
+ /**
354
+ * Gets the type of a field/argument of this type, if it is an object type.
355
+ */
356
+ GraphqlType getAFieldOrArgument ( ) { result = this .getFieldOrArgument ( _) }
357
+
358
+ /**
359
+ * Gets the type of the `name` field/argument of this type, if it exists.
360
+ */
361
+ GraphqlType getFieldOrArgument ( string name ) {
362
+ result =
363
+ any ( GraphqlFieldDefinitionMethodCall field |
364
+ field .getFieldName ( ) = name and
365
+ this .getModule ( ) .getADeclaration ( ) = field .getReceiverClass ( )
366
+ ) .getFieldType ( ) or
367
+ result =
368
+ any ( GraphqlInputObjectArgumentDefinitionCall arg |
369
+ arg .getArgumentName ( ) = name and this .getModule ( ) .getADeclaration ( ) = arg .getReceiverClass ( )
370
+ ) .getArgumentType ( )
371
+ }
372
+
373
+ /**
374
+ * Holds if this type is an enum.
375
+ */
376
+ predicate isEnum ( ) {
377
+ API:: getTopLevelMember ( "GraphQL" )
378
+ .getMember ( "Schema" )
379
+ .getMember ( "Enum" )
380
+ .getADescendentModule ( )
381
+ .getAnImmediateReference ( )
382
+ .asExpr ( )
383
+ .getExpr ( ) = this
384
+ }
385
+
386
+ /**
387
+ * Holds if this type is scalar - i.e. it is neither an object or an enum.
388
+ */
389
+ predicate isScalar ( ) { not exists ( this .getAFieldOrArgument ( ) ) and not this .isEnum ( ) }
292
390
}
293
391
294
392
/**
@@ -350,29 +448,26 @@ class GraphqlFieldResolutionMethod extends Method, Http::Server::RequestHandler:
350
448
351
449
/** Gets the method call which is the definition of the field corresponding to this resolver method. */
352
450
GraphqlFieldDefinitionMethodCall getDefinition ( ) {
353
- result
354
- .getKeywordArgument ( "resolver_method" )
355
- .getConstantValue ( )
356
- .isStringlikeValue ( this .getName ( ) )
357
- or
358
- not exists ( result .getKeywordArgument ( "resolver_method" ) .( SymbolLiteral ) ) and
359
- result .getFieldName ( ) = this .getName ( )
451
+ result .getEnclosingModule ( ) = this .getEnclosingModule ( ) and
452
+ (
453
+ result
454
+ .getKeywordArgument ( "resolver_method" )
455
+ .getConstantValue ( )
456
+ .isStringlikeValue ( this .getName ( ) )
457
+ or
458
+ not exists ( result .getKeywordArgument ( "resolver_method" ) .( SymbolLiteral ) ) and
459
+ result .getFieldName ( ) = this .getName ( )
460
+ )
360
461
}
361
462
362
463
// check for a named argument the same name as a defined argument for this field
363
464
override Parameter getARoutedParameter ( ) {
364
465
result = this .getAParameter ( ) and
365
466
exists ( GraphqlFieldArgumentDefinitionMethodCall argDefn |
366
- argDefn .getEnclosingCallable ( ) = this .getDefinition ( ) .getBlock ( ) and
367
- (
368
- result .( KeywordParameter ) .hasName ( argDefn .getArgumentName ( ) )
369
- or
370
- // TODO this will cause false positives because now *anything* in the **args
371
- // param will be flagged as RoutedParameter/RemoteFlowSource, but really
372
- // only the hash keys corresponding to the defined arguments are user input
373
- // others could be things defined in the `:extras` keyword argument to the `argument`
374
- result instanceof HashSplatParameter // often you see `def field(**args)`
375
- )
467
+ argDefn = this .getDefinition ( ) .getAnArgumentCall ( ) and
468
+ [ argDefn .getArgumentType ( ) , argDefn .getArgumentElementType ( ) ] .isScalar ( )
469
+ |
470
+ result .( KeywordParameter ) .hasName ( argDefn .getArgumentName ( ) )
376
471
)
377
472
}
378
473
@@ -383,3 +478,80 @@ class GraphqlFieldResolutionMethod extends Method, Http::Server::RequestHandler:
383
478
/** Gets the class containing this method. */
384
479
GraphqlSchemaObjectClass getGraphqlClass ( ) { result = schemaObjectClass }
385
480
}
481
+
482
+ private DataFlow:: CallNode hashParameterAccess (
483
+ GraphqlFieldResolutionMethod method , HashSplatParameter param , GraphqlType type
484
+ ) {
485
+ exists (
486
+ DataFlow:: LocalSourceNode paramNode , GraphqlFieldArgumentDefinitionMethodCall def , string key
487
+ |
488
+ param = method .getAParameter ( ) and
489
+ paramNode .( DataFlow:: ParameterNode ) .getParameter ( ) = param and
490
+ def = method .getDefinition ( ) .getAnArgumentCall ( ) and
491
+ (
492
+ // Direct access to the params hash
493
+ [ def .getArgumentType ( ) , def .getArgumentElementType ( ) ] = type and
494
+ def .getArgumentName ( ) = key and
495
+ paramNode .flowsTo ( hashAccess ( result , key ) )
496
+ or
497
+ // Nested access
498
+ exists ( GraphqlType type2 |
499
+ hashParameterAccess ( method , param , type2 )
500
+ .( DataFlow:: LocalSourceNode )
501
+ .flowsTo ( hashAccess ( result , key ) ) and
502
+ type2 .getFieldOrArgument ( key ) = type
503
+ )
504
+ )
505
+ )
506
+ }
507
+
508
+ private DataFlow:: Node parameterAccess (
509
+ GraphqlFieldResolutionMethod method , DataFlow:: LocalSourceNode param , GraphqlType type
510
+ ) {
511
+ param = getAGraphqlParameter ( method , type ) and
512
+ result = param
513
+ or
514
+ exists ( string key , GraphqlType type2 |
515
+ param = parameterAccess ( method , _, type2 ) and
516
+ param .flowsTo ( hashAccess ( result , key ) ) and
517
+ type2 .getFieldOrArgument ( key ) = type
518
+ )
519
+ }
520
+
521
+ private DataFlow:: ParameterNode getAGraphqlParameter (
522
+ GraphqlFieldResolutionMethod method , GraphqlType type
523
+ ) {
524
+ result .getCallable ( ) = method and
525
+ (
526
+ result .getParameter ( ) instanceof KeywordParameter and
527
+ exists ( GraphqlFieldArgumentDefinitionMethodCall c |
528
+ c = method .getDefinition ( ) .getArgumentCall ( result .getName ( ) )
529
+ |
530
+ type = [ c .getArgumentType ( ) , c .getArgumentElementType ( ) ]
531
+ )
532
+ or
533
+ result .getParameter ( ) instanceof SimpleParameter and
534
+ type = method .getDefinition ( ) .getFieldType ( )
535
+ )
536
+ }
537
+
538
+ /**
539
+ * Gets the receiver of the hash access `access` with key `key`.
540
+ * For example, in `h["foo"]` the receiver is `h`, the key is "foo"
541
+ * and `access` is the dataflow node for the whole expression.
542
+ */
543
+ private DataFlow:: Node hashAccess ( DataFlow:: CallNode access , string key ) {
544
+ access .asExpr ( ) instanceof ExprNodes:: ElementReferenceCfgNode and
545
+ access .getArgument ( 0 ) .getConstantValue ( ) .isStringlikeValue ( key ) and
546
+ access .getReceiver ( ) = result
547
+ }
548
+
549
+ private class GraphqlParameterAccess extends RemoteFlowSource:: Range {
550
+ GraphqlParameterAccess ( ) {
551
+ exists ( GraphqlType type | type .isScalar ( ) |
552
+ this = hashParameterAccess ( _, _, type ) or this = parameterAccess ( _, _, type )
553
+ )
554
+ }
555
+
556
+ override string getSourceType ( ) { result = "GraphQL" }
557
+ }
0 commit comments