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 +7 -0
- data/.editorconfig +12 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +88 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +116 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/jsonapi-authorization.gemspec +31 -0
- data/lib/jsonapi-authorization.rb +1 -0
- data/lib/jsonapi/authorization.rb +12 -0
- data/lib/jsonapi/authorization/configuration.rb +23 -0
- data/lib/jsonapi/authorization/default_pundit_authorizer.rb +195 -0
- data/lib/jsonapi/authorization/pundit_operations_processor.rb +230 -0
- data/lib/jsonapi/authorization/pundit_scoped_resource.rb +29 -0
- data/lib/jsonapi/authorization/version.rb +5 -0
- metadata +207 -0
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
data/.gitignore
ADDED
data/.rspec
ADDED
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
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
|
+
[](https://travis-ci.org/venuu/jsonapi-authorization) [](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
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,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
|
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:
|