graphql-guard 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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) }
|