graphql-guard 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +10 -6
- data/README.md +113 -40
- data/graphql-guard.gemspec +1 -1
- data/lib/graphql/guard.rb +8 -0
- data/lib/graphql/guard/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1356ec0ce6198540fffd1ca245c4b138792bc81ff776094a8b1ac6e12e2940d6
|
4
|
+
data.tar.gz: 1282de4a1c06f4c32bba870d8076fc94a276bf39cb2cb56e0cbd5f880aa73ff9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6c831b4be4415ed4b10f575bab5b7b2d6888965972bd9bdb7dcc1ec4477f46542e66d8b8d1566b27a3e4f044e66bc76a018920567710f4f7f723a8a6e4b02c7
|
7
|
+
data.tar.gz: ac3d9a36703f5f206ba40c776d2d0476a287ab99534b2e1fd27e97459ed7af3549d6cc4ef956b7f8a878f26f9b879cd8f42f0746d1c2e8d3b23f246b54f76f76
|
data/CHANGELOG.md
CHANGED
@@ -8,15 +8,19 @@ one of the following labels: `Added`, `Changed`, `Deprecated`,
|
|
8
8
|
to manage the versions of this gem so
|
9
9
|
that you can set version constraints properly.
|
10
10
|
|
11
|
-
#### [Unreleased](https://github.com/exAspArk/graphql-guard/compare/v1.
|
11
|
+
#### [Unreleased](https://github.com/exAspArk/graphql-guard/compare/v1.1.0...HEAD)
|
12
12
|
|
13
13
|
* WIP
|
14
14
|
|
15
|
+
#### [v1.1.0](https://github.com/exAspArk/graphql-guard/compare/v1.0.0...v1.1.0) – 2018-05-09
|
16
|
+
|
17
|
+
* `Added`: support to `mask` fields depending on the context.
|
18
|
+
|
15
19
|
#### [v1.0.0](https://github.com/exAspArk/graphql-guard/compare/v0.4.0...v1.0.0) – 2017-07-31
|
16
20
|
|
17
21
|
* `Changed`: guards for every `*` field also accepts arguments: `->(object, arguments, context) { ... }`:
|
18
22
|
|
19
|
-
|
23
|
+
Before:
|
20
24
|
|
21
25
|
<pre>
|
22
26
|
GraphQL::ObjectType.define do
|
@@ -26,7 +30,7 @@ GraphQL::ObjectType.define do
|
|
26
30
|
end
|
27
31
|
</pre>
|
28
32
|
|
29
|
-
|
33
|
+
After:
|
30
34
|
|
31
35
|
<pre>
|
32
36
|
GraphQL::ObjectType.define do
|
@@ -38,14 +42,14 @@ end
|
|
38
42
|
|
39
43
|
* `Changed`: `.field_with_guard` from `graphql/guard/testing` module accepts policy object as a second argument:
|
40
44
|
|
41
|
-
|
45
|
+
Before:
|
42
46
|
|
43
47
|
<pre>
|
44
|
-
guard_object = GraphQL::Guard.new(policy_object: GraphqlPolicy)
|
48
|
+
<b>guard_object</b> = GraphQL::Guard.new(policy_object: GraphqlPolicy)
|
45
49
|
posts_field = QueryType.field_with_guard('posts', <b>guard_object</b>)
|
46
50
|
</pre>
|
47
51
|
|
48
|
-
|
52
|
+
After:
|
49
53
|
|
50
54
|
<pre>
|
51
55
|
posts_field = QueryType.field_with_guard('posts', <b>GraphqlPolicy</b>)
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[](https://travis-ci.org/exAspArk/graphql-guard)
|
4
4
|
[](https://coveralls.io/github/exAspArk/graphql-guard)
|
5
|
-
[](https://codeclimate.com/github/exAspArk/graphql-guard/maintainability)
|
6
6
|
[](https://rubygems.org/gems/graphql-guard)
|
7
7
|
[](https://rubygems.org/gems/graphql-guard)
|
8
8
|
|
@@ -14,10 +14,11 @@ This gem provides a field-level authorization for [graphql-ruby](https://github.
|
|
14
14
|
* [Inline policies](#inline-policies)
|
15
15
|
* [Policy object](#policy-object)
|
16
16
|
* [Priority order](#priority-order)
|
17
|
-
* [Error handling](#error-handling)
|
18
17
|
* [Integration](#integration)
|
19
18
|
* [CanCanCan](#cancancan)
|
20
19
|
* [Pundit](#pundit)
|
20
|
+
* [Error handling](#error-handling)
|
21
|
+
* [Schema masking](#schema-masking)
|
21
22
|
* [Installation](#installation)
|
22
23
|
* [Testing](#testing)
|
23
24
|
* [Development](#development)
|
@@ -25,6 +26,10 @@ This gem provides a field-level authorization for [graphql-ruby](https://github.
|
|
25
26
|
* [License](#license)
|
26
27
|
* [Code of Conduct](#code-of-conduct)
|
27
28
|
|
29
|
+
<a href="https://www.universe.com/" target="_blank" rel="noopener noreferrer">
|
30
|
+
<img src="images/universe.png" height="41" width="153" alt="Sponsored by Universe" style="max-width:100%;">
|
31
|
+
</a>
|
32
|
+
|
28
33
|
## Usage
|
29
34
|
|
30
35
|
Define a GraphQL schema:
|
@@ -35,14 +40,14 @@ PostType = GraphQL::ObjectType.define do
|
|
35
40
|
name "Post"
|
36
41
|
|
37
42
|
field :id, !types.ID
|
38
|
-
field :title,
|
43
|
+
field :title, types.String
|
39
44
|
end
|
40
45
|
|
41
46
|
# Define a query
|
42
47
|
QueryType = GraphQL::ObjectType.define do
|
43
48
|
name "Query"
|
44
49
|
|
45
|
-
field :posts, !types[PostType] do
|
50
|
+
field :posts, !types[!PostType] do
|
46
51
|
argument :user_id, !types.ID
|
47
52
|
resolve ->(obj, args, ctx) { Post.where(user_id: args[:user_id]) }
|
48
53
|
end
|
@@ -54,7 +59,7 @@ Schema = GraphQL::Schema.define do
|
|
54
59
|
end
|
55
60
|
|
56
61
|
# Execute query
|
57
|
-
|
62
|
+
Schema.execute(query, variables: { user_id: 1 }, context: { current_user: current_user })
|
58
63
|
```
|
59
64
|
|
60
65
|
### Inline policies
|
@@ -74,7 +79,7 @@ Now you can define `guard` for a field, which will check permissions before reso
|
|
74
79
|
QueryType = GraphQL::ObjectType.define do
|
75
80
|
name "Query"
|
76
81
|
|
77
|
-
<b>field :posts</b>, !types[PostType] do
|
82
|
+
<b>field :posts</b>, !types[!PostType] do
|
78
83
|
argument :user_id, !types.ID
|
79
84
|
<b>guard ->(obj, args, ctx) {</b> args[:user_id] == ctx[:current_user].id <b>}</b>
|
80
85
|
...
|
@@ -124,6 +129,14 @@ Schema = GraphQL::Schema.define do
|
|
124
129
|
end
|
125
130
|
</pre>
|
126
131
|
|
132
|
+
When using a policy object, you may want to allow [introspection queries](http://graphql.org/learn/introspection/) to skip authorization. A simple way to avoid having to whitelist every introspection type in the `RULES` hash of your policy object is to check the `type` parameter in the `guard` method:
|
133
|
+
|
134
|
+
<pre>
|
135
|
+
def self.guard(type, field)
|
136
|
+
<b>type.introspection? ? ->(_obj, _args, _ctx) { true } :</b> RULES.dig(type, field) # or "false" to restrict an access
|
137
|
+
end
|
138
|
+
</pre>
|
139
|
+
|
127
140
|
## Priority order
|
128
141
|
|
129
142
|
`GraphQL::Guard` will use the policy in the following order of priority:
|
@@ -159,34 +172,6 @@ Schema = GraphQL::Schema.define do
|
|
159
172
|
end
|
160
173
|
</pre>
|
161
174
|
|
162
|
-
## Error handling
|
163
|
-
|
164
|
-
By default `GraphQL::Guard` raises a `GraphQL::Guard::NotAuthorizedError` exception if access to field is not authorized.
|
165
|
-
You can change this behavior, by passing custom `not_authorized` lambda. For example:
|
166
|
-
|
167
|
-
<pre>
|
168
|
-
SchemaWithErrors = GraphQL::Schema.define do
|
169
|
-
query QueryType
|
170
|
-
use GraphQL::Guard.new(
|
171
|
-
# Returns an error in the response
|
172
|
-
<b>not_authorized: ->(type, field) { GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") }</b>
|
173
|
-
|
174
|
-
# By default it raises an error
|
175
|
-
# not_authorized: ->(type, field) { raise GraphQL::Guard::NotAuthorizedError.new("#{type}.#{field}") }
|
176
|
-
)
|
177
|
-
end
|
178
|
-
</pre>
|
179
|
-
|
180
|
-
In this case executing a query will continue, but return `nil` for not authorized field and also an array of `errors`:
|
181
|
-
|
182
|
-
<pre>
|
183
|
-
SchemaWithErrors.execute("query { <b>posts</b>(user_id: 1) { id title } }")
|
184
|
-
# => {
|
185
|
-
# "data" => <b>nil</b>,
|
186
|
-
# "errors" => [{ "messages" => <b>"Not authorized to access Query.posts"</b>, "locations": { ... }, "path" => [<b>"posts"</b>] }]
|
187
|
-
# }
|
188
|
-
</pre>
|
189
|
-
|
190
175
|
## Integration
|
191
176
|
|
192
177
|
You can simply reuse your existing policies if you really want. You don't need any monkey patches or magic for it ;)
|
@@ -211,12 +196,12 @@ end
|
|
211
196
|
# Use the ability in your guard
|
212
197
|
PostType = GraphQL::ObjectType.define do
|
213
198
|
name "Post"
|
214
|
-
|
199
|
+
guard ->(post, args, ctx) { <b>ctx[:current_ability].can?(:read, post)</b> }
|
215
200
|
...
|
216
201
|
end
|
217
202
|
|
218
203
|
# Pass the ability
|
219
|
-
|
204
|
+
Schema.execute(query, context: { <b>current_ability: Ability.new(current_user)</b> })
|
220
205
|
</pre>
|
221
206
|
|
222
207
|
### Pundit
|
@@ -232,12 +217,100 @@ end
|
|
232
217
|
# Use the ability in your guard
|
233
218
|
PostType = GraphQL::ObjectType.define do
|
234
219
|
name "Post"
|
235
|
-
|
220
|
+
guard ->(post, args, ctx) { <b>PostPolicy.new(ctx[:current_user], post).show?</b> }
|
236
221
|
...
|
237
222
|
end
|
238
223
|
|
239
224
|
# Pass current_user
|
240
|
-
|
225
|
+
Schema.execute(query, context: { <b>current_user: current_user</b> })
|
226
|
+
</pre>
|
227
|
+
|
228
|
+
## Error handling
|
229
|
+
|
230
|
+
By default `GraphQL::Guard` raises a `GraphQL::Guard::NotAuthorizedError` exception if access to the field is not authorized.
|
231
|
+
You can change this behavior, by passing custom `not_authorized` lambda. For example:
|
232
|
+
|
233
|
+
<pre>
|
234
|
+
SchemaWithErrors = GraphQL::Schema.define do
|
235
|
+
query QueryType
|
236
|
+
use GraphQL::Guard.new(
|
237
|
+
# By default it raises an error
|
238
|
+
# not_authorized: ->(type, field) { raise GraphQL::Guard::NotAuthorizedError.new("#{type}.#{field}") }
|
239
|
+
|
240
|
+
# Returns an error in the response
|
241
|
+
<b>not_authorized: ->(type, field) { GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") }</b>
|
242
|
+
)
|
243
|
+
end
|
244
|
+
</pre>
|
245
|
+
|
246
|
+
In this case executing a query will continue, but return `nil` for not authorized field and also an array of `errors`:
|
247
|
+
|
248
|
+
<pre>
|
249
|
+
SchemaWithErrors.execute("query { <b>posts</b>(user_id: 1) { id title } }")
|
250
|
+
# => {
|
251
|
+
# "data" => <b>nil</b>,
|
252
|
+
# "errors" => [{
|
253
|
+
# "messages" => <b>"Not authorized to access Query.posts"</b>,
|
254
|
+
# "locations": { "line" => 1, "column" => 9 },
|
255
|
+
# "path" => [<b>"posts"</b>]
|
256
|
+
# }]
|
257
|
+
# }
|
258
|
+
</pre>
|
259
|
+
|
260
|
+
In more advanced cases, you may want not to return `errors` only for some unauthorized fields. Simply return `nil` if user is not authorized to access the field. You can achieve it, for example, by placing the logic into your `PolicyObject`:
|
261
|
+
|
262
|
+
<pre>
|
263
|
+
class <b>GraphqlPolicy</b>
|
264
|
+
RULES = {
|
265
|
+
PostType => {
|
266
|
+
'*': {
|
267
|
+
guard: ->(obj, args, ctx) { ... },
|
268
|
+
<b>not_authorized:</b> ->(type, field) { GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") }
|
269
|
+
}
|
270
|
+
title: {
|
271
|
+
guard: ->(obj, args, ctx) { ... },
|
272
|
+
<b>not_authorized:</b> ->(type, field) { nil } # simply return nil if not authorized, no errors
|
273
|
+
}
|
274
|
+
}
|
275
|
+
}
|
276
|
+
|
277
|
+
def self.guard(type, field)
|
278
|
+
RULES.dig(type, field, :guard)
|
279
|
+
end
|
280
|
+
|
281
|
+
def self.<b>not_authorized_handler</b>(type, field)
|
282
|
+
RULES</b>.dig(type, field, <b>:not_authorized</b>) || RULES</b>.dig(type, :'*', <b>:not_authorized</b>)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
Schema = GraphQL::Schema.define do
|
287
|
+
query QueryType
|
288
|
+
mutation MutationType
|
289
|
+
|
290
|
+
use GraphQL::Guard.new(
|
291
|
+
policy_object: GraphqlPolicy,
|
292
|
+
not_authorized: ->(type, field) {
|
293
|
+
handler = GraphqlPolicy.<b>not_authorized_handler</b>(type, field)
|
294
|
+
handler.call(type, field)
|
295
|
+
}
|
296
|
+
)
|
297
|
+
end
|
298
|
+
</pre>
|
299
|
+
|
300
|
+
## Schema masking
|
301
|
+
|
302
|
+
It's possible to hide fields from being introspectable and accessible based on the context. For example:
|
303
|
+
|
304
|
+
<pre>
|
305
|
+
PostType = GraphQL::ObjectType.define do
|
306
|
+
name "Post"
|
307
|
+
|
308
|
+
field :id, !types.ID
|
309
|
+
field :title, types.String do
|
310
|
+
# The field "title" is accessible only for beta testers
|
311
|
+
<b>mask ->(ctx) {</b> ctx[:current_user].beta_tester? <b>}</b>
|
312
|
+
end
|
313
|
+
end
|
241
314
|
</pre>
|
242
315
|
|
243
316
|
## Installation
|
@@ -264,7 +337,7 @@ It's possible to test fields with `guard` in isolation:
|
|
264
337
|
# Your type
|
265
338
|
QueryType = GraphQL::ObjectType.define do
|
266
339
|
name "Query"
|
267
|
-
<b>field :posts</b>, !types[PostType], <b>guard ->(obj, args, ctx) {</b> ... <b>}</b>
|
340
|
+
<b>field :posts</b>, !types[!PostType], <b>guard ->(obj, args, ctx) {</b> ... <b>}</b>
|
268
341
|
end
|
269
342
|
|
270
343
|
# Your test
|
@@ -282,7 +355,7 @@ If you would like to test your fields with policy objects:
|
|
282
355
|
# Your type
|
283
356
|
QueryType = GraphQL::ObjectType.define do
|
284
357
|
name "Query"
|
285
|
-
<b>field :posts</b>, !types[PostType]
|
358
|
+
<b>field :posts</b>, !types[!PostType]
|
286
359
|
end
|
287
360
|
|
288
361
|
# Your policy object
|
data/graphql-guard.gemspec
CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
|
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
-
f.match(%r{^(
|
18
|
+
f.match(%r{^(spec|images)/})
|
19
19
|
end
|
20
20
|
spec.bindir = "exe"
|
21
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
data/lib/graphql/guard.rb
CHANGED
@@ -5,6 +5,7 @@ require "graphql/guard/version"
|
|
5
5
|
|
6
6
|
GraphQL::ObjectType.accepts_definitions(guard: GraphQL::Define.assign_metadata_key(:guard))
|
7
7
|
GraphQL::Field.accepts_definitions(guard: GraphQL::Define.assign_metadata_key(:guard))
|
8
|
+
GraphQL::Field.accepts_definitions(mask: GraphQL::Define.assign_metadata_key(:mask))
|
8
9
|
|
9
10
|
module GraphQL
|
10
11
|
class Guard
|
@@ -22,6 +23,13 @@ module GraphQL
|
|
22
23
|
|
23
24
|
def use(schema_definition)
|
24
25
|
schema_definition.instrument(:field, self)
|
26
|
+
schema_definition.target.instance_eval do
|
27
|
+
def default_filter
|
28
|
+
GraphQL::Filter.new(except: default_mask).merge(only: ->(schema_member, ctx) {
|
29
|
+
schema_member.metadata[:mask] ? schema_member.metadata[:mask].call(ctx) : true
|
30
|
+
})
|
31
|
+
end
|
32
|
+
end
|
25
33
|
end
|
26
34
|
|
27
35
|
def instrument(type, field)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-guard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- exAspArk
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-05-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -115,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
115
|
version: '0'
|
116
116
|
requirements: []
|
117
117
|
rubyforge_project:
|
118
|
-
rubygems_version: 2.
|
118
|
+
rubygems_version: 2.7.6
|
119
119
|
signing_key:
|
120
120
|
specification_version: 4
|
121
121
|
summary: Simple authorization gem for graphql-ruby
|