graphql-guard 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 33482164f7f2f2a17ba43bc1219975ee6de6ea19
4
- data.tar.gz: fade6ad6159072a1f3609cbc4e43b5fba2ce100b
3
+ metadata.gz: edc74c4db940ec20e1daa0907380a2db57c57bd5
4
+ data.tar.gz: 984fc4fb2913d17f4c6a331ac8f628aace746125
5
5
  SHA512:
6
- metadata.gz: e198c43bd8b1a4e9ce8de01db82605f1d937efbbd8f6f509e89ae1363e253ec43a70251584fa6f7ef7a44d7b89a4029322be4b93ad9e43b4d8e04a6f3a26bea1
7
- data.tar.gz: f465007bb574373cb6d220aea2b6361d828055a69bf57e91ed5e12a6f6fb31d64043b7636e15763eaac922926618ab1e01c280456dd91b022094df6d9e300bb7
6
+ metadata.gz: e2ab0b88f3daf2acdfdba60814b62fdbb3d36c34aec3f4441da3ebdc83daff068c1115d0680959c552cdd894ecc0d55c59fa3db6ca9bcd80039493b08d2158c5
7
+ data.tar.gz: d3987791bdd7cf4a494572ab2bedd08a5992cd71b1909a9d84572fe048e5bdf47fbd0796af38e21d12bfeaefcebf6506d362f315147d2dd51e4c648cd0fc0e51
data/.travis.yml CHANGED
@@ -2,4 +2,6 @@ sudo: false
2
2
  language: ruby
3
3
  rvm:
4
4
  - 2.3.4
5
+ env:
6
+ - CI=true
5
7
  before_install: gem install bundler -v 1.15.2
data/CHANGELOG.md CHANGED
@@ -8,10 +8,14 @@ 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/v0.2.0...HEAD)
11
+ #### [Unreleased](https://github.com/exAspArk/graphql-guard/compare/v0.4.0...HEAD)
12
12
 
13
13
  * WIP
14
14
 
15
+ #### [v0.4.0](https://github.com/exAspArk/graphql-guard/compare/v0.3.0...v0.4.0) – 2017-07-25
16
+
17
+ * `Added`: ability to test `guard` lambdas via field.
18
+
15
19
  #### [v0.3.0](https://github.com/exAspArk/graphql-guard/compare/v0.2.0...v0.3.0) – 2017-07-19
16
20
 
17
21
  * `Added`: ability to use custom error handlers.
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ source "https://rubygems.org"
3
3
  git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  gem "pry"
6
+ gem 'coveralls', require: false
6
7
 
7
8
  # Specify your gem's dependencies in graphql-guard.gemspec
8
9
  gemspec
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # graphql-guard
2
2
 
3
3
  [![Build Status](https://travis-ci.org/exAspArk/graphql-guard.svg?branch=master)](https://travis-ci.org/exAspArk/graphql-guard)
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)
6
+ [![Downloads](https://img.shields.io/gem/dt/graphql-guard.svg)](https://rubygems.org/gems/graphql-guard)
7
+ [![Latest Version](https://img.shields.io/gem/v/graphql-guard.svg)](https://rubygems.org/gems/graphql-guard)
4
8
 
5
9
  This tiny gem provides a field-level authorization for [graphql-ruby](https://github.com/rmosolgo/graphql-ruby).
6
10
 
@@ -15,6 +19,7 @@ This tiny gem provides a field-level authorization for [graphql-ruby](https://gi
15
19
  * [CanCanCan](#cancancan)
16
20
  * [Pundit](#pundit)
17
21
  * [Installation](#installation)
22
+ * [Testing](#testing)
18
23
  * [Development](#development)
19
24
  * [Contributing](#contributing)
20
25
  * [License](#license)
@@ -25,28 +30,30 @@ This tiny gem provides a field-level authorization for [graphql-ruby](https://gi
25
30
  Define a GraphQL schema:
26
31
 
27
32
  ```ruby
28
- # define a type
33
+ # Define a type
29
34
  PostType = GraphQL::ObjectType.define do
30
35
  name "Post"
36
+
31
37
  field :id, !types.ID
32
38
  field :title, !types.String
33
39
  end
34
40
 
35
- # define a query
41
+ # Define a query
36
42
  QueryType = GraphQL::ObjectType.define do
37
43
  name "Query"
44
+
38
45
  field :posts, !types[PostType] do
39
46
  argument :user_id, !types.ID
40
- resolve ->(_obj, args, _ctx) { Post.where(user_id: args[:user_id]) }
47
+ resolve ->(_, args, _) { Post.where(user_id: args[:user_id]) }
41
48
  end
42
49
  end
43
50
 
44
- # define a schema
51
+ # Define a schema
45
52
  Schema = GraphQL::Schema.define do
46
53
  query QueryType
47
54
  end
48
55
 
49
- # execute query
56
+ # Execute query
50
57
  GraphSchema.execute(query, variables: { user_id: 1 }, context: { current_user: current_user })
51
58
  ```
52
59
 
@@ -54,67 +61,68 @@ GraphSchema.execute(query, variables: { user_id: 1 }, context: { current_user: c
54
61
 
55
62
  Add `GraphQL::Guard` to your schema:
56
63
 
57
- ```ruby
64
+ <pre>
58
65
  Schema = GraphQL::Schema.define do
59
66
  query QueryType
60
- use GraphQL::Guard.new # <======= ʘ‿ʘ
67
+ <b>use GraphQL::Guard.new</b>
61
68
  end
62
- ```
69
+ </pre>
63
70
 
64
71
  Now you can define `guard` for a field, which will check permissions before resolving the field:
65
72
 
66
- ```ruby
73
+ <pre>
67
74
  QueryType = GraphQL::ObjectType.define do
68
75
  name "Query"
69
- field :posts, !types[PostType] do
76
+
77
+ <b>field :posts</b>, !types[PostType] do
70
78
  argument :user_id, !types.ID
71
- guard ->(_obj, args, ctx) { args[:user_id] == ctx[:current_user].id } # <======= ʘ‿ʘ
79
+ <b>guard ->(obj, args, ctx) {</b> args[:user_id] == ctx[:current_user].id <b>}</b>
72
80
  ...
73
81
  end
74
82
  end
75
- ```
83
+ </pre>
76
84
 
77
- You can also define `guard`, which will be executed for all fields in the type:
85
+ You can also define `guard`, which will be executed for every (`*`) field in the type:
78
86
 
79
- ```ruby
87
+ <pre>
80
88
  PostType = GraphQL::ObjectType.define do
81
89
  name "Post"
82
- guard ->(_post, ctx) { ctx[:current_user].admin? } # <======= ʘ‿ʘ
90
+ <b>guard ->(obj, ctx) {</b> ctx[:current_user].admin? <b>}</b>
83
91
  ...
84
92
  end
85
- ```
93
+ </pre>
86
94
 
87
- If `guard` block returns `false`, then it'll raise a `GraphQL::Guard::NotAuthorizedError` error.
95
+ If `guard` block returns `nil` or `false`, then it'll raise a `GraphQL::Guard::NotAuthorizedError` error.
88
96
 
89
97
  ### Policy object
90
98
 
91
- Alternatively, it's possible to describe all policies by using PORO (Plain Old Ruby Object), which should implement a `guard` method. For example:
99
+ Alternatively, it's possible to extract and describe all policies by using PORO (Plain Old Ruby Object), which should implement a `guard` method. For example:
92
100
 
93
- ```ruby
94
- class GraphqlPolicy
101
+ <pre>
102
+ class <b>GraphqlPolicy</b>
95
103
  RULES = {
96
104
  QueryType => {
97
- posts: ->(_obj, args, ctx) { args[:user_id] == ctx[:current_user].id }
105
+ <b>posts: ->(obj, args, ctx) {</b> args[:user_id] == ctx[:current_user].id <b>}</b>
98
106
  },
99
107
  PostType => {
100
- '*': ->(_post, ctx) { ctx[:current_user].admin? }
108
+ <b>'*': ->(obj, ctx) {</b> ctx[:current_user].admin? <b>}</b>
101
109
  }
102
110
  }
103
111
 
104
- def self.guard(type, field)
112
+ def self.<b>guard(type, field)</b>
105
113
  RULES.dig(type, field)
106
114
  end
107
115
  end
108
- ```
116
+ </pre>
109
117
 
110
118
  Pass this object to `GraphQL::Guard`:
111
119
 
112
- ```ruby
120
+ <pre>
113
121
  Schema = GraphQL::Schema.define do
114
122
  query QueryType
115
- use GraphQL::Guard.new(policy_object: GraphqlPolicy) # <======= ʘ‿ʘ
123
+ use GraphQL::Guard.new(<b>policy_object: GraphqlPolicy</b>)
116
124
  end
117
- ```
125
+ </pre>
118
126
 
119
127
  ## Priority order
120
128
 
@@ -125,12 +133,12 @@ end
125
133
  3. Inline policy on the type.
126
134
  2. Policy from the policy object on the type.
127
135
 
128
- ```ruby
129
- class GraphqlPolicy
136
+ <pre>
137
+ class <b>GraphqlPolicy</b>
130
138
  RULES = {
131
139
  PostType => {
132
- title: ->(_post, ctx) { ctx[:current_user].admin? }, # <======= 2
133
- '*': ->(_post, ctx) { ctx[:current_user].admin? } # <======= 4
140
+ <b>'*': ->(_, ctx) {</b> ctx[:current_user].admin? <b>}</b>, # <=== <b>4</b>
141
+ <b>title: ->(_, _, ctx) {</b> ctx[:current_user].admin? <b>}</b> # <=== <b>2</b>
134
142
  }
135
143
  }
136
144
 
@@ -141,43 +149,43 @@ end
141
149
 
142
150
  PostType = GraphQL::ObjectType.define do
143
151
  name "Post"
144
- guard ->(_post, ctx) { ctx[:current_user].admin? } # <======= 3
145
- field :title, !types.String, guard: ->(_post, _args, ctx) { ctx[:current_user].admin? } # <======= 1
152
+ <b>guard ->(_, ctx) {</b> ctx[:current_user].admin? <b>}</b> # <=== <b>3</b>
153
+ <b>field :title</b>, !types.String, <b>guard: ->(_, _, ctx) {</b> ctx[:current_user].admin? <b>}</b> # <=== <b>1</b>
146
154
  end
147
155
 
148
156
  Schema = GraphQL::Schema.define do
149
157
  query QueryType
150
- use GraphQL::Guard.new(policy_object: GraphqlPolicy)
158
+ use GraphQL::Guard.new(<b>policy_object: GraphqlPolicy</b>)
151
159
  end
152
- ```
160
+ </pre>
153
161
 
154
162
  ## Error handling
155
163
 
156
164
  By default `GraphQL::Guard` raises a `GraphQL::Guard::NotAuthorizedError` exception if access to field is not authorized.
157
165
  You can change this behavior, by passing custom `not_authorized` lambda. For example:
158
166
 
159
- ```ruby
160
- SchemaWithoutExceptions = GraphQL::Schema.define do
167
+ <pre>
168
+ SchemaWithErrors = GraphQL::Schema.define do
161
169
  query QueryType
162
170
  use GraphQL::Guard.new(
163
- # by default it raises an error
164
- # not_authorized: ->(type, field) { raise GraphQL::Guard::NotAuthorizedError.new("#{type}.#{field}") }
171
+ # Returns an error in the response
172
+ <b>not_authorized: ->(type, field) { GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") }</b>
165
173
 
166
- # returns an error in the response
167
- not_authorized: ->(type, field) { GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") }
174
+ # By default it raises an error
175
+ # not_authorized: ->(type, field) { raise GraphQL::Guard::NotAuthorizedError.new("#{type}.#{field}") }
168
176
  )
169
177
  end
170
- ```
178
+ </pre>
171
179
 
172
180
  In this case executing a query will continue, but return `nil` for not authorized field and also an array of `errors`:
173
181
 
174
- ```ruby
175
- SchemaWithoutExceptions.execute("query { posts(user_id: 1) { id title } }")
182
+ <pre>
183
+ SchemaWithErrors.execute("query { <b>posts</b>(user_id: 1) { id title } }")
176
184
  # => {
177
- # "data" => nil,
178
- # "errors" => [{ "messages" => "Not authorized to access Query.posts", "locations": { ... }, "path" => ["posts"] }]
185
+ # "data" => <b>nil</b>,
186
+ # "errors" => [{ "messages" => <b>"Not authorized to access Query.posts"</b>, "locations": { ... }, "path" => [<b>"posts"</b>] }]
179
187
  # }
180
- ```
188
+ </pre>
181
189
 
182
190
  ## Integration
183
191
 
@@ -185,13 +193,13 @@ You can simply reuse your existing policies if you really want. You don't need a
185
193
 
186
194
  ### CanCanCan
187
195
 
188
- ```ruby
189
- # define an ability
190
- class Ability
196
+ <pre>
197
+ # Define an ability
198
+ class <b>Ability</b>
191
199
  include CanCan::Ability
192
200
 
193
201
  def initialize(user)
194
- user ||= User.new # guest user if not logged in
202
+ user ||= User.new
195
203
  if user.admin?
196
204
  can :manage, :all
197
205
  else
@@ -200,46 +208,37 @@ class Ability
200
208
  end
201
209
  end
202
210
 
203
- # use the ability in your guard policy object
204
- class GraphqlPolicy
205
- RULES = {
206
- PostType => {
207
- '*': ->(post, ctx) { ctx[:current_ability].can?(:read, post) },
208
- }
209
- }
210
-
211
- def self.guard(type, field)
212
- RULES.dig(type, field)
213
- end
211
+ # Use the ability in your guard
212
+ PostType = GraphQL::ObjectType.define do
213
+ name "Post"
214
+ <b>guard ->(post, ctx) { ctx[:current_ability].can?(:read, post) }</b>
215
+ ...
214
216
  end
215
217
 
216
- # pass the ability
217
- GraphSchema.execute(query, context: { current_ability: Ability.new(current_user) })
218
- ```
218
+ # Pass the ability
219
+ GraphSchema.execute(query, context: { <b>current_ability: Ability.new(current_user)</b> })
220
+ </pre>
219
221
 
220
222
  ### Pundit
221
223
 
222
- ```ruby
223
- # define a policy
224
- class PostPolicy < ApplicationPolicy
224
+ <pre>
225
+ # Define a policy
226
+ class <b>PostPolicy</b> < ApplicationPolicy
225
227
  def show?
226
228
  user.admin? || record.author_id == user.id
227
229
  end
228
230
  end
229
231
 
230
- # use the policy in your guard policy object
231
- class GraphqlPolicy
232
- RULES = {
233
- PostType => {
234
- '*': ->(post, ctx) { PostPolicy.new(ctx[:current_user], post).show? },
235
- }
236
- }
237
-
238
- def self.guard(type, field)
239
- RULES.dig(type, field)
240
- end
232
+ # Use the ability in your guard
233
+ PostType = GraphQL::ObjectType.define do
234
+ name "Post"
235
+ <b>guard ->(post, ctx) { PostPolicy.new(ctx[:current_user], post).show? }</b>
236
+ ...
241
237
  end
242
- ```
238
+
239
+ # Pass current_user
240
+ GraphSchema.execute(query, context: { <b>current_user: current_user</b> })
241
+ </pre>
243
242
 
244
243
  ## Installation
245
244
 
@@ -257,6 +256,52 @@ Or install it yourself as:
257
256
 
258
257
  $ gem install graphql-guard
259
258
 
259
+ ## Testing
260
+
261
+ It's possible to test fields with `guard` in isolation:
262
+
263
+ <pre>
264
+ # Your type
265
+ QueryType = GraphQL::ObjectType.define do
266
+ name "Query"
267
+ <b>field :posts</b>, !types[PostType], <b>guard ->(obj, args, ctx) {</b> ... <b>}</b>
268
+ end
269
+
270
+ # Your test
271
+ <b>require "graphql/guard/testing"</b>
272
+
273
+ posts = QueryType.<b>field_with_guard('posts')</b>
274
+ result = posts.<b>guard(obj, args, ctx)</b>
275
+ expect(result).to eq(true)
276
+ </pre>
277
+
278
+ If you would like to test your fields with policy objects:
279
+
280
+
281
+ <pre>
282
+ # Your type
283
+ QueryType = GraphQL::ObjectType.define do
284
+ name "Query"
285
+ <b>field :posts</b>, !types[PostType]
286
+ end
287
+
288
+ # Your policy object
289
+ class <b>GraphqlPolicy</b>
290
+ def self.<b>guard</b>(type, field)
291
+ <b>->(_obj, args, ctx) {</b> ... <b>}</b>
292
+ end
293
+ end
294
+
295
+ # Your test
296
+ <b>require "graphql/guard/testing"</b>
297
+
298
+ <b>guard_object</b> = <b>GraphQL::Guard.new(policy_object: PolicyObject::GraphqlPolicy)</b>
299
+
300
+ posts = QueryType.<b>field_with_guard('posts', guard_object)</b>
301
+ result = posts.<b>guard(obj, args, ctx)</b>
302
+ expect(result).to eq(true)
303
+ </pre>
304
+
260
305
  ## Development
261
306
 
262
307
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/lib/graphql/guard.rb CHANGED
@@ -25,8 +25,8 @@ module GraphQL
25
25
  end
26
26
 
27
27
  def instrument(type, field)
28
- field_guard_proc = inline_field_guard(field) || policy_object_guard(type, field.name.to_sym)
29
- type_guard_proc = inline_type_guard(type) || policy_object_guard(type, ANY_FIELD_NAME)
28
+ field_guard_proc = field_guard_proc(type, field)
29
+ type_guard_proc = type_guard_proc(type, field)
30
30
  return field if !field_guard_proc && !type_guard_proc
31
31
 
32
32
  old_resolve_proc = field.resolve_proc
@@ -48,6 +48,14 @@ module GraphQL
48
48
  field.redefine { resolve(new_resolve_proc) }
49
49
  end
50
50
 
51
+ def field_guard_proc(type, field)
52
+ inline_field_guard(field) || policy_object_guard(type, field.name.to_sym)
53
+ end
54
+
55
+ def type_guard_proc(type, field)
56
+ inline_type_guard(type) || policy_object_guard(type, ANY_FIELD_NAME)
57
+ end
58
+
51
59
  private
52
60
 
53
61
  def policy_object_guard(type, field_name)
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Field
5
+ NoGuardError = Class.new(StandardError)
6
+
7
+ def guard(*args)
8
+ raise NoGuardError.new("Get your field by calling: Type.field_with_guard('#{name}')") unless @guard_type
9
+ guard_proc = @guard_object.field_guard_proc(@guard_type, self) || @guard_object.type_guard_proc(@guard_type, self)
10
+ raise NoGuardError.new("Guard lambda does not exist for #{@guard_type}.#{name}") unless guard_proc
11
+
12
+ guard_proc.call(*args)
13
+ end
14
+
15
+ def __guard_object=(guard_object)
16
+ @guard_object = guard_object || GraphQL::Guard.new
17
+ end
18
+
19
+ def __guard_type=(guard_type)
20
+ @guard_type = guard_type
21
+ end
22
+ end
23
+
24
+ class ObjectType
25
+ def field_with_guard(field_name, guard_object = nil)
26
+ field = get_field(field_name)
27
+ return unless field
28
+
29
+ field.clone.tap do |f|
30
+ f.__guard_object = guard_object
31
+ f.__guard_type = self
32
+ end
33
+ end
34
+ end
35
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  class Guard
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.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: 0.3.0
4
+ version: 0.4.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-19 00:00:00.000000000 Z
11
+ date: 2017-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -93,6 +93,7 @@ files:
93
93
  - bin/setup
94
94
  - graphql-guard.gemspec
95
95
  - lib/graphql/guard.rb
96
+ - lib/graphql/guard/testing.rb
96
97
  - lib/graphql/guard/version.rb
97
98
  homepage: https://github.com/exAspArk/graphql-guard
98
99
  licenses: