graphql-guard 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2fee7151ba4f1b935f9bb185bc1ecef61920d0bc
4
- data.tar.gz: d5286b90482d2e5a99827f05a3587d9f596a0669
2
+ SHA256:
3
+ metadata.gz: 1356ec0ce6198540fffd1ca245c4b138792bc81ff776094a8b1ac6e12e2940d6
4
+ data.tar.gz: 1282de4a1c06f4c32bba870d8076fc94a276bf39cb2cb56e0cbd5f880aa73ff9
5
5
  SHA512:
6
- metadata.gz: 36f519bf2ed0879655eced44f1a8062e13fcfc01d08565c7c92d33ee50cef08278e430054cc42ed9834a629dc16d3512b19cf14ef2c395a19f37929c615488c0
7
- data.tar.gz: c9873f1af3b37a07398e853871f661bb45d232d79cc833aa6f0bc2aca1104e961b8e2ecf2bda3a38950de7ad91181918dbe62447cb2267ac88966b88df9c3c26
6
+ metadata.gz: d6c831b4be4415ed4b10f575bab5b7b2d6888965972bd9bdb7dcc1ec4477f46542e66d8b8d1566b27a3e4f044e66bc76a018920567710f4f7f723a8a6e4b02c7
7
+ data.tar.gz: ac3d9a36703f5f206ba40c776d2d0476a287ab99534b2e1fd27e97459ed7af3549d6cc4ef956b7f8a878f26f9b879cd8f42f0746d1c2e8d3b23f246b54f76f76
@@ -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.0.0...HEAD)
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
- * Before:
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
- * After:
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
- * Before:
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
- * After:
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
  [![Build Status](https://travis-ci.org/exAspArk/graphql-guard.svg?branch=master)](https://travis-ci.org/exAspArk/graphql-guard)
4
4
  [![Coverage Status](https://coveralls.io/repos/github/exAspArk/graphql-guard/badge.svg)](https://coveralls.io/github/exAspArk/graphql-guard)
5
- [![Code Climate](https://img.shields.io/codeclimate/github/exAspArk/graphql-guard.svg)](https://codeclimate.com/github/exAspArk/graphql-guard)
5
+ [![Code Climate](https://img.shields.io/codeclimate/maintainability/exAspArk/graphql-guard.svg)](https://codeclimate.com/github/exAspArk/graphql-guard/maintainability)
6
6
  [![Downloads](https://img.shields.io/gem/dt/graphql-guard.svg)](https://rubygems.org/gems/graphql-guard)
7
7
  [![Latest Version](https://img.shields.io/gem/v/graphql-guard.svg)](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, !types.String
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
- GraphSchema.execute(query, variables: { user_id: 1 }, context: { current_user: current_user })
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
- <b>guard ->(post, args, ctx) { ctx[:current_ability].can?(:read, post) }</b>
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
- GraphSchema.execute(query, context: { <b>current_ability: Ability.new(current_user)</b> })
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
- <b>guard ->(post, args, ctx) { PostPolicy.new(ctx[:current_user], post).show? }</b>
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
- GraphSchema.execute(query, context: { <b>current_user: current_user</b> })
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
@@ -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{^(test|spec|features)/})
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) }
@@ -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)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  class Guard
5
- VERSION = "1.0.0"
5
+ VERSION = "1.1.0"
6
6
  end
7
7
  end
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.0.0
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: 2017-07-31 00:00:00.000000000 Z
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.5.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