graphql-pundit 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/.codeclimate.yml +16 -0
- data/.rubocop.yml +1 -1
- data/Gemfile +2 -0
- data/README.md +51 -6
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/lib/graphql-pundit.rb +23 -7
- data/lib/graphql-pundit/instrumenter.rb +21 -28
- data/lib/graphql-pundit/instrumenters/authorization.rb +52 -0
- data/lib/graphql-pundit/instrumenters/scope.rb +56 -0
- data/lib/graphql-pundit/version.rb +3 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ce932c3e90158e8692934bac208942e9a10faa9
|
4
|
+
data.tar.gz: 87858e55cab3ec052e5756b66ebb5fa2285e4dc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fd1f0da13f241e13bcf145adadb710f256a36418049b93c56674c1d10dc3cf1d3fc3bf75773eb49fd3b72ff0d1057ec2949d3fd1b5bfebbdb6279641fcbe67a
|
7
|
+
data.tar.gz: 86d4a7a066b99752538caf1e60f554939e514af4a7fe86138ac6d85c5a2a389aef2bb0486f756b90972eb5140e4088854fc682173645b0276ac02f10ed29c077
|
data/.codeclimate.yml
ADDED
data/.rubocop.yml
CHANGED
@@ -503,7 +503,7 @@ Layout/ExtraSpacing:
|
|
503
503
|
Style/FileName:
|
504
504
|
# File names listed in `AllCops:Include` are excluded by default. Add extra
|
505
505
|
# excludes here.
|
506
|
-
Exclude: []
|
506
|
+
Exclude: ['lib/graphql-pundit.rb']
|
507
507
|
# When `true`, requires that each source file should define a class or module
|
508
508
|
# with a name which matches the file name (converted to ... case).
|
509
509
|
# It further expects it to be nested inside modules which match the names
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
[![Gem](https://img.shields.io/gem/v/graphql-pundit.svg)](https://rubygems.org/gems/graphql-pundit)
|
2
|
+
[![Build Status](https://travis-ci.org/ontohub/graphql-pundit.svg?branch=master)](https://travis-ci.org/ontohub/graphql-pundit)
|
3
|
+
[![Coverage Status](https://codecov.io/gh/ontohub/graphql-pundit/branch/master/graph/badge.svg)](https://codecov.io/gh/ontohub/graphql-pundit)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/ontohub/graphql-pundit/badges/gpa.svg)](https://codeclimate.com/github/ontohub/graphql-pundit)
|
5
|
+
[![Dependency Status](https://gemnasium.com/badges/github.com/ontohub/graphql-pundit.svg)](https://gemnasium.com/github.com/ontohub/graphql-pundit)
|
6
|
+
[![GitHub issues](https://img.shields.io/github/issues/ontohub/graphql-pundit.svg?maxAge=2592000)](https://waffle.io/ontohub/ontohub-backend?source=ontohub%2Fgraphql-pundit)
|
7
|
+
|
1
8
|
# GraphQL::Pundit
|
2
9
|
|
3
10
|
|
@@ -7,8 +14,7 @@
|
|
7
14
|
Add this line to your application's Gemfile:
|
8
15
|
|
9
16
|
```ruby
|
10
|
-
gem 'graphql-pundit'
|
11
|
-
branch: 'master'
|
17
|
+
gem 'graphql-pundit'
|
12
18
|
```
|
13
19
|
|
14
20
|
And then execute:
|
@@ -43,23 +49,30 @@ For each field you want to authorize via Pundit, add the following code to the f
|
|
43
49
|
|
44
50
|
```ruby
|
45
51
|
field :email do
|
46
|
-
authorize
|
52
|
+
authorize # will use UserPolicy#email?
|
47
53
|
resolve ...
|
48
54
|
end
|
49
55
|
```
|
50
56
|
|
51
|
-
By default, this will use the Policy for the parent object (the first argument passed to the resolve proc), checking for `:
|
57
|
+
By default, this will use the Policy for the parent object (the first argument passed to the resolve proc), checking for `:email?` for the current user. Sometimes, the field name will differ from the policy method name, in which case you can specify it explicitly:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
field :email do
|
61
|
+
authorize :read_email # will use UserPolicy#read_email?
|
62
|
+
resolve ...
|
63
|
+
end
|
64
|
+
```
|
52
65
|
|
53
66
|
Now, in some cases you'll want to use a different policy, or in case of mutations, the passed object might be `nil`:
|
54
67
|
|
55
68
|
```ruby
|
56
69
|
field :createUser
|
57
|
-
authorize! :create, User
|
70
|
+
authorize! :create, User # or User.new; will use UserPolicy#create?
|
58
71
|
resolve ...
|
59
72
|
end
|
60
73
|
```
|
61
74
|
|
62
|
-
This will use the `:create?` method of the `UserPolicy`.
|
75
|
+
This will use the `:create?` method of the `UserPolicy`. You can also pass in objects instead of a class, if you wish to authorize the user for the specific object.
|
63
76
|
|
64
77
|
You might have also noticed the use of `authorize!` instead of `authorize` in this example. The difference between the two is this:
|
65
78
|
|
@@ -67,6 +80,38 @@ You might have also noticed the use of `authorize!` instead of `authorize` in th
|
|
67
80
|
- `authorize!` will set the field to `nil` and add an error to the response if authorization fails
|
68
81
|
|
69
82
|
You would normally want to use `authorize` for fields in queries, that only e.g. the owner of something can see, while `authorize!` would be usually used in mutations, where you want to communicate to the client that the operation failed because the user is unauthorized.
|
83
|
+
|
84
|
+
If you still need more control over how policies are called, you can pass a lambda to `authorize`:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
field :email
|
88
|
+
authorize ->(obj, args, ctx) { UserPolicy.new(obj, ctx[:me]).private_data?(:email) }
|
89
|
+
resolve ...
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
If the lambda returns a falsy value or raises a `Pundit::UnauthorizedError` the field will resolve to `nil`, if it returns a truthy value, control will be passed to the resolve function. Of course, this can be used with `authorize!` as well.
|
94
|
+
|
95
|
+
### Scopes
|
96
|
+
|
97
|
+
Pundit scopes are supported by using `scope` in the field definition
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
field :posts
|
101
|
+
scope
|
102
|
+
resolve ...
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
By default, this will use the Scope definied in the `PostPolicy`. If you do not want to define a scope inside of the policy, you can also pass a lambda to `scope`. The return value will be passed to `resolve` as first argument.
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
field :posts
|
110
|
+
scope ->(_root, _args, ctx) { Post.where(owner: ctx[:current_user]) }
|
111
|
+
resolve ->(posts, args, ctx) { ... }
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
70
115
|
## Development
|
71
116
|
|
72
117
|
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/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
#!/usr/bin/env ruby
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'graphql/pundit'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +11,5 @@ require "graphql/pundit"
|
|
10
11
|
# require "pry"
|
11
12
|
# Pry.start
|
12
13
|
|
13
|
-
require
|
14
|
+
require 'irb'
|
14
15
|
IRB.start(__FILE__)
|
data/lib/graphql-pundit.rb
CHANGED
@@ -1,17 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'graphql-pundit/instrumenter'
|
2
4
|
require 'graphql-pundit/version'
|
3
5
|
|
4
6
|
require 'graphql'
|
5
7
|
|
8
|
+
# Define `authorize` and `authorize!` helpers
|
6
9
|
module GraphQL
|
7
10
|
def self.assign_authorize(raise_unauthorized)
|
8
|
-
lambda do |defn, query, record = nil|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
)
|
11
|
+
lambda do |defn, query = nil, record = nil|
|
12
|
+
opts = {record: record,
|
13
|
+
query: query || defn.name,
|
14
|
+
raise: raise_unauthorized}
|
15
|
+
if query.respond_to?(:call)
|
16
|
+
opts = {proc: query, raise: raise_unauthorized}
|
17
|
+
end
|
18
|
+
Define::InstanceDefinable::AssignMetadataKey.new(:authorize).
|
19
|
+
call(defn, opts)
|
13
20
|
end
|
14
21
|
end
|
15
|
-
|
16
|
-
|
22
|
+
|
23
|
+
def self.assign_scope
|
24
|
+
lambda do |defn, proc = :infer_scope|
|
25
|
+
Define::InstanceDefinable::AssignMetadataKey.new(:scope).
|
26
|
+
call(defn, proc)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Field.accepts_definitions(authorize: assign_authorize(false),
|
31
|
+
authorize!: assign_authorize(true),
|
32
|
+
scope: assign_scope)
|
17
33
|
end
|
@@ -1,43 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pundit'
|
4
|
+
require 'graphql-pundit/instrumenters/authorization'
|
5
|
+
require 'graphql-pundit/instrumenters/scope'
|
2
6
|
|
3
7
|
module GraphQL
|
4
8
|
module Pundit
|
9
|
+
# Intrumenter combining the authorization and scope instrumenters
|
5
10
|
class Instrumenter
|
6
|
-
attr_reader :current_user
|
11
|
+
attr_reader :current_user,
|
12
|
+
:authorization_instrumenter,
|
13
|
+
:scope_instrumenter
|
7
14
|
|
8
15
|
def initialize(current_user = :current_user)
|
9
16
|
@current_user = current_user
|
17
|
+
@authorization_instrumenter = Instrumenters::Authorization.
|
18
|
+
new(current_user)
|
19
|
+
@scope_instrumenter = Instrumenters::Scope.new(current_user)
|
10
20
|
end
|
11
21
|
|
12
|
-
def instrument(
|
13
|
-
|
14
|
-
|
15
|
-
resolve_proc = resolve_proc(current_user,
|
16
|
-
old_resolve,
|
17
|
-
field.metadata[:authorize])
|
18
|
-
field.redefine do
|
19
|
-
resolve resolve_proc
|
20
|
-
end
|
21
|
-
else
|
22
|
-
field
|
23
|
-
end
|
22
|
+
def instrument(type, field)
|
23
|
+
scoped_field = scope_instrumenter.instrument(type, field)
|
24
|
+
authorization_instrumenter.instrument(type, scoped_field)
|
24
25
|
end
|
25
26
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
old_resolve.call(obj, args, ctx)
|
35
|
-
rescue ::Pundit::NotAuthorizedError
|
36
|
-
if options[:raise]
|
37
|
-
raise GraphQL::ExecutionError,
|
38
|
-
"You're not authorized to do this"
|
39
|
-
end
|
40
|
-
end
|
27
|
+
def authorize(current_user, obj, args, ctx, options)
|
28
|
+
if options[:proc]
|
29
|
+
options[:proc].call(obj, args, ctx)
|
30
|
+
else
|
31
|
+
::Pundit.authorize(ctx[current_user],
|
32
|
+
options[:record] || obj,
|
33
|
+
options[:query].to_s + '?')
|
41
34
|
end
|
42
35
|
end
|
43
36
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pundit'
|
4
|
+
|
5
|
+
module GraphQL
|
6
|
+
module Pundit
|
7
|
+
module Instrumenters
|
8
|
+
# Instrumenter that supplies `authorize`
|
9
|
+
class Authorization
|
10
|
+
attr_reader :current_user
|
11
|
+
|
12
|
+
def initialize(current_user = :current_user)
|
13
|
+
@current_user = current_user
|
14
|
+
end
|
15
|
+
|
16
|
+
def instrument(_type, field)
|
17
|
+
return field unless field.metadata[:authorize]
|
18
|
+
old_resolve = field.resolve_proc
|
19
|
+
resolve_proc = resolve_proc(current_user,
|
20
|
+
old_resolve,
|
21
|
+
field.metadata[:authorize])
|
22
|
+
field.redefine do
|
23
|
+
resolve resolve_proc
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
28
|
+
def resolve_proc(current_user, old_resolve, options)
|
29
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
30
|
+
lambda do |obj, args, ctx|
|
31
|
+
begin
|
32
|
+
result = if options[:proc]
|
33
|
+
options[:proc].call(obj, args, ctx)
|
34
|
+
else
|
35
|
+
query = options[:query].to_s + '?'
|
36
|
+
record = options[:record] || obj
|
37
|
+
::Pundit.authorize(ctx[current_user], record, query)
|
38
|
+
end
|
39
|
+
raise ::Pundit::NotAuthorizedError unless result
|
40
|
+
old_resolve.call(obj, args, ctx)
|
41
|
+
rescue ::Pundit::NotAuthorizedError
|
42
|
+
if options[:raise]
|
43
|
+
raise GraphQL::ExecutionError,
|
44
|
+
"You're not authorized to do this"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pundit'
|
4
|
+
|
5
|
+
module GraphQL
|
6
|
+
module Pundit
|
7
|
+
module Instrumenters
|
8
|
+
# Instrumenter that supplies `scope`
|
9
|
+
class Scope
|
10
|
+
attr_reader :current_user
|
11
|
+
|
12
|
+
def initialize(current_user = :current_user)
|
13
|
+
@current_user = current_user
|
14
|
+
end
|
15
|
+
|
16
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
17
|
+
def instrument(_type, field)
|
18
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
19
|
+
scope = field.metadata[:scope]
|
20
|
+
return field unless scope
|
21
|
+
unless valid_value?(scope)
|
22
|
+
raise ArgumentError, 'Invalid value passed to `scope`'
|
23
|
+
end
|
24
|
+
|
25
|
+
old_resolve = field.resolve_proc
|
26
|
+
|
27
|
+
scope_proc = lambda do |obj, _args, ctx|
|
28
|
+
::Pundit.policy_scope!(ctx[current_user], obj)
|
29
|
+
end
|
30
|
+
scope_proc = scope if proc?(scope)
|
31
|
+
|
32
|
+
field.redefine do
|
33
|
+
resolve(lambda do |obj, args, ctx|
|
34
|
+
new_scope = scope_proc.call(obj, args, ctx)
|
35
|
+
old_resolve.call(new_scope, args, ctx)
|
36
|
+
end)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def valid_value?(value)
|
43
|
+
inferred?(value) || proc?(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def proc?(value)
|
47
|
+
value.respond_to?(:call)
|
48
|
+
end
|
49
|
+
|
50
|
+
def inferred?(value)
|
51
|
+
value == :infer_scope
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-pundit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ontohub Core Developers
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -143,6 +143,7 @@ executables: []
|
|
143
143
|
extensions: []
|
144
144
|
extra_rdoc_files: []
|
145
145
|
files:
|
146
|
+
- ".codeclimate.yml"
|
146
147
|
- ".gitignore"
|
147
148
|
- ".hound.yml"
|
148
149
|
- ".rspec"
|
@@ -161,6 +162,8 @@ files:
|
|
161
162
|
- graphql-pundit.gemspec
|
162
163
|
- lib/graphql-pundit.rb
|
163
164
|
- lib/graphql-pundit/instrumenter.rb
|
165
|
+
- lib/graphql-pundit/instrumenters/authorization.rb
|
166
|
+
- lib/graphql-pundit/instrumenters/scope.rb
|
164
167
|
- lib/graphql-pundit/version.rb
|
165
168
|
homepage: https://github.com/ontohub/graphql-pundit
|
166
169
|
licenses:
|