graphql-guard 0.1.0 → 0.2.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 +9 -1
- data/Gemfile +2 -0
- data/README.md +75 -11
- data/lib/graphql/guard.rb +34 -15
- data/lib/graphql/guard/version.rb +3 -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: 5870b2ab41869305af964ae8412f7b7628328272
|
4
|
+
data.tar.gz: 35db74d61a58b98c041a6f31a3fe05f1fd2183e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 984a157c13ca13f6d49b6b2c770f51d8898b573ac58b8bf9c927b7c63b38e0a9cc6a1e731a621546a1fa875aef9b0c3489323aa8f7605b503ed91bfc02fcf279
|
7
|
+
data.tar.gz: 15f06cd5cfe6d2e5fad01258010c10b4a9203fa4427344e5007107decc9583b3b4d215073acf9c1e887cdca405c55f5d3507612359b3caa81152a5820c7a34fe
|
data/CHANGELOG.md
CHANGED
@@ -8,6 +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/
|
11
|
+
#### [Unreleased](https://github.com/exAspArk/graphql-guard/compare/v0.2.0...HEAD)
|
12
12
|
|
13
13
|
* WIP
|
14
|
+
|
15
|
+
#### [v0.2.0](https://github.com/exAspArk/graphql-guard/compare/v0.1.0...v0.2.0)
|
16
|
+
|
17
|
+
* `Added`: support for object policies.
|
18
|
+
|
19
|
+
#### [v0.1.0](https://github.com/exAspArk/graphql-guard/compare/e6d7d0f...v0.1.0) – 2017-07-19
|
20
|
+
|
21
|
+
* `Added`: initial functional version with inline policies.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -19,9 +19,9 @@ end
|
|
19
19
|
# define query
|
20
20
|
QueryType = GraphQL::ObjectType.define do
|
21
21
|
name "Query"
|
22
|
-
field :posts, [PostType] do
|
22
|
+
field :posts, !types[PostType] do
|
23
23
|
argument :user_id, !types.ID
|
24
|
-
resolve ->(_obj, args, _ctx) { Post.
|
24
|
+
resolve ->(_obj, args, _ctx) { Post.where(user_id: args[:user_id]) }
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -38,14 +38,14 @@ GraphSchema.execute(
|
|
38
38
|
)
|
39
39
|
```
|
40
40
|
|
41
|
-
###
|
41
|
+
### Inline policies
|
42
42
|
|
43
43
|
Add `GraphQL::Guard` to your schema:
|
44
44
|
|
45
45
|
```ruby
|
46
46
|
Schema = GraphQL::Schema.define do
|
47
47
|
query QueryType
|
48
|
-
use GraphQL::Guard.new #
|
48
|
+
use GraphQL::Guard.new # <======= ʘ‿ʘ
|
49
49
|
end
|
50
50
|
```
|
51
51
|
|
@@ -54,27 +54,91 @@ Now you can define `guard` for a field, which will check permissions before reso
|
|
54
54
|
```ruby
|
55
55
|
QueryType = GraphQL::ObjectType.define do
|
56
56
|
name "Query"
|
57
|
-
field :posts, [PostType] do
|
57
|
+
field :posts, !types[PostType] do
|
58
58
|
argument :user_id, !types.ID
|
59
|
-
guard ->(_obj, args, ctx) { args[:user_id] == ctx[:current_user].id } #
|
60
|
-
|
59
|
+
guard ->(_obj, args, ctx) { args[:user_id] == ctx[:current_user].id } # <======= ʘ‿ʘ
|
60
|
+
...
|
61
61
|
end
|
62
62
|
end
|
63
63
|
```
|
64
64
|
|
65
|
-
You can also define `guard
|
65
|
+
You can also define `guard`, which will be executed for all fields in the type:
|
66
66
|
|
67
67
|
```ruby
|
68
68
|
PostType = GraphQL::ObjectType.define do
|
69
69
|
name "Post"
|
70
|
-
guard ->(
|
71
|
-
|
72
|
-
field :title, !types.String
|
70
|
+
guard ->(_post, ctx) { ctx[:current_user].admin? } # <======= ʘ‿ʘ
|
71
|
+
...
|
73
72
|
end
|
74
73
|
```
|
75
74
|
|
76
75
|
If `guard` block returns `false`, then it'll raise a `GraphQL::Guard::NotAuthorizedError` error.
|
77
76
|
|
77
|
+
### Policy object
|
78
|
+
|
79
|
+
Alternatively, it's possible to describe all policies by using PORO (Plain Old Ruby Object), which should implement a `guard` method. For example:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
class GraphqlPolicy
|
83
|
+
RULES = {
|
84
|
+
QueryType => {
|
85
|
+
posts: ->(_obj, args, ctx) { args[:user_id] == ctx[:current_user].id }
|
86
|
+
},
|
87
|
+
PostType => {
|
88
|
+
'*': ->(post, ctx) { ctx[:current_user].admin? }
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
def self.guard(type, field)
|
93
|
+
RULES.dig(type, field)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
Use pass this object to `GraphQL::Guard`:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
Schema = GraphQL::Schema.define do
|
102
|
+
query QueryType
|
103
|
+
use GraphQL::Guard.new(policy_object: GraphqlPolicy) # <======= ʘ‿ʘ
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
## Order of priority
|
108
|
+
|
109
|
+
`GraphQL::Guard` will use the policy in the following order of priority:
|
110
|
+
|
111
|
+
1. Inline policy on the field.
|
112
|
+
2. Policy from the policy object on the field.
|
113
|
+
3. Inline policy on the type.
|
114
|
+
2. Policy from the policy object on the type.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
class GraphqlPolicy
|
118
|
+
RULES = {
|
119
|
+
PostType => {
|
120
|
+
title: ->(_post, ctx) { ctx[:current_user].admin? }, # <======= 2
|
121
|
+
'*': ->(_post, ctx) { ctx[:current_user].admin? } # <======= 4
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
def self.guard(type, field)
|
126
|
+
RULES.dig(type, field)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
PostType = GraphQL::ObjectType.define do
|
131
|
+
name "Post"
|
132
|
+
guard ->(_post, ctx) { ctx[:current_user].admin? } # <======= 3
|
133
|
+
field :title, !types.String, guard: ->(_post, _args, ctx) { ctx[:current_user].admin? } # <======= 1
|
134
|
+
end
|
135
|
+
|
136
|
+
Schema = GraphQL::Schema.define do
|
137
|
+
query QueryType
|
138
|
+
use GraphQL::Guard.new(policy_object: GraphqlPolicy)
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
78
142
|
## Installation
|
79
143
|
|
80
144
|
Add this line to your application's Gemfile:
|
data/lib/graphql/guard.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "graphql"
|
2
4
|
require "graphql/guard/version"
|
3
5
|
|
@@ -6,36 +8,53 @@ GraphQL::Field.accepts_definitions(guard: GraphQL::Define.assign_metadata_key(:g
|
|
6
8
|
|
7
9
|
module GraphQL
|
8
10
|
class Guard
|
11
|
+
ANY_FIELD_NAME = :'*'
|
12
|
+
|
9
13
|
NotAuthorizedError = Class.new(StandardError)
|
10
14
|
|
15
|
+
attr_reader :policy_object
|
16
|
+
|
17
|
+
def initialize(policy_object: nil)
|
18
|
+
@policy_object = policy_object
|
19
|
+
end
|
20
|
+
|
11
21
|
def use(schema_definition)
|
12
22
|
schema_definition.instrument(:field, self)
|
13
23
|
end
|
14
24
|
|
15
25
|
def instrument(type, field)
|
16
|
-
|
17
|
-
|
26
|
+
field_guard_proc = inline_field_guard(field) || policy_object_guard(type, field.name.to_sym)
|
27
|
+
type_guard_proc = inline_type_guard(type) || policy_object_guard(type, ANY_FIELD_NAME)
|
28
|
+
return field if !field_guard_proc && !type_guard_proc
|
18
29
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
->(object, arguments, context) do
|
27
|
-
raise NotAuthorizedError.new("#{type}.#{field.name}") unless guard_proc.call(object, context)
|
28
|
-
old_resolve_proc.call(object, arguments, context)
|
30
|
+
old_resolve_proc = field.resolve_proc
|
31
|
+
new_resolve_proc = ->(object, arguments, context) do
|
32
|
+
authorized =
|
33
|
+
if field_guard_proc
|
34
|
+
field_guard_proc.call(object, arguments, context)
|
35
|
+
elsif type_guard_proc
|
36
|
+
type_guard_proc.call(object, context)
|
29
37
|
end
|
30
|
-
|
38
|
+
raise NotAuthorizedError.new("#{type}.#{field.name}") unless authorized
|
39
|
+
|
40
|
+
old_resolve_proc.call(object, arguments, context)
|
41
|
+
end
|
31
42
|
|
32
43
|
field.redefine { resolve(new_resolve_proc) }
|
33
44
|
end
|
34
45
|
|
35
46
|
private
|
36
47
|
|
37
|
-
def
|
38
|
-
|
48
|
+
def policy_object_guard(type, field_name)
|
49
|
+
policy_object && policy_object.guard(type, field_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def inline_field_guard(field)
|
53
|
+
field.metadata[:guard]
|
54
|
+
end
|
55
|
+
|
56
|
+
def inline_type_guard(type)
|
57
|
+
type.metadata[:guard]
|
39
58
|
end
|
40
59
|
end
|
41
60
|
end
|