@@ -253,6 +253,46 @@ class GraphqlFieldDefinitionMethodCall extends GraphqlSchemaObjectClassMethodCal
253253
254254 /** Gets the name of this GraphQL field. */
255255 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 ( ) }
256296}
257297
258298/**
@@ -289,6 +329,64 @@ private class GraphqlFieldArgumentDefinitionMethodCall extends GraphqlSchemaObje
289329
290330 /** Gets the name of the argument (i.e. the first argument to this `argument` method call) */
291331 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 ( ) }
292390}
293391
294392/**
@@ -350,29 +448,26 @@ class GraphqlFieldResolutionMethod extends Method, Http::Server::RequestHandler:
350448
351449 /** Gets the method call which is the definition of the field corresponding to this resolver method. */
352450 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+ )
360461 }
361462
362463 // check for a named argument the same name as a defined argument for this field
363464 override Parameter getARoutedParameter ( ) {
364465 result = this .getAParameter ( ) and
365466 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 ( ) )
376471 )
377472 }
378473
@@ -383,3 +478,80 @@ class GraphqlFieldResolutionMethod extends Method, Http::Server::RequestHandler:
383478 /** Gets the class containing this method. */
384479 GraphqlSchemaObjectClass getGraphqlClass ( ) { result = schemaObjectClass }
385480}
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