graphql-pundit 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c2b929e8f4c25ece4d0afbaa33cde63dfc066a4a
4
- data.tar.gz: 9cd2e4ecb125ffa2c23f8ed087e1fd3f4a673f67
3
+ metadata.gz: 6ce932c3e90158e8692934bac208942e9a10faa9
4
+ data.tar.gz: 87858e55cab3ec052e5756b66ebb5fa2285e4dc8
5
5
  SHA512:
6
- metadata.gz: 4e8f9deb8f6a921cbfaa30b600e1dee9ffa2de10be06940536ad43a41ce26d4cba7b416acc4fc1bfe78974c3a2b6853b0193cbbeeb8629050aae7c13147e3b11
7
- data.tar.gz: 0467ea4815289e93d65bf9c05bf0e4e3071246e57d4deadfff8e413dc296eccffcf6428622a36a4896ba50deff9cfda01f9ea282cbb92c7ce5178dc05d8ab748
6
+ metadata.gz: 5fd1f0da13f241e13bcf145adadb710f256a36418049b93c56674c1d10dc3cf1d3fc3bf75773eb49fd3b72ff0d1057ec2949d3fd1b5bfebbdb6279641fcbe67a
7
+ data.tar.gz: 86d4a7a066b99752538caf1e60f554939e514af4a7fe86138ac6d85c5a2a389aef2bb0486f756b90972eb5140e4088854fc682173645b0276ac02f10ed29c077
@@ -0,0 +1,16 @@
1
+ ---
2
+ engines:
3
+ duplication:
4
+ enabled: true
5
+ config:
6
+ languages:
7
+ - ruby
8
+ fixme:
9
+ enabled: false
10
+ rubocop:
11
+ enabled: false
12
+ ratings:
13
+ paths:
14
+ - "**.rb"
15
+ exclude_paths:
16
+ - spec/
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in graphql-pundit.gemspec
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', github: 'ontohub/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 :read_email
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 `:read_email?` for the current user.
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
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
1
2
  #!/usr/bin/env ruby
2
3
 
3
- require "bundler/setup"
4
- require "graphql/pundit"
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 "irb"
14
+ require 'irb'
14
15
  IRB.start(__FILE__)
@@ -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
- GraphQL::Define::InstanceDefinable::AssignMetadataKey.new(:authorize).call(
10
- defn,
11
- record: record, query: query, raise: raise_unauthorized
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
- GraphQL::Field.accepts_definitions authorize: assign_authorize(false)
16
- GraphQL::Field.accepts_definitions authorize!: assign_authorize(true)
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(_type, field)
13
- if field.metadata[:authorize]
14
- old_resolve = field.resolve_proc
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 resolve_proc(current_user, old_resolve, options)
27
- lambda do |obj, args, ctx|
28
- query = options[:query].to_s + '?'
29
- record = options[:record] || obj
30
- begin
31
- unless ::Pundit.authorize(ctx[current_user], record, query)
32
- raise ::Pundit::NotAuthorizedError
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
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GraphQL
2
4
  module Pundit
3
- VERSION = '0.1.0'.freeze
5
+ VERSION = '0.2.0'
4
6
  end
5
7
  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.1.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-08-07 00:00:00.000000000 Z
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: