graphql-guard 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +5 -1
- data/README.md +113 -11
- data/lib/graphql/guard.rb +9 -4
- data/lib/graphql/guard/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33482164f7f2f2a17ba43bc1219975ee6de6ea19
|
4
|
+
data.tar.gz: fade6ad6159072a1f3609cbc4e43b5fba2ce100b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e198c43bd8b1a4e9ce8de01db82605f1d937efbbd8f6f509e89ae1363e253ec43a70251584fa6f7ef7a44d7b89a4029322be4b93ad9e43b4d8e04a6f3a26bea1
|
7
|
+
data.tar.gz: f465007bb574373cb6d220aea2b6361d828055a69bf57e91ed5e12a6f6fb31d64043b7636e15763eaac922926618ab1e01c280456dd91b022094df6d9e300bb7
|
data/CHANGELOG.md
CHANGED
@@ -12,7 +12,11 @@ that you can set version constraints properly.
|
|
12
12
|
|
13
13
|
* WIP
|
14
14
|
|
15
|
-
#### [v0.
|
15
|
+
#### [v0.3.0](https://github.com/exAspArk/graphql-guard/compare/v0.2.0...v0.3.0) – 2017-07-19
|
16
|
+
|
17
|
+
* `Added`: ability to use custom error handlers.
|
18
|
+
|
19
|
+
#### [v0.2.0](https://github.com/exAspArk/graphql-guard/compare/v0.1.0...v0.2.0) – 2017-07-19
|
16
20
|
|
17
21
|
* `Added`: support for object policies.
|
18
22
|
|
data/README.md
CHANGED
@@ -4,19 +4,35 @@
|
|
4
4
|
|
5
5
|
This tiny gem provides a field-level authorization for [graphql-ruby](https://github.com/rmosolgo/graphql-ruby).
|
6
6
|
|
7
|
+
## Contents
|
8
|
+
|
9
|
+
* [Usage](#usage)
|
10
|
+
* [Inline policies](#inline-policies)
|
11
|
+
* [Policy object](#policy-object)
|
12
|
+
* [Priority order](#priority-order)
|
13
|
+
* [Error handling](#error-handling)
|
14
|
+
* [Integration](#integration)
|
15
|
+
* [CanCanCan](#cancancan)
|
16
|
+
* [Pundit](#pundit)
|
17
|
+
* [Installation](#installation)
|
18
|
+
* [Development](#development)
|
19
|
+
* [Contributing](#contributing)
|
20
|
+
* [License](#license)
|
21
|
+
* [Code of Conduct](#code-of-conduct)
|
22
|
+
|
7
23
|
## Usage
|
8
24
|
|
9
25
|
Define a GraphQL schema:
|
10
26
|
|
11
27
|
```ruby
|
12
|
-
# define type
|
28
|
+
# define a type
|
13
29
|
PostType = GraphQL::ObjectType.define do
|
14
30
|
name "Post"
|
15
31
|
field :id, !types.ID
|
16
32
|
field :title, !types.String
|
17
33
|
end
|
18
34
|
|
19
|
-
# define query
|
35
|
+
# define a query
|
20
36
|
QueryType = GraphQL::ObjectType.define do
|
21
37
|
name "Query"
|
22
38
|
field :posts, !types[PostType] do
|
@@ -25,17 +41,13 @@ QueryType = GraphQL::ObjectType.define do
|
|
25
41
|
end
|
26
42
|
end
|
27
43
|
|
28
|
-
# define schema
|
44
|
+
# define a schema
|
29
45
|
Schema = GraphQL::Schema.define do
|
30
46
|
query QueryType
|
31
47
|
end
|
32
48
|
|
33
49
|
# execute query
|
34
|
-
GraphSchema.execute(
|
35
|
-
query,
|
36
|
-
variables: { user_id: 1 },
|
37
|
-
context: { current_user: current_user }
|
38
|
-
)
|
50
|
+
GraphSchema.execute(query, variables: { user_id: 1 }, context: { current_user: current_user })
|
39
51
|
```
|
40
52
|
|
41
53
|
### Inline policies
|
@@ -85,7 +97,7 @@ class GraphqlPolicy
|
|
85
97
|
posts: ->(_obj, args, ctx) { args[:user_id] == ctx[:current_user].id }
|
86
98
|
},
|
87
99
|
PostType => {
|
88
|
-
'*': ->(
|
100
|
+
'*': ->(_post, ctx) { ctx[:current_user].admin? }
|
89
101
|
}
|
90
102
|
}
|
91
103
|
|
@@ -95,7 +107,7 @@ class GraphqlPolicy
|
|
95
107
|
end
|
96
108
|
```
|
97
109
|
|
98
|
-
|
110
|
+
Pass this object to `GraphQL::Guard`:
|
99
111
|
|
100
112
|
```ruby
|
101
113
|
Schema = GraphQL::Schema.define do
|
@@ -104,7 +116,7 @@ Schema = GraphQL::Schema.define do
|
|
104
116
|
end
|
105
117
|
```
|
106
118
|
|
107
|
-
##
|
119
|
+
## Priority order
|
108
120
|
|
109
121
|
`GraphQL::Guard` will use the policy in the following order of priority:
|
110
122
|
|
@@ -139,6 +151,96 @@ Schema = GraphQL::Schema.define do
|
|
139
151
|
end
|
140
152
|
```
|
141
153
|
|
154
|
+
## Error handling
|
155
|
+
|
156
|
+
By default `GraphQL::Guard` raises a `GraphQL::Guard::NotAuthorizedError` exception if access to field is not authorized.
|
157
|
+
You can change this behavior, by passing custom `not_authorized` lambda. For example:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
SchemaWithoutExceptions = GraphQL::Schema.define do
|
161
|
+
query QueryType
|
162
|
+
use GraphQL::Guard.new(
|
163
|
+
# by default it raises an error
|
164
|
+
# not_authorized: ->(type, field) { raise GraphQL::Guard::NotAuthorizedError.new("#{type}.#{field}") }
|
165
|
+
|
166
|
+
# returns an error in the response
|
167
|
+
not_authorized: ->(type, field) { GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") }
|
168
|
+
)
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
In this case executing a query will continue, but return `nil` for not authorized field and also an array of `errors`:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
SchemaWithoutExceptions.execute("query { posts(user_id: 1) { id title } }")
|
176
|
+
# => {
|
177
|
+
# "data" => nil,
|
178
|
+
# "errors" => [{ "messages" => "Not authorized to access Query.posts", "locations": { ... }, "path" => ["posts"] }]
|
179
|
+
# }
|
180
|
+
```
|
181
|
+
|
182
|
+
## Integration
|
183
|
+
|
184
|
+
You can simply reuse your existing policies if you really want. You don't need any monkey patches or magic for it ;)
|
185
|
+
|
186
|
+
### CanCanCan
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
# define an ability
|
190
|
+
class Ability
|
191
|
+
include CanCan::Ability
|
192
|
+
|
193
|
+
def initialize(user)
|
194
|
+
user ||= User.new # guest user if not logged in
|
195
|
+
if user.admin?
|
196
|
+
can :manage, :all
|
197
|
+
else
|
198
|
+
can :read, Post, author_id: user.id
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
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
|
214
|
+
end
|
215
|
+
|
216
|
+
# pass the ability
|
217
|
+
GraphSchema.execute(query, context: { current_ability: Ability.new(current_user) })
|
218
|
+
```
|
219
|
+
|
220
|
+
### Pundit
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
# define a policy
|
224
|
+
class PostPolicy < ApplicationPolicy
|
225
|
+
def show?
|
226
|
+
user.admin? || record.author_id == user.id
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
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
|
241
|
+
end
|
242
|
+
```
|
243
|
+
|
142
244
|
## Installation
|
143
245
|
|
144
246
|
Add this line to your application's Gemfile:
|
data/lib/graphql/guard.rb
CHANGED
@@ -9,13 +9,15 @@ GraphQL::Field.accepts_definitions(guard: GraphQL::Define.assign_metadata_key(:g
|
|
9
9
|
module GraphQL
|
10
10
|
class Guard
|
11
11
|
ANY_FIELD_NAME = :'*'
|
12
|
+
DEFAULT_NOT_AUTHORIZED = ->(type, field) { raise NotAuthorizedError.new("#{type}.#{field}") }
|
12
13
|
|
13
14
|
NotAuthorizedError = Class.new(StandardError)
|
14
15
|
|
15
|
-
attr_reader :policy_object
|
16
|
+
attr_reader :policy_object, :not_authorized
|
16
17
|
|
17
|
-
def initialize(policy_object: nil)
|
18
|
+
def initialize(policy_object: nil, not_authorized: DEFAULT_NOT_AUTHORIZED)
|
18
19
|
@policy_object = policy_object
|
20
|
+
@not_authorized = not_authorized
|
19
21
|
end
|
20
22
|
|
21
23
|
def use(schema_definition)
|
@@ -35,9 +37,12 @@ module GraphQL
|
|
35
37
|
elsif type_guard_proc
|
36
38
|
type_guard_proc.call(object, context)
|
37
39
|
end
|
38
|
-
raise NotAuthorizedError.new("#{type}.#{field.name}") unless authorized
|
39
40
|
|
40
|
-
|
41
|
+
if authorized
|
42
|
+
old_resolve_proc.call(object, arguments, context)
|
43
|
+
else
|
44
|
+
not_authorized.call(type, field.name.to_sym)
|
45
|
+
end
|
41
46
|
end
|
42
47
|
|
43
48
|
field.redefine { resolve(new_resolve_proc) }
|