Skip to content

Commit 5706bc6

Browse files
committed
Ruby: Model GraphQL InputObject arguments
1 parent 5411123 commit 5706bc6

File tree

6 files changed

+89
-41
lines changed

6 files changed

+89
-41
lines changed

ruby/ql/lib/codeql/ruby/frameworks/GraphQL.qll

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,29 @@ class GraphqlFieldDefinitionMethodCall extends GraphqlSchemaObjectClassMethodCal
267267
}
268268
}
269269

270+
/**
271+
* A call to `argument` in a GraphQL InputObject class.
272+
*/
273+
class GraphqlInputObjectArgumentDefinitionCall extends DataFlow::CallNode {
274+
GraphqlInputObjectArgumentDefinitionCall() {
275+
this =
276+
graphQlSchema()
277+
.getMember("InputObject")
278+
.getADescendentModule()
279+
.getAnOwnModuleSelf()
280+
.getAMethodCall()
281+
}
282+
283+
/** Gets the name of the argument (i.e. the first argument to this `argument` method call) */
284+
string getArgumentName() { result = this.getArgument(0).getConstantValue().getStringlikeValue() }
285+
286+
/** Gets the type of this argument */
287+
GraphqlType getArgumentType() { result = this.getArgument(1).asExpr().getExpr() }
288+
289+
/** Gets the class representing the receiver of this method. */
290+
ClassDeclaration getReceiverClass() { result = this.asExpr().getExpr().getEnclosingModule() }
291+
}
292+
270293
/**
271294
* A `MethodCall` that represents calling the class method `argument` inside the
272295
* block for a `field` definition on a GraphQL object.
@@ -313,19 +336,23 @@ private class GraphqlType extends ConstantAccess {
313336
Module getModule() { result.getAnImmediateReference() = this }
314337

315338
/**
316-
* Gets a field of this type, if it is an object type.
339+
* Gets the type of a field/argument of this type, if it is an object type.
317340
*/
318-
GraphqlType getAField() { result = this.getField(_) }
341+
GraphqlType getAFieldOrArgument() { result = this.getFieldOrArgument(_) }
319342

320343
/**
321-
* Gets the field of this type named `name`, if it exists.
344+
* Gets the type of the `name` field/argument of this type, if it exists.
322345
*/
323-
GraphqlType getField(string name) {
346+
GraphqlType getFieldOrArgument(string name) {
324347
result =
325348
any(GraphqlFieldDefinitionMethodCall field |
326349
field.getFieldName() = name and
327350
this.getModule().getADeclaration() = field.getReceiverClass()
328-
).getFieldType()
351+
).getFieldType() or
352+
result =
353+
any(GraphqlInputObjectArgumentDefinitionCall arg |
354+
arg.getArgumentName() = name and this.getModule().getADeclaration() = arg.getReceiverClass()
355+
).getArgumentType()
329356
}
330357

331358
/**
@@ -344,7 +371,7 @@ private class GraphqlType extends ConstantAccess {
344371
/**
345372
* Holds if this type is scalar - i.e. it is neither an object or an enum.
346373
*/
347-
predicate isScalar() { not exists(this.getAField()) and not this.isEnum() }
374+
predicate isScalar() { not exists(this.getAFieldOrArgument()) and not this.isEnum() }
348375
}
349376

350377
/**
@@ -440,34 +467,35 @@ private DataFlow::CallNode hashAccess(DataFlow::Node recv, string key) {
440467
}
441468

442469
private DataFlow::CallNode parameterAccess(
443-
GraphqlFieldResolutionMethod method, GraphqlFieldArgumentDefinitionMethodCall def,
444-
HashSplatParameter param, string key, GraphqlType type
470+
GraphqlFieldResolutionMethod method, HashSplatParameter param, GraphqlType type
445471
) {
446-
param = method.getAParameter() and
447-
def = method.getDefinition().getAnArgumentCall() and
448-
(
449-
// Direct access to the params hash
450-
def.getArgumentType() = type and
451-
def.getArgumentName() = key and
452-
exists(DataFlow::Node paramRead |
453-
paramRead.asExpr().getExpr() = param.getVariable().getAnAccess().(VariableReadAccess) and
454-
result = hashAccess(paramRead, key)
455-
)
456-
or
457-
// Nested access
458-
exists(GraphqlType type2 |
459-
parameterAccess(method, _, param, _, type2)
460-
.(DataFlow::LocalSourceNode)
461-
.flowsTo(result.getReceiver()) and
462-
result = hashAccess(_, key) and
463-
type2.getField(key) = type
472+
exists(GraphqlFieldArgumentDefinitionMethodCall def, string key |
473+
param = method.getAParameter() and
474+
def = method.getDefinition().getAnArgumentCall() and
475+
(
476+
// Direct access to the params hash
477+
def.getArgumentType() = type and
478+
def.getArgumentName() = key and
479+
exists(DataFlow::Node paramRead |
480+
paramRead.asExpr().getExpr() = param.getVariable().getAnAccess().(VariableReadAccess) and
481+
result = hashAccess(paramRead, key)
482+
)
483+
or
484+
// Nested access
485+
exists(GraphqlType type2 |
486+
parameterAccess(method, param, type2)
487+
.(DataFlow::LocalSourceNode)
488+
.flowsTo(result.getReceiver()) and
489+
result = hashAccess(_, key) and
490+
type2.getFieldOrArgument(key) = type
491+
)
464492
)
465493
)
466494
}
467495

468496
private class GraphqlParameterAccess extends RemoteFlowSource::Range {
469497
GraphqlParameterAccess() {
470-
exists(GraphqlType type | this = parameterAccess(_, _, _, _, type) and type.isScalar())
498+
exists(GraphqlType type | this = parameterAccess(_, _, type) and type.isScalar())
471499
}
472500

473501
override string getSourceType() { result = "GraphQL" }

ruby/ql/test/library-tests/frameworks/graphql/GraphQL.expected

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
graphqlSchemaObjectClass
22
| app/graphql/types/base_object.rb:2:3:4:5 | BaseObject |
33
| app/graphql/types/mutation_type.rb:2:3:4:5 | MutationType |
4-
| app/graphql/types/post.rb:1:1:5:5 | Post |
5-
| app/graphql/types/query_type.rb:2:3:63:5 | QueryType |
4+
| app/graphql/types/post.rb:1:1:6:5 | Post |
5+
| app/graphql/types/query_type.rb:2:3:64:5 | QueryType |
66
graphqlSchemaObjectFieldDefinition
77
| app/graphql/types/mutation_type.rb:2:3:4:5 | MutationType | app/graphql/types/mutation_type.rb:3:5:3:44 | call to field |
8-
| app/graphql/types/post.rb:1:1:5:5 | Post | app/graphql/types/post.rb:2:5:2:24 | call to field |
9-
| app/graphql/types/post.rb:1:1:5:5 | Post | app/graphql/types/post.rb:3:5:3:36 | call to field |
10-
| app/graphql/types/post.rb:1:1:5:5 | Post | app/graphql/types/post.rb:4:5:4:60 | call to field |
11-
| app/graphql/types/query_type.rb:2:3:63:5 | QueryType | app/graphql/types/query_type.rb:3:5:5:40 | call to field |
12-
| app/graphql/types/query_type.rb:2:3:63:5 | QueryType | app/graphql/types/query_type.rb:7:5:9:7 | call to field |
13-
| app/graphql/types/query_type.rb:2:3:63:5 | QueryType | app/graphql/types/query_type.rb:15:5:17:7 | call to field |
14-
| app/graphql/types/query_type.rb:2:3:63:5 | QueryType | app/graphql/types/query_type.rb:24:5:26:7 | call to field |
15-
| app/graphql/types/query_type.rb:2:3:63:5 | QueryType | app/graphql/types/query_type.rb:32:5:35:7 | call to field |
16-
| app/graphql/types/query_type.rb:2:3:63:5 | QueryType | app/graphql/types/query_type.rb:46:5:49:7 | call to field |
17-
| app/graphql/types/query_type.rb:2:3:63:5 | QueryType | app/graphql/types/query_type.rb:55:5:57:7 | call to field |
8+
| app/graphql/types/post.rb:1:1:6:5 | Post | app/graphql/types/post.rb:2:5:2:24 | call to field |
9+
| app/graphql/types/post.rb:1:1:6:5 | Post | app/graphql/types/post.rb:3:5:3:36 | call to field |
10+
| app/graphql/types/post.rb:1:1:6:5 | Post | app/graphql/types/post.rb:4:5:4:60 | call to field |
11+
| app/graphql/types/post.rb:1:1:6:5 | Post | app/graphql/types/post.rb:5:5:5:51 | call to field |
12+
| app/graphql/types/query_type.rb:2:3:64:5 | QueryType | app/graphql/types/query_type.rb:3:5:5:40 | call to field |
13+
| app/graphql/types/query_type.rb:2:3:64:5 | QueryType | app/graphql/types/query_type.rb:7:5:9:7 | call to field |
14+
| app/graphql/types/query_type.rb:2:3:64:5 | QueryType | app/graphql/types/query_type.rb:15:5:17:7 | call to field |
15+
| app/graphql/types/query_type.rb:2:3:64:5 | QueryType | app/graphql/types/query_type.rb:24:5:26:7 | call to field |
16+
| app/graphql/types/query_type.rb:2:3:64:5 | QueryType | app/graphql/types/query_type.rb:32:5:35:7 | call to field |
17+
| app/graphql/types/query_type.rb:2:3:64:5 | QueryType | app/graphql/types/query_type.rb:46:5:49:7 | call to field |
18+
| app/graphql/types/query_type.rb:2:3:64:5 | QueryType | app/graphql/types/query_type.rb:55:5:57:7 | call to field |
1819
graphqlResolveMethod
1920
| app/graphql/mutations/dummy.rb:9:5:12:7 | resolve |
2021
| app/graphql/resolvers/dummy_resolver.rb:10:5:13:7 | resolve |
@@ -32,6 +33,7 @@ graphqlFieldDefinitionMethodCall
3233
| app/graphql/types/post.rb:2:5:2:24 | call to field |
3334
| app/graphql/types/post.rb:3:5:3:36 | call to field |
3435
| app/graphql/types/post.rb:4:5:4:60 | call to field |
36+
| app/graphql/types/post.rb:5:5:5:51 | call to field |
3537
| app/graphql/types/query_type.rb:3:5:5:40 | call to field |
3638
| app/graphql/types/query_type.rb:7:5:9:7 | call to field |
3739
| app/graphql/types/query_type.rb:15:5:17:7 | call to field |
@@ -45,7 +47,7 @@ graphqlFieldResolutionMethod
4547
| app/graphql/types/query_type.rb:27:5:30:7 | with_splat |
4648
| app/graphql/types/query_type.rb:36:5:40:7 | with_splat_and_named_arg |
4749
| app/graphql/types/query_type.rb:50:5:53:7 | with_enum |
48-
| app/graphql/types/query_type.rb:58:5:62:7 | with_nested_enum |
50+
| app/graphql/types/query_type.rb:58:5:63:7 | with_nested_enum |
4951
graphqlFieldResolutionRoutedParameter
5052
| app/graphql/types/query_type.rb:10:5:13:7 | with_arg | app/graphql/types/query_type.rb:10:18:10:23 | number |
5153
| app/graphql/types/query_type.rb:18:5:22:7 | custom_method | app/graphql/types/query_type.rb:18:23:18:33 | blah_number |
@@ -56,12 +58,17 @@ graphqlFieldResolutionDefinition
5658
| app/graphql/types/query_type.rb:27:5:30:7 | with_splat | app/graphql/types/query_type.rb:24:5:26:7 | call to field |
5759
| app/graphql/types/query_type.rb:36:5:40:7 | with_splat_and_named_arg | app/graphql/types/query_type.rb:32:5:35:7 | call to field |
5860
| app/graphql/types/query_type.rb:50:5:53:7 | with_enum | app/graphql/types/query_type.rb:46:5:49:7 | call to field |
59-
| app/graphql/types/query_type.rb:58:5:62:7 | with_nested_enum | app/graphql/types/query_type.rb:55:5:57:7 | call to field |
61+
| app/graphql/types/query_type.rb:58:5:63:7 | with_nested_enum | app/graphql/types/query_type.rb:55:5:57:7 | call to field |
6062
graphqlRemoteFlowSources
6163
| app/graphql/mutations/dummy.rb:5:24:5:25 | id |
6264
| app/graphql/mutations/dummy.rb:9:17:9:25 | something |
6365
| app/graphql/resolvers/dummy_resolver.rb:6:24:6:25 | id |
6466
| app/graphql/resolvers/dummy_resolver.rb:10:17:10:25 | something |
6567
| app/graphql/types/query_type.rb:10:18:10:23 | number |
6668
| app/graphql/types/query_type.rb:18:23:18:33 | blah_number |
69+
| app/graphql/types/query_type.rb:28:22:28:37 | ...[...] |
70+
| app/graphql/types/query_type.rb:29:7:29:22 | ...[...] |
6771
| app/graphql/types/query_type.rb:36:34:36:37 | arg1 |
72+
| app/graphql/types/query_type.rb:38:22:38:32 | ...[...] |
73+
| app/graphql/types/query_type.rb:52:22:52:32 | ...[...] |
74+
| app/graphql/types/query_type.rb:60:22:60:41 | ...[...] |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Types
2+
class Direction < Types::BaseEnum
3+
value "asc", "Ascending order", value: "asc"
4+
value "desc", "Descending order", value: "desc"
5+
end
6+
end

ruby/ql/test/library-tests/frameworks/graphql/app/graphql/types/post.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ class Types::Post < GraphQL::Schema::Object
22
field :title, String
33
field :body, String, null: false
44
field :media_category, Types::MediaCategory, null: false
5+
field :direction, Types::Direction, null: false
56
end
67
end
78

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module Types
2+
class PostOrder < Types::BaseInputObject
3+
argument :direction, Types::Direction, "The ordering direction", required: true
4+
end
5+
end

ruby/ql/test/library-tests/frameworks/graphql/app/graphql/types/query_type.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def with_nested_enum(**args)
5959
system("echo #{args[:inner]}")
6060
system("echo #{args[:inner][:title]}")
6161
system("echo #{args[:inner][:media_category]}")
62+
system("echo #{args[:inner][:direction]}")
6263
end
6364
end
6465
end

0 commit comments

Comments
 (0)