graphql-guard-cody 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1b45c4212878e7872146e00571bafaa0b98ca1fa726feb8f85d1b49cfd8944fd
4
+ data.tar.gz: 4eefc0431a87d4c53b797e5d3701bcb21e70aa98773fd7584adea38a1e9353ca
5
+ SHA512:
6
+ metadata.gz: 886d8750dcfb9c24abb33dc4f2c426653b374f4ea71d4635a78a9b8e7fb12715b834a72a7137fe5d8c191fce4bc2548a6c08bf4db6e5a5f048006263ebc55d33
7
+ data.tar.gz: 4243ca803a13e30c7e32089542b14fce3e9c79913c94ff89d83c26da2dccda922f78ff2b95a5852e185ff0fca0a177e70653e0f17dc6da6eb0f49a2835289e49
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+ .ruby-version
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ sudo: false
2
+ language: ruby
3
+ before_install: gem install bundler -v 2.1.4
4
+ rvm:
5
+ - 2.3.8
6
+ - 2.4.9
7
+ - 2.5.7
8
+ - 2.6.5
9
+ - 2.7.0
10
+ env:
11
+ - CI=true
data/CHANGELOG.md ADDED
@@ -0,0 +1,150 @@
1
+ # Changelog
2
+
3
+ The following are lists of the notable changes included with each release.
4
+ This is intended to help keep people informed about notable changes between
5
+ versions, as well as provide a rough history. Each item is prefixed with
6
+ one of the following labels: `Added`, `Changed`, `Deprecated`,
7
+ `Removed`, `Fixed`, `Security`. We also use [Semantic Versioning](http://semver.org)
8
+ to manage the versions of this gem so
9
+ that you can set version constraints properly.
10
+
11
+ #### [Unreleased](https://github.com/exAspArk/graphql-guard/compare/v2.0.0...HEAD)
12
+
13
+ * WIP
14
+
15
+ #### [v2.0.0](https://github.com/exAspArk/graphql-guard/compare/v1.3.1...v2.0.0) – 2020-04-20
16
+
17
+ * `Added`: support for `graphql` gem version `>= 1.10` with `Interpreter`. [#39](https://github.com/exAspArk/graphql-guard/pull/39)
18
+ * `Removed`: support for previous `graphql` gem versions. [#39](https://github.com/exAspArk/graphql-guard/pull/39)
19
+
20
+ **Breaking changes**:
21
+
22
+ * Requires using `graphql` gem version `>= 1.10.0` with [Interpreter](https://graphql-ruby.org/queries/interpreter.html).
23
+
24
+ Before:
25
+
26
+ ```rb
27
+ class Schema < GraphQL::Schema
28
+ query QueryType
29
+ mutation MutationType
30
+
31
+ use GraphQL::Guard.new
32
+ end
33
+ ```
34
+
35
+ After:
36
+
37
+ ```rb
38
+ class Schema < GraphQL::Schema
39
+ use GraphQL::Execution::Interpreter
40
+ use GraphQL::Analysis::AST
41
+
42
+ query QueryType
43
+ mutation MutationType
44
+
45
+ use GraphQL::Guard.new
46
+ end
47
+ ```
48
+
49
+
50
+ * Use the actual type in the Policy object without `type.metadata[:type_class]`.
51
+
52
+ Before (with `graphql` gem version `>= 1.8` and class-based type definitions):
53
+
54
+ ```rb
55
+ class GraphqlPolicy
56
+ def self.guard(type, field)
57
+ RULES.dig(type.metadata[:type_class], field)
58
+ end
59
+ end
60
+ ```
61
+
62
+ After:
63
+
64
+ ```rb
65
+ class GraphqlPolicy
66
+ def self.guard(type, field)
67
+ RULES.dig(type, field)
68
+ end
69
+ end
70
+ ```
71
+
72
+
73
+ #### [v1.3.1](https://github.com/exAspArk/graphql-guard/compare/v1.3.0...v1.3.1) – 2020-01-22
74
+
75
+ * `Fixed`: compatibility with `graphql` gem version 1.10. [#36](https://github.com/exAspArk/graphql-guard/pull/36)
76
+
77
+ #### [v1.3.0](https://github.com/exAspArk/graphql-guard/compare/v1.2.2...v1.3.0) – 2019-10-24
78
+
79
+ * `Added`: More descriptive default error message for `NotAuthorizedError`. [#32](https://github.com/exAspArk/graphql-guard/pull/32)
80
+
81
+ #### [v1.2.2](https://github.com/exAspArk/graphql-guard/compare/v1.2.1...v1.2.2) – 2019-03-04
82
+
83
+ * `Fixed`: compatibility with Ruby 2.6 and `graphql` gem version 1.7. [#26](https://github.com/exAspArk/graphql-guard/pull/26)
84
+
85
+ #### [v1.2.1](https://github.com/exAspArk/graphql-guard/compare/v1.2.0...v1.2.1) – 2018-10-18
86
+
87
+ * `Fixed`: compatibility with Ruby 2.5 and `graphql` gem version 1.7. [#21](https://github.com/exAspArk/graphql-guard/pull/21)
88
+
89
+ #### [v1.2.0](https://github.com/exAspArk/graphql-guard/compare/v1.1.0...v1.2.0) – 2018-06-29
90
+
91
+ * `Added`: support for `graphql` gem version 1.8. [#17](https://github.com/exAspArk/graphql-guard/pull/17)
92
+
93
+ #### [v1.1.0](https://github.com/exAspArk/graphql-guard/compare/v1.0.0...v1.1.0) – 2018-05-09
94
+
95
+ * `Added`: support to `mask` fields depending on the context.
96
+
97
+ #### [v1.0.0](https://github.com/exAspArk/graphql-guard/compare/v0.4.0...v1.0.0) – 2017-07-31
98
+
99
+ * `Changed`: guards for every `*` field also accepts arguments: `->(object, arguments, context) { ... }`:
100
+
101
+ Before:
102
+
103
+ <pre>
104
+ GraphQL::ObjectType.define do
105
+ name "Post"
106
+ guard ->(obj, ctx) { ... }
107
+ ...
108
+ end
109
+ </pre>
110
+
111
+ After:
112
+
113
+ <pre>
114
+ GraphQL::ObjectType.define do
115
+ name "Post"
116
+ guard ->(obj, <b>args</b>, ctx) { ... }
117
+ ...
118
+ end
119
+ </pre>
120
+
121
+ * `Changed`: `.field_with_guard` from `graphql/guard/testing` module accepts policy object as a second argument:
122
+
123
+ Before:
124
+
125
+ <pre>
126
+ <b>guard_object</b> = GraphQL::Guard.new(policy_object: GraphqlPolicy)
127
+ posts_field = QueryType.field_with_guard('posts', <b>guard_object</b>)
128
+ </pre>
129
+
130
+ After:
131
+
132
+ <pre>
133
+ posts_field = QueryType.field_with_guard('posts', <b>GraphqlPolicy</b>)
134
+ </pre>
135
+
136
+ #### [v0.4.0](https://github.com/exAspArk/graphql-guard/compare/v0.3.0...v0.4.0) – 2017-07-25
137
+
138
+ * `Added`: ability to test `guard` lambdas via field.
139
+
140
+ #### [v0.3.0](https://github.com/exAspArk/graphql-guard/compare/v0.2.0...v0.3.0) – 2017-07-19
141
+
142
+ * `Added`: ability to use custom error handlers.
143
+
144
+ #### [v0.2.0](https://github.com/exAspArk/graphql-guard/compare/v0.1.0...v0.2.0) – 2017-07-19
145
+
146
+ * `Added`: support for object policies.
147
+
148
+ #### [v0.1.0](https://github.com/exAspArk/graphql-guard/compare/e6d7d0f...v0.1.0) – 2017-07-19
149
+
150
+ * `Added`: initial functional version with inline policies.
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at exaspark@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "graphql", "~> 1.10"
4
+
5
+ gem "pry", require: false
6
+ gem "coveralls", require: false
7
+
8
+ # Specify your gem's dependencies in graphql-guard.gemspec
9
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 exAspArk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,393 @@
1
+ # graphql-guard
2
+
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/maintainability/exAspArk/graphql-guard.svg)](https://codeclimate.com/github/exAspArk/graphql-guard/maintainability)
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)
8
+
9
+ This gem provides a field-level authorization for [graphql-ruby](https://github.com/rmosolgo/graphql-ruby).
10
+
11
+ ## Contents
12
+
13
+ * [Usage](#usage)
14
+ * [Inline policies](#inline-policies)
15
+ * [Policy object](#policy-object)
16
+ * [Priority order](#priority-order)
17
+ * [Integration](#integration)
18
+ * [CanCanCan](#cancancan)
19
+ * [Pundit](#pundit)
20
+ * [Error handling](#error-handling)
21
+ * [Schema masking](#schema-masking)
22
+ * [Installation](#installation)
23
+ * [Testing](#testing)
24
+ * [Development](#development)
25
+ * [Contributing](#contributing)
26
+ * [License](#license)
27
+ * [Code of Conduct](#code-of-conduct)
28
+
29
+ ## Usage
30
+
31
+ Define a GraphQL schema:
32
+
33
+ ```ruby
34
+ # Define a type
35
+ class PostType < GraphQL::Schema::Object
36
+ field :id, ID, null: false
37
+ field :title, String, null: true
38
+ end
39
+
40
+ # Define a query
41
+ class QueryType < GraphQL::Schema::Object
42
+ field :posts, [PostType], null: false do
43
+ argument :user_id, ID, required: true
44
+ end
45
+
46
+ def posts(user_id:)
47
+ Post.where(user_id: user_id)
48
+ end
49
+ end
50
+
51
+ # Define a schema
52
+ class Schema < GraphQL::Schema
53
+ use GraphQL::Execution::Interpreter
54
+ use GraphQL::Analysis::AST
55
+ query QueryType
56
+ end
57
+
58
+ # Execute query
59
+ Schema.execute(query, variables: { userId: 1 }, context: { current_user: current_user })
60
+ ```
61
+
62
+ ### Inline policies
63
+
64
+ Add `GraphQL::Guard` to your schema:
65
+
66
+ <pre>
67
+ class Schema < GraphQL::Schema
68
+ use GraphQL::Execution::Interpreter
69
+ use GraphQL::Analysis::AST
70
+ query QueryType
71
+ <b>use GraphQL::Guard.new</b>
72
+ end
73
+ </pre>
74
+
75
+ Now you can define `guard` for a field, which will check permissions before resolving the field:
76
+
77
+ <pre>
78
+ class QueryType < GraphQL::Schema::Object
79
+ <b>field :posts</b>, [PostType], null: false do
80
+ argument :user_id, ID, required: true
81
+ <b>guard ->(obj, args, ctx) {</b> args[:user_id] == ctx[:current_user].id <b>}</b>
82
+ end
83
+ ...
84
+ end
85
+ </pre>
86
+
87
+ You can also define `guard`, which will be executed for every `*` field in the type:
88
+
89
+ <pre>
90
+ class PostType < GraphQL::Schema::Object
91
+ <b>guard ->(obj, args, ctx) {</b> ctx[:current_user].admin? <b>}</b>
92
+ ...
93
+ end
94
+ </pre>
95
+
96
+ If `guard` block returns `nil` or `false`, then it'll raise a `GraphQL::Guard::NotAuthorizedError` error.
97
+
98
+ ### Policy object
99
+
100
+ 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:
101
+
102
+ <pre>
103
+ class <b>GraphqlPolicy</b>
104
+ RULES = {
105
+ QueryType => {
106
+ <b>posts: ->(obj, args, ctx) {</b> args[:user_id] == ctx[:current_user].id <b>}</b>
107
+ },
108
+ PostType => {
109
+ <b>'*': ->(obj, args, ctx) {</b> ctx[:current_user].admin? <b>}</b>
110
+ }
111
+ }
112
+
113
+ def self.<b>guard(type, field)</b>
114
+ RULES.dig(type, field)
115
+ end
116
+ end
117
+ </pre>
118
+
119
+ Pass this object to `GraphQL::Guard`:
120
+
121
+ <pre>
122
+ class Schema < GraphQL::Schema
123
+ use GraphQL::Execution::Interpreter
124
+ use GraphQL::Analysis::AST
125
+ query QueryType
126
+ use GraphQL::Guard.new(<b>policy_object: GraphqlPolicy</b>)
127
+ end
128
+ </pre>
129
+
130
+ 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:
131
+
132
+ <pre>
133
+ def self.guard(type, field)
134
+ <b>type.introspection? ? ->(_obj, _args, _ctx) { true } :</b> RULES.dig(type, field) # or "false" to restrict an access
135
+ end
136
+ </pre>
137
+
138
+ ## Priority order
139
+
140
+ `GraphQL::Guard` will use the policy in the following order of priority:
141
+
142
+ 1. Inline policy on the field.
143
+ 2. Policy from the policy object on the field.
144
+ 3. Inline policy on the type.
145
+ 2. Policy from the policy object on the type.
146
+
147
+ <pre>
148
+ class <b>GraphqlPolicy</b>
149
+ RULES = {
150
+ PostType => {
151
+ <b>'*': ->(obj, args, ctx) {</b> ctx[:current_user].admin? <b>}</b>, # <=== <b>4</b>
152
+ <b>title: ->(obj, args, ctx) {</b> ctx[:current_user].admin? <b>}</b> # <=== <b>2</b>
153
+ }
154
+ }
155
+
156
+ def self.guard(type, field)
157
+ RULES.dig(type, field)
158
+ end
159
+ end
160
+
161
+ class PostType < GraphQL::Schema::Object
162
+ <b>guard ->(obj, args, ctx) {</b> ctx[:current_user].admin? <b>}</b> # <=== <b>3</b>
163
+ field :title, String, null: true, <b>guard: ->(obj, args, ctx) {</b> ctx[:current_user].admin? <b>}</b> # <=== <b>1</b>
164
+ end
165
+
166
+ class Schema < GraphQL::Schema
167
+ use GraphQL::Execution::Interpreter
168
+ use GraphQL::Analysis::AST
169
+ query QueryType
170
+ use GraphQL::Guard.new(<b>policy_object: GraphqlPolicy</b>)
171
+ end
172
+ </pre>
173
+
174
+ ## Integration
175
+
176
+ You can simply reuse your existing policies if you really want. You don't need any monkey patches or magic for it ;)
177
+
178
+ ### CanCanCan
179
+
180
+ <pre>
181
+ # Define an ability
182
+ class <b>Ability</b>
183
+ include CanCan::Ability
184
+
185
+ def initialize(user)
186
+ user ||= User.new
187
+ if user.admin?
188
+ can :manage, :all
189
+ else
190
+ can :read, Post, author_id: user.id
191
+ end
192
+ end
193
+ end
194
+
195
+ # Use the ability in your guard
196
+ class PostType < GraphQL::Schema::Object
197
+ guard ->(post, args, ctx) { <b>ctx[:current_ability].can?(:read, post)</b> }
198
+ ...
199
+ end
200
+
201
+ # Pass the ability
202
+ Schema.execute(query, context: { <b>current_ability: Ability.new(current_user)</b> })
203
+ </pre>
204
+
205
+ ### Pundit
206
+
207
+ <pre>
208
+ # Define a policy
209
+ class <b>PostPolicy</b> < ApplicationPolicy
210
+ def show?
211
+ user.admin? || record.author_id == user.id
212
+ end
213
+ end
214
+
215
+ # Use the ability in your guard
216
+ class PostType < GraphQL::Schema::Object
217
+ guard ->(post, args, ctx) { <b>PostPolicy.new(ctx[:current_user], post).show?</b> }
218
+ ...
219
+ end
220
+
221
+ # Pass current_user
222
+ Schema.execute(query, context: { <b>current_user: current_user</b> })
223
+ </pre>
224
+
225
+ ## Error handling
226
+
227
+ By default `GraphQL::Guard` raises a `GraphQL::Guard::NotAuthorizedError` exception if access to the field is not authorized.
228
+ You can change this behavior, by passing custom `not_authorized` lambda. For example:
229
+
230
+ <pre>
231
+ class SchemaWithErrors < GraphQL::Schema
232
+ use GraphQL::Execution::Interpreter
233
+ use GraphQL::Analysis::AST
234
+ query QueryType
235
+ use GraphQL::Guard.new(
236
+ # By default it raises an error
237
+ # not_authorized: ->(type, field) do
238
+ # raise GraphQL::Guard::NotAuthorizedError.new("#{type}.#{field}")
239
+ # end
240
+
241
+ # Returns an error in the response
242
+ <b>not_authorized: ->(type, field) do
243
+ GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}")
244
+ end</b>
245
+ )
246
+ end
247
+ </pre>
248
+
249
+ In this case executing a query will continue, but return `nil` for not authorized field and also an array of `errors`:
250
+
251
+ <pre>
252
+ SchemaWithErrors.execute("query { <b>posts</b>(user_id: 1) { id title } }")
253
+ # => {
254
+ # "data" => <b>nil</b>,
255
+ # "errors" => [{
256
+ # "messages" => <b>"Not authorized to access Query.posts"</b>,
257
+ # "locations": { "line" => 1, "column" => 9 },
258
+ # "path" => [<b>"posts"</b>]
259
+ # }]
260
+ # }
261
+ </pre>
262
+
263
+ 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`:
264
+
265
+ <pre>
266
+ class <b>GraphqlPolicy</b>
267
+ RULES = {
268
+ PostType => {
269
+ '*': {
270
+ guard: ->(obj, args, ctx) { ... },
271
+ <b>not_authorized:</b> ->(type, field) { GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") }
272
+ }
273
+ title: {
274
+ guard: ->(obj, args, ctx) { ... },
275
+ <b>not_authorized:</b> ->(type, field) { nil } # simply return nil if not authorized, no errors
276
+ }
277
+ }
278
+ }
279
+
280
+ def self.guard(type, field)
281
+ RULES.dig(type, field, :guard)
282
+ end
283
+
284
+ def self.<b>not_authorized_handler</b>(type, field)
285
+ RULES</b>.dig(type, field, <b>:not_authorized</b>) || RULES</b>.dig(type, :'*', <b>:not_authorized</b>)
286
+ end
287
+ end
288
+
289
+ class Schema < GraphQL::Schema
290
+ use GraphQL::Execution::Interpreter
291
+ use GraphQL::Analysis::AST
292
+ query QueryType
293
+ mutation MutationType
294
+
295
+ use GraphQL::Guard.new(
296
+ policy_object: GraphqlPolicy,
297
+ not_authorized: ->(type, field) {
298
+ handler = GraphqlPolicy.<b>not_authorized_handler</b>(type, field)
299
+ handler.call(type, field)
300
+ }
301
+ )
302
+ end
303
+ </pre>
304
+
305
+ ## Schema masking
306
+
307
+ It's possible to hide fields from being introspectable and accessible based on the context. For example:
308
+
309
+ <pre>
310
+ class PostType < GraphQL::Schema::Object
311
+ field :id, ID, null: false
312
+ field :title, String, null: true do
313
+ # The field "title" is accessible only for beta testers
314
+ <b>mask ->(ctx) {</b> ctx[:current_user].beta_tester? <b>}</b>
315
+ end
316
+ end
317
+ </pre>
318
+
319
+ ## Installation
320
+
321
+ Add this line to your application's Gemfile:
322
+
323
+ ```ruby
324
+ gem 'graphql-guard'
325
+ ```
326
+
327
+ And then execute:
328
+
329
+ $ bundle
330
+
331
+ Or install it yourself as:
332
+
333
+ $ gem install graphql-guard
334
+
335
+ ## Testing
336
+
337
+ It's possible to test fields with `guard` in isolation:
338
+
339
+ <pre>
340
+ # Your type
341
+ class QueryType < GraphQL::Schema::Object
342
+ field :posts, [PostType], null: false, <b>guard ->(obj, args, ctx) {</b> ... <b>}</b>
343
+ end
344
+
345
+ # Your test
346
+ <b>require "graphql/guard/testing"</b>
347
+
348
+ posts = QueryType.<b>field_with_guard('posts')</b>
349
+ result = posts.<b>guard(obj, args, ctx)</b>
350
+ expect(result).to eq(true)
351
+ </pre>
352
+
353
+ If you would like to test your fields with policy objects:
354
+
355
+
356
+ <pre>
357
+ # Your type
358
+ class QueryType < GraphQL::Schema::Object
359
+ field :posts, [PostType], null: false
360
+ end
361
+
362
+ # Your policy object
363
+ class <b>GraphqlPolicy</b>
364
+ def self.<b>guard</b>(type, field)
365
+ <b>->(obj, args, ctx) {</b> ... <b>}</b>
366
+ end
367
+ end
368
+
369
+ # Your test
370
+ <b>require "graphql/guard/testing"</b>
371
+
372
+ posts = QueryType.<b>field_with_guard('posts', GraphqlPolicy)</b>
373
+ result = posts.<b>guard(obj, args, ctx)</b>
374
+ expect(result).to eq(true)
375
+ </pre>
376
+
377
+ ## Development
378
+
379
+ 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.
380
+
381
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
382
+
383
+ ## Contributing
384
+
385
+ Bug reports and pull requests are welcome on GitHub at https://github.com/exAspArk/graphql-guard. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
386
+
387
+ ## License
388
+
389
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
390
+
391
+ ## Code of Conduct
392
+
393
+ Everyone interacting in the Graphql::Guard project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/exAspArk/graphql-guard/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "graphql/guard"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "graphql/guard/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "graphql-guard-cody"
8
+ spec.version = GraphQL::Guard::VERSION
9
+ spec.authors = ["exAspArk"]
10
+ spec.email = ["exaspark@gmail.com"]
11
+
12
+ spec.summary = %q{Simple authorization gem for graphql-ruby}
13
+ spec.description = %q{Simple authorization gem for graphql-ruby}
14
+ spec.homepage = "https://github.com/exAspArk/graphql-guard"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(spec|images)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.required_ruby_version = '>= 2.1.0' # keyword args
25
+
26
+ spec.add_runtime_dependency "graphql_cody", ">= 1.10.0", "< 2"
27
+
28
+ spec.add_development_dependency "bundler", "~> 2.1"
29
+ spec.add_development_dependency "rake", "~> 13.0"
30
+ spec.add_development_dependency "rspec", "~> 3.0"
31
+ end
@@ -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_instance
9
+
10
+ guard_proc = @__guard_instance.find_guard_proc(@__guard_type, self)
11
+ raise NoGuardError.new("Guard lambda does not exist for #{@__guard_type}.#{name}") unless guard_proc
12
+
13
+ guard_proc.call(*args)
14
+ end
15
+
16
+ def __set_guard_instance(policy_object, guard_type)
17
+ @__policy_object = policy_object
18
+ @__guard_type = guard_type
19
+ @__guard_instance = GraphQL::Guard.new(policy_object: policy_object)
20
+ end
21
+ end
22
+
23
+ class Schema
24
+ class Object
25
+ def self.field_with_guard(field_name, policy_object = nil)
26
+ field = fields[field_name]
27
+ return unless field
28
+
29
+ field.to_graphql.clone.tap do |f|
30
+ f.__set_guard_instance(policy_object, self.to_graphql)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Guard
5
+ VERSION = "2.0.0"
6
+ end
7
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "graphql"
4
+ require "graphql/guard/version"
5
+
6
+ module GraphQL
7
+ class Guard
8
+ NotAuthorizedError = Class.new(StandardError)
9
+
10
+ ANY_FIELD_NAME = :'*'
11
+
12
+ DEFAULT_NOT_AUTHORIZED = ->(type, field) do
13
+ raise NotAuthorizedError.new("Not authorized to access: #{type}.#{field}")
14
+ end
15
+
16
+ MASKING_FILTER = ->(schema_member, ctx) do
17
+ mask = schema_member.graphql_definition.metadata[:mask]
18
+ mask ? mask.call(ctx) : true
19
+ end
20
+
21
+ attr_reader :policy_object, :not_authorized
22
+
23
+ def initialize(policy_object: nil, not_authorized: DEFAULT_NOT_AUTHORIZED)
24
+ @policy_object = policy_object
25
+ @not_authorized = not_authorized
26
+ end
27
+
28
+ def use(schema_definition)
29
+ if schema_definition.interpreter?
30
+ schema_definition.tracer(self)
31
+ else
32
+ raise "Please use the graphql gem version >= 1.10 with GraphQL::Execution::Interpreter"
33
+ end
34
+
35
+ add_schema_masking!(schema_definition)
36
+ end
37
+
38
+ def trace(event, trace_data)
39
+ if event == 'execute_field'
40
+ ensure_guarded(trace_data) { yield }
41
+ else
42
+ yield
43
+ end
44
+ end
45
+
46
+ def find_guard_proc(type, field)
47
+ return unless type.respond_to?(:type_class)
48
+
49
+ inline_guard(field) ||
50
+ policy_object_guard(type.type_class, field.name.to_sym) ||
51
+ inline_guard(type) ||
52
+ policy_object_guard(type.type_class, ANY_FIELD_NAME)
53
+ end
54
+
55
+ private
56
+
57
+ def add_schema_masking!(schema_definition)
58
+ schema_definition.class_eval do
59
+ def self.default_filter
60
+ GraphQL::Filter.new(except: default_mask).merge(only: MASKING_FILTER)
61
+ end
62
+ end
63
+ end
64
+
65
+ def ensure_guarded(trace_data)
66
+ field = trace_data[:field]
67
+ guard_proc = find_guard_proc(field.owner, field)
68
+ return yield unless guard_proc
69
+
70
+ if guard_proc.call(trace_data[:object], args(trace_data), trace_data[:query].context)
71
+ yield
72
+ else
73
+ not_authorized.call(field.owner.graphql_definition, field.name.to_sym)
74
+ end
75
+ end
76
+
77
+ def args(trace_data)
78
+ if trace_data[:arguments].key?(:input) && !trace_data[:arguments][:input].is_a?(Hash)
79
+ return trace_data[:arguments][:input] # Relay mutation input
80
+ end
81
+
82
+ trace_data[:arguments]
83
+ end
84
+
85
+ def policy_object_guard(type, field_name)
86
+ @policy_object && @policy_object.guard(type, field_name)
87
+ end
88
+
89
+ def inline_guard(type_or_field)
90
+ type_or_field.graphql_definition.metadata[:guard]
91
+ end
92
+ end
93
+ end
94
+
95
+ GraphQL::ObjectType.accepts_definitions(guard: GraphQL::Define.assign_metadata_key(:guard))
96
+ GraphQL::Field.accepts_definitions(guard: GraphQL::Define.assign_metadata_key(:guard))
97
+ GraphQL::Field.accepts_definitions(mask: GraphQL::Define.assign_metadata_key(:mask))
98
+ GraphQL::Schema::Object.accepts_definition(:guard)
99
+ GraphQL::Schema::Field.accepts_definition(:guard)
100
+ GraphQL::Schema::Field.accepts_definition(:mask)
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphql-guard-cody
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - exAspArk
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-04-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: graphql_cody
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.10.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.10.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: bundler
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.1'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.1'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ description: Simple authorization gem for graphql-ruby
76
+ email:
77
+ - exaspark@gmail.com
78
+ executables: []
79
+ extensions: []
80
+ extra_rdoc_files: []
81
+ files:
82
+ - ".gitignore"
83
+ - ".rspec"
84
+ - ".travis.yml"
85
+ - CHANGELOG.md
86
+ - CODE_OF_CONDUCT.md
87
+ - Gemfile
88
+ - LICENSE.txt
89
+ - README.md
90
+ - Rakefile
91
+ - bin/console
92
+ - bin/setup
93
+ - graphql-guard.gemspec
94
+ - lib/graphql/guard.rb
95
+ - lib/graphql/guard/testing.rb
96
+ - lib/graphql/guard/version.rb
97
+ homepage: https://github.com/exAspArk/graphql-guard
98
+ licenses:
99
+ - MIT
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 2.1.0
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubygems_version: 3.2.22
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: Simple authorization gem for graphql-ruby
120
+ test_files: []