jsonapi-authorization 0.4.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: af758844e145dc016dca6a62a4ff24aa6875dec6
4
+ data.tar.gz: c2db139f822d5715e89eb7d4765155df6f6fbb04
5
+ SHA512:
6
+ metadata.gz: af34e3b306341ea6f0871f7bb56013183aa555fe7e6bb3a4d67a21ae2b6d1f3851b6d852fb5c6ae09671dba5db06f713b11ecf912cb168c0367be8f906ac1720
7
+ data.tar.gz: 40e7c48af85cf62f14451836c0fa2b14da5710e29beb475b625dd8df42a347b5413a49c91db0750682c8beecb1aabf767ae8d6cb28db9ec3ade26dd66165112d
data/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ # editorconfig.org
2
+ root = true
3
+
4
+ [*]
5
+ indent_style = space
6
+ indent_size = 2
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.orig
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,88 @@
1
+ Metrics/LineLength:
2
+ Enabled: true
3
+ Max: 100
4
+
5
+ Style/MultilineOperationIndentation:
6
+ EnforcedStyle: indented
7
+
8
+ Metrics/ClassLength:
9
+ Enabled: false
10
+
11
+ Style/Documentation:
12
+ Enabled: false
13
+
14
+ Style/ExtraSpacing:
15
+ AllowForAlignment: true
16
+
17
+ Metrics/MethodLength:
18
+ Enabled: false
19
+
20
+ Style/Next:
21
+ Enabled: false
22
+
23
+ Metrics/AbcSize:
24
+ Enabled: false
25
+
26
+ Style/DoubleNegation:
27
+ Enabled: false
28
+
29
+ Style/SignalException:
30
+ Enabled: false
31
+
32
+ Style/StringLiterals:
33
+ Enabled: false
34
+
35
+ Style/SpaceInsideHashLiteralBraces:
36
+ Enabled: false
37
+ EnforcedStyle: space
38
+
39
+ Style/IndentHash:
40
+ Enabled: false
41
+
42
+ Style/ClassAndModuleChildren:
43
+ Enabled: false
44
+
45
+ Style/PercentLiteralDelimiters:
46
+ Enabled: false
47
+
48
+ Style/BlockDelimiters:
49
+ Enabled: false
50
+
51
+ Style/GuardClause:
52
+ Enabled: false
53
+
54
+ Style/ClosingParenthesisIndentation:
55
+ Enabled: false
56
+
57
+ Style/IfUnlessModifier:
58
+ Enabled: false
59
+
60
+ Style/NumericLiterals:
61
+ Enabled: false
62
+
63
+ Style/AsciiComments:
64
+ Enabled: false
65
+
66
+ Style/StructInheritance:
67
+ Enabled: false
68
+
69
+ Style/MultilineBlockChain:
70
+ Enabled: false
71
+
72
+ Metrics/ParameterLists:
73
+ Enabled: false
74
+
75
+ Style/WordArray:
76
+ MinSize: 2
77
+
78
+ Metrics/ModuleLength:
79
+ Enabled: false
80
+
81
+ Style/SingleLineBlockParams:
82
+ Methods:
83
+ - reduce:
84
+ - acc
85
+ - obj
86
+ - inject:
87
+ - acc
88
+ - obj
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.2
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ cache: bundler
3
+ sudo: false
4
+ env:
5
+ - "RAILS_VERSION=4.1.0"
6
+ - "RAILS_VERSION=4.2.0"
7
+ rvm:
8
+ - 2.1.2
9
+ before_install: gem install bundler -v 1.11.2
10
+ notifications:
11
+ email: false
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'sqlite3', '1.3.10'
6
+
7
+ version = ENV['RAILS_VERSION'] || 'default'
8
+
9
+ case version
10
+ when 'master'
11
+ gem 'rails', git: 'https://github.com/rails/rails.git'
12
+ gem 'arel', git: 'https://github.com/rails/arel.git'
13
+ when 'default'
14
+ gem 'rails', '>= 4.2'
15
+ else
16
+ gem 'rails', "~> #{version}"
17
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2016 Vesa Laakso, Emil Sågfors
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # JSONAPI::Authorization
2
+
3
+ [![Build Status](https://travis-ci.org/venuu/jsonapi-authorization.svg?branch=master)](https://travis-ci.org/venuu/jsonapi-authorization) [![Gem Version](https://badge.fury.io/rb/jsonapi-authorization.png)](http://badge.fury.io/rb/jsonapi-authorization)
4
+
5
+ `JSONAPI::Authorization` adds authorization to the [jsonapi-resources][jr] (JR) gem using [Pundit][pundit].
6
+
7
+ ***PLEASE NOTE:*** This gem currently handles only a subset of operations available in JR. This gem is still considered to be ***alpha quality*** and therefore you shouldn't rely on it on production (yet).
8
+
9
+ [jr]: https://github.com/cerebris/jsonapi-resources "A resource-focused Rails library for developing JSON API compliant servers."
10
+ [pundit]: https://github.com/elabs/pundit "Minimal authorization through OO design and pure Ruby classes"
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'jsonapi-authorization'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install jsonapi-authorization
27
+
28
+ ## Usage
29
+
30
+ Make sure you have a Pundit policy specified for every backing model that your JR resources use. Then hook this gem up to your application like so:
31
+
32
+ ```ruby
33
+ JSONAPI.configure do |config|
34
+ config.operations_processor = '::JSONAPI::Authorization::Pundit'
35
+ end
36
+ ```
37
+
38
+ Make all your JR controllers specify the following in the `context`:
39
+
40
+ ```ruby
41
+ class BaseResourceController < ActionController::Base
42
+ include JSONAPI::ActsAsResourceController
43
+
44
+ private
45
+
46
+ def context
47
+ {user: current_user, action: action_name}
48
+ end
49
+ end
50
+ ```
51
+
52
+ Have your JR resources include the `JSONAPI::Authorization::PunditScopedResource` module.
53
+
54
+ ```ruby
55
+ class BaseResource < JSONAPI::Resource
56
+ include JSONAPI::Authorization::PunditScopedResource
57
+ abstract
58
+ end
59
+ ```
60
+
61
+ If you want to send a custom response for unauthorized requests, add a `rescue_from` hook to your `BaseResourceController` and whitelist `Pundit::NotAuthorizedError` in your JR configuration.
62
+
63
+ ## Known bugs
64
+
65
+ There is a bug affecting `jsonapi-resources` error whitelisting, see https://github.com/cerebris/jsonapi-resources/pull/573. To make your whitelisting and `rescue_from` to work properly, here is a potential workaround:
66
+
67
+ ```ruby
68
+ JSONAPI.configure do |config|
69
+ config.exception_class_whitelist = [Pundit::NotAuthorizedError]
70
+ end
71
+ ```
72
+
73
+ ```ruby
74
+ class BaseResourceController < ActionController::Base
75
+ rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
76
+
77
+ private
78
+
79
+ # https://github.com/cerebris/jsonapi-resources/pull/573
80
+ def handle_exceptions(e)
81
+ if JSONAPI.configuration.exception_class_whitelist.any? { |k| e.class.ancestors.include?(k) }
82
+ raise e
83
+ else
84
+ super
85
+ end
86
+ end
87
+
88
+ def user_not_authorized
89
+ head :forbidden
90
+ end
91
+ end
92
+ ```
93
+
94
+ ## Configuration
95
+
96
+ You can use a custom authorizer class by specifying a configure block in an initializer file. If using a custom authorizer class, be sure to require them at the top of the initializer before usage.
97
+
98
+ ```ruby
99
+ JSONAPI::Authorization.configure do |config|
100
+ config.authorizer = MyCustomAuthorizer
101
+ end
102
+ ```
103
+
104
+ ## Development
105
+
106
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
107
+
108
+ 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).
109
+
110
+ ## Credits
111
+
112
+ Originally based on discussion and code samples by [@barelyknown](https://github.com/barelyknown) and others in [cerebris/jsonapi-resources#16](https://github.com/cerebris/jsonapi-resources/issues/16).
113
+
114
+ ## Contributing
115
+
116
+ Bug reports and pull requests are welcome on GitHub at https://github.com/venuu/jsonapi-authorization.
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 "jsonapi/authorization"
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
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 'jsonapi/authorization/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jsonapi-authorization"
8
+ spec.version = JSONAPI::Authorization::VERSION
9
+ spec.authors = ["Vesa Laakso", "Emil Sågfors"]
10
+ spec.email = ["laakso.vesa@gmail.com", "emil.sagfors@iki.fi"]
11
+ spec.license = "MIT"
12
+
13
+ spec.summary = "Generic authorization for jsonapi-resources gem"
14
+ spec.description = "Adds generic authorization to the jsonapi-resources gem using Pundit."
15
+ spec.homepage = "https://github.com/venuu/jsonapi-authorization"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "jsonapi-resources", "0.7.0"
21
+ spec.add_dependency "pundit", "~> 1.0"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.11"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.0"
26
+ spec.add_development_dependency "rspec-rails", "~> 3.0"
27
+ spec.add_development_dependency "pry", "~> 0.10"
28
+ spec.add_development_dependency "pry-byebug", "~> 1.3"
29
+ spec.add_development_dependency "pry-doc", "~> 0.6"
30
+ spec.add_development_dependency "pry-rails", "~> 0.3.4"
31
+ end
@@ -0,0 +1 @@
1
+ require 'jsonapi/authorization'
@@ -0,0 +1,12 @@
1
+ require "jsonapi-resources"
2
+ require "jsonapi/authorization/configuration"
3
+ require "jsonapi/authorization/default_pundit_authorizer"
4
+ require "jsonapi/authorization/pundit_operations_processor"
5
+ require "jsonapi/authorization/pundit_scoped_resource"
6
+ require "jsonapi/authorization/version"
7
+
8
+ module JSONAPI
9
+ module Authorization
10
+ # Your code goes here...
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ require 'jsonapi/authorization/default_pundit_authorizer'
2
+
3
+ module JSONAPI
4
+ module Authorization
5
+ class Configuration
6
+ attr_accessor :authorizer
7
+
8
+ def initialize
9
+ self.authorizer = ::JSONAPI::Authorization::DefaultPunditAuthorizer
10
+ end
11
+ end
12
+
13
+ class << self
14
+ attr_accessor :configuration
15
+ end
16
+
17
+ @configuration ||= Configuration.new
18
+
19
+ def self.configure
20
+ yield(@configuration)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,195 @@
1
+ module JSONAPI
2
+ module Authorization
3
+ # An authorizer is a class responsible for linking JSONAPI operations to
4
+ # your choice of authorization mechanism.
5
+ #
6
+ # This class uses Pundit for authorization. It does not yet support all
7
+ # the available operations — you can use your own authorizer class instead
8
+ # if you have different needs. See the README.md for configuration
9
+ # information.
10
+ #
11
+ # Fetching records is the concern of +PunditScopedResource+ which in turn
12
+ # affects which records end up being passed here.
13
+ class DefaultPunditAuthorizer
14
+ attr_reader :user
15
+
16
+ # Creates a new DefaultPunditAuthorizer instance
17
+ #
18
+ # ==== Parameters
19
+ #
20
+ # * +context+ - The context passed down from the controller layer
21
+ def initialize(context)
22
+ @user = context[:user]
23
+ end
24
+
25
+ # <tt>GET /resources</tt>
26
+ #
27
+ # ==== Parameters
28
+ #
29
+ # * +source_class+ - The source class (e.g. +Article+ for +ArticleResource+)
30
+ def find(source_class)
31
+ ::Pundit.authorize(user, source_class, 'index?')
32
+ end
33
+
34
+ # <tt>GET /resources/:id</tt>
35
+ #
36
+ # ==== Parameters
37
+ #
38
+ # * +source_record+ - The record to show
39
+ def show(source_record)
40
+ ::Pundit.authorize(user, source_record, 'show?')
41
+ end
42
+
43
+ # <tt>GET /resources/:id/relationships/other-resources</tt>
44
+ # <tt>GET /resources/:id/relationships/another-resource</tt>
45
+ #
46
+ # A query for a +has_one+ or a +has_many+ association
47
+ #
48
+ # ==== Parameters
49
+ #
50
+ # * +source_record+ - The record whose relationship is queried
51
+ # * +related_record+ - The associated +has_one+ record to show or +nil+
52
+ # if the associated record was not found. For a +has_many+ association,
53
+ # this will always be +nil+
54
+ def show_relationship(source_record, related_record)
55
+ ::Pundit.authorize(user, source_record, 'show?')
56
+ ::Pundit.authorize(user, related_record, 'show?') unless related_record.nil?
57
+ end
58
+
59
+ # <tt>GET /resources/:id/another-resource</tt>
60
+ #
61
+ # A query for a record through a +has_one+ association
62
+ #
63
+ # ==== Parameters
64
+ #
65
+ # * +source_record+ - The record whose relationship is queried
66
+ # * +related_record+ - The associated record to show or +nil+ if the
67
+ # associated record was not found
68
+ def show_related_resource(source_record, related_record)
69
+ ::Pundit.authorize(user, source_record, 'show?')
70
+ ::Pundit.authorize(user, related_record, 'show?') unless related_record.nil?
71
+ end
72
+
73
+ # <tt>GET /resources/:id/other-resources</tt>
74
+ #
75
+ # A query for records through a +has_many+ association
76
+ #
77
+ # ==== Parameters
78
+ #
79
+ # * +source_record+ - The record whose relationship is queried
80
+ def show_related_resources(source_record)
81
+ ::Pundit.authorize(user, source_record, 'show?')
82
+ end
83
+
84
+ # <tt>PATCH /resources/:id</tt>
85
+ #
86
+ # ==== Parameters
87
+ #
88
+ # * +source_record+ - The record to be modified
89
+ # * +new_related_records+ - An array of records to be associated to the
90
+ # +source_record+. This will contain the records specified in the
91
+ # "relationships" key in the request
92
+ #--
93
+ # TODO: Should probably take old records as well
94
+ def replace_fields(source_record, new_related_records)
95
+ ::Pundit.authorize(user, source_record, 'update?')
96
+
97
+ new_related_records.each do |record|
98
+ ::Pundit.authorize(user, record, 'update?')
99
+ end
100
+ end
101
+
102
+ # <tt>POST /resources</tt>
103
+ #
104
+ # ==== Parameters
105
+ #
106
+ # * +source_class+ - The class of the record to be created
107
+ # * +related_records+ - An array of records to be associated to the new
108
+ # record. This will contain the records specified in the
109
+ # "relationships" key in the request
110
+ def create_resource(source_class, related_records)
111
+ ::Pundit.authorize(user, source_class, 'create?')
112
+
113
+ related_records.each do |record|
114
+ ::Pundit.authorize(user, record, 'update?')
115
+ end
116
+ end
117
+
118
+ # <tt>DELETE /resources/:id</tt>
119
+ #
120
+ # ==== Parameters
121
+ #
122
+ # * +source_record+ - The record to be removed
123
+ def remove_resource(source_record)
124
+ ::Pundit.authorize(user, source_record, 'destroy?')
125
+ end
126
+
127
+ # <tt>PATCH /resources/:id/relationships/another-resource</tt>
128
+ #
129
+ # A replace request for a +has_one+ association
130
+ #
131
+ # ==== Parameters
132
+ #
133
+ # * +source_record+ - The record whose relationship is modified
134
+ # * +old_related_record+ - The current associated record
135
+ # * +new_related_record+ - The new record replacing the +old_record+
136
+ # association, or +nil+ if the association is to be cleared
137
+ def replace_to_one_relationship(source_record, old_related_record, new_related_record)
138
+ raise NotImplementedError
139
+ end
140
+
141
+ # <tt>POST /resources/:id/relationships/other-resources</tt>
142
+ #
143
+ # A request for adding to a +has_many+ association
144
+ #
145
+ # ==== Parameters
146
+ #
147
+ # * +source_record+ - The record whose relationship is modified
148
+ # * +new_related_records+ - The new records to be added to the association
149
+ def create_to_many_relationship(source_record, new_related_records)
150
+ raise NotImplementedError
151
+ end
152
+
153
+ # <tt>PATCH /resources/:id/relationships/other-resources</tt>
154
+ #
155
+ # A replace request for a +has_many+ association
156
+ #
157
+ # ==== Parameters
158
+ #
159
+ # * +source_record+ - The record whose relationship is modified
160
+ # * +new_related_records+ - The new records replacing the entire +has_many+
161
+ # association
162
+ #--
163
+ # TODO: Should probably take old records as well
164
+ def replace_to_many_relationship(source_record, new_related_records)
165
+ raise NotImplementedError
166
+ end
167
+
168
+ # <tt>DELETE /resources/:id/relationships/other-resources</tt>
169
+ #
170
+ # A request to deassociate elements of a +has_many+ association
171
+ #
172
+ # NOTE: this is called once per related record, not all at once
173
+ #
174
+ # ==== Parameters
175
+ #
176
+ # * +source_record+ - The record whose relationship is modified
177
+ # * +related_record+ - The record which will be deassociatied from +source_record+
178
+ def remove_to_many_relationship(source_record, related_record)
179
+ raise NotImplementedError
180
+ end
181
+
182
+ # <tt>DELETE /resources/:id/relationships/another-resource</tt>
183
+ #
184
+ # A request to deassociate a +has_one+ association
185
+ #
186
+ # ==== Parameters
187
+ #
188
+ # * +source_record+ - The record whose relationship is modified
189
+ # * +related_record+ - The record which will be deassociatied from +source_record+
190
+ def remove_to_one_relationship(source_record, related_record)
191
+ raise NotImplementedError
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,230 @@
1
+ require 'pundit'
2
+
3
+ module JSONAPI
4
+ module Authorization
5
+ class PunditOperationsProcessor < ::ActiveRecordOperationsProcessor
6
+ set_callback :find_operation, :before, :authorize_find
7
+ set_callback :show_operation, :before, :authorize_show
8
+ set_callback :show_relationship_operation, :before, :authorize_show_relationship
9
+ set_callback :show_related_resource_operation, :before, :authorize_show_related_resource
10
+ set_callback :show_related_resources_operation, :before, :authorize_show_related_resources
11
+ set_callback :create_resource_operation, :before, :authorize_create_resource
12
+ set_callback :remove_resource_operation, :before, :authorize_remove_resource
13
+ set_callback :replace_fields_operation, :before, :authorize_replace_fields
14
+ set_callback :replace_to_one_relationship_operation, :before, :authorize_replace_to_one_relationship
15
+ set_callback :create_to_many_relationship_operation, :before, :authorize_create_to_many_relationship
16
+ set_callback :replace_to_many_relationship_operation, :before, :authorize_replace_to_many_relationship
17
+ set_callback :remove_to_many_relationship_operation, :before, :authorize_remove_to_many_relationship
18
+ set_callback :remove_to_one_relationship_operation, :before, :authorize_remove_to_one_relationship
19
+
20
+ def authorize_find
21
+ authorizer.find(@operation.resource_klass._model_class)
22
+ end
23
+
24
+ def authorize_show
25
+ record = @operation.resource_klass.find_by_key(
26
+ operation_resource_id,
27
+ context: operation_context
28
+ )._model
29
+
30
+ authorizer.show(record)
31
+ end
32
+
33
+ def authorize_show_relationship
34
+ parent_resource = @operation.resource_klass.find_by_key(
35
+ @operation.parent_key,
36
+ context: operation_context
37
+ )
38
+
39
+ relationship = @operation.resource_klass._relationship(@operation.relationship_type)
40
+
41
+ related_resource =
42
+ case relationship
43
+ when JSONAPI::Relationship::ToOne
44
+ parent_resource.public_send(@operation.relationship_type)
45
+ when JSONAPI::Relationship::ToMany
46
+ # Do nothing — already covered by policy scopes
47
+ else
48
+ raise "Unexpected relationship type: #{relationship.inspect}"
49
+ end
50
+
51
+ parent_record = parent_resource._model
52
+ related_record = related_resource._model unless related_resource.nil?
53
+ authorizer.show_relationship(parent_record, related_record)
54
+ end
55
+
56
+ def authorize_show_related_resource
57
+ source_resource = @operation.source_klass.find_by_key(
58
+ @operation.source_id,
59
+ context: operation_context
60
+ )
61
+
62
+ related_resource = source_resource.public_send(@operation.relationship_type)
63
+
64
+ source_record = source_resource._model
65
+ related_record = related_resource._model unless related_resource.nil?
66
+ authorizer.show_related_resource(source_record, related_record)
67
+ end
68
+
69
+ def authorize_show_related_resources
70
+ source_record = @operation.source_klass.find_by_key(
71
+ @operation.source_id,
72
+ context: operation_context
73
+ )._model
74
+
75
+ authorizer.show_related_resources(source_record)
76
+ end
77
+
78
+ def authorize_replace_fields
79
+ source_record = @operation.resource_klass.find_by_key(
80
+ @operation.resource_id,
81
+ context: operation_context
82
+ )._model
83
+
84
+ authorizer.replace_fields(source_record, related_models)
85
+ end
86
+
87
+ def authorize_create_resource
88
+ source_class = @operation.resource_klass._model_class
89
+
90
+ authorizer.create_resource(source_class, related_models)
91
+ end
92
+
93
+ def authorize_remove_resource
94
+ record = @operation.resource_klass.find_by_key(
95
+ operation_resource_id,
96
+ context: operation_context
97
+ )._model
98
+
99
+ authorizer.remove_resource(record)
100
+ end
101
+
102
+ def authorize_replace_to_one_relationship
103
+ source_resource = @operation.resource_klass.find_by_key(
104
+ @operation.resource_id,
105
+ context: operation_context
106
+ )
107
+ source_record = source_resource._model
108
+
109
+ old_related_record = source_resource.records_for(@operation.relationship_type)
110
+ unless @operation.key_value.nil?
111
+ new_related_resource = @operation.resource_klass._relationship(@operation.relationship_type).resource_klass.find_by_key(
112
+ @operation.key_value,
113
+ context: operation_context
114
+ )
115
+ new_related_record = new_related_resource._model unless new_related_resource.nil?
116
+ end
117
+
118
+ authorizer.replace_to_one_relationship(
119
+ source_record,
120
+ old_related_record,
121
+ new_related_record
122
+ )
123
+ end
124
+
125
+ def authorize_create_to_many_relationship
126
+ source_record = @operation.resource_klass.find_by_key(
127
+ @operation.resource_id,
128
+ context: operation_context
129
+ )._model
130
+
131
+ related_models =
132
+ model_class_for_relationship(@operation.relationship_type).find(@operation.data)
133
+
134
+ authorizer.create_to_many_relationship(source_record, related_models)
135
+ end
136
+
137
+ def authorize_replace_to_many_relationship
138
+ source_resource = @operation.resource_klass.find_by_key(
139
+ @operation.resource_id,
140
+ context: operation_context
141
+ )
142
+ source_record = source_resource._model
143
+
144
+ related_records = source_resource.records_for(@operation.relationship_type)
145
+
146
+ authorizer.replace_to_many_relationship(
147
+ source_record,
148
+ related_records
149
+ )
150
+ end
151
+
152
+ def authorize_remove_to_many_relationship
153
+ source_resource = @operation.resource_klass.find_by_key(
154
+ @operation.resource_id,
155
+ context: operation_context
156
+ )
157
+ source_record = source_resource._model
158
+
159
+ related_resource = @operation.resource_klass._relationship(@operation.relationship_type).resource_klass.find_by_key(
160
+ @operation.associated_key,
161
+ context: operation_context
162
+ )
163
+ related_record = related_resource._model unless related_resource.nil?
164
+
165
+ authorizer.remove_to_many_relationship(
166
+ source_record,
167
+ related_record
168
+ )
169
+ end
170
+
171
+ def authorize_remove_to_one_relationship
172
+ source_resource = @operation.resource_klass.find_by_key(
173
+ @operation.resource_id,
174
+ context: operation_context
175
+ )
176
+
177
+ related_resource = source_resource.public_send(@operation.relationship_type)
178
+
179
+ source_record = source_resource._model
180
+ related_record = related_resource._model unless related_resource.nil?
181
+ authorizer.remove_to_one_relationship(source_record, related_record)
182
+ end
183
+
184
+ private
185
+
186
+ def authorizer
187
+ @authorizer ||= ::JSONAPI::Authorization.configuration.authorizer.new(operation_context)
188
+ end
189
+
190
+ # TODO: Communicate with upstream to fix this nasty hack
191
+ def operation_context
192
+ case @operation
193
+ when JSONAPI::ShowRelatedResourcesOperation
194
+ @operation.instance_variable_get('@options')[:context]
195
+ else
196
+ @operation.options[:context]
197
+ end
198
+ end
199
+
200
+ # TODO: Communicate with upstream to fix this nasty hack
201
+ def operation_resource_id
202
+ case @operation
203
+ when JSONAPI::ShowOperation
204
+ @operation.id
205
+ when JSONAPI::ShowRelatedResourcesOperation
206
+ @operation.source_id
207
+ else
208
+ @operation.resource_id
209
+ end
210
+ end
211
+
212
+ def model_class_for_relationship(assoc_name)
213
+ @operation.resource_klass._relationship(assoc_name).resource_klass._model_class
214
+ end
215
+
216
+ def related_models
217
+ data = @operation.options[:data]
218
+ return [] if data.nil?
219
+
220
+ [:to_one, :to_many].flat_map do |rel_type|
221
+ data[rel_type].flat_map do |assoc_name, assoc_ids|
222
+ assoc_klass = model_class_for_relationship(assoc_name)
223
+ # TODO: find_by_key?
224
+ assoc_klass.find(assoc_ids)
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,29 @@
1
+ require 'pundit'
2
+
3
+ module JSONAPI
4
+ module Authorization
5
+ module PunditScopedResource
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ def records(options = {})
10
+ ::Pundit.policy_scope!(options[:context][:user], _model_class)
11
+ end
12
+ end
13
+
14
+ def records_for(association_name)
15
+ record_or_records = @model.public_send(association_name)
16
+ relationship = self.class._relationships[association_name]
17
+
18
+ case relationship
19
+ when JSONAPI::Relationship::ToOne
20
+ record_or_records
21
+ when JSONAPI::Relationship::ToMany
22
+ ::Pundit.policy_scope!(context[:user], record_or_records)
23
+ else
24
+ raise "Unknown relationship type #{relationship.inspect}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ module JSONAPI
2
+ module Authorization
3
+ VERSION = "0.4.0".freeze
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,207 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsonapi-authorization
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Vesa Laakso
8
+ - Emil Sågfors
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-01-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: jsonapi-resources
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '='
19
+ - !ruby/object:Gem::Version
20
+ version: 0.7.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - '='
26
+ - !ruby/object:Gem::Version
27
+ version: 0.7.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: pundit
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: bundler
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.11'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.11'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rake
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '10.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '10.0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '3.0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '3.0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rspec-rails
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '3.0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '3.0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: pry
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '0.10'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '0.10'
112
+ - !ruby/object:Gem::Dependency
113
+ name: pry-byebug
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '1.3'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '1.3'
126
+ - !ruby/object:Gem::Dependency
127
+ name: pry-doc
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '0.6'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '0.6'
140
+ - !ruby/object:Gem::Dependency
141
+ name: pry-rails
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - "~>"
145
+ - !ruby/object:Gem::Version
146
+ version: 0.3.4
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - "~>"
152
+ - !ruby/object:Gem::Version
153
+ version: 0.3.4
154
+ description: Adds generic authorization to the jsonapi-resources gem using Pundit.
155
+ email:
156
+ - laakso.vesa@gmail.com
157
+ - emil.sagfors@iki.fi
158
+ executables: []
159
+ extensions: []
160
+ extra_rdoc_files: []
161
+ files:
162
+ - ".editorconfig"
163
+ - ".gitignore"
164
+ - ".rspec"
165
+ - ".rubocop.yml"
166
+ - ".ruby-version"
167
+ - ".travis.yml"
168
+ - Gemfile
169
+ - LICENSE.txt
170
+ - README.md
171
+ - Rakefile
172
+ - bin/console
173
+ - bin/setup
174
+ - jsonapi-authorization.gemspec
175
+ - lib/jsonapi-authorization.rb
176
+ - lib/jsonapi/authorization.rb
177
+ - lib/jsonapi/authorization/configuration.rb
178
+ - lib/jsonapi/authorization/default_pundit_authorizer.rb
179
+ - lib/jsonapi/authorization/pundit_operations_processor.rb
180
+ - lib/jsonapi/authorization/pundit_scoped_resource.rb
181
+ - lib/jsonapi/authorization/version.rb
182
+ homepage: https://github.com/venuu/jsonapi-authorization
183
+ licenses:
184
+ - MIT
185
+ metadata: {}
186
+ post_install_message:
187
+ rdoc_options: []
188
+ require_paths:
189
+ - lib
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ required_rubygems_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ requirements: []
201
+ rubyforge_project:
202
+ rubygems_version: 2.2.2
203
+ signing_key:
204
+ specification_version: 4
205
+ summary: Generic authorization for jsonapi-resources gem
206
+ test_files: []
207
+ has_rdoc: