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 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: