apiphobic-authorization 1.0.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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/LICENSE.txt +19 -0
- data/README.md +35 -0
- data/lib/apiphobic-authorization.rb +3 -0
- data/lib/apiphobic/authorization/authorizable_resource.rb +133 -0
- data/lib/apiphobic/authorization/authorizer.rb +47 -0
- data/lib/apiphobic/authorization/authorizers/parameters.rb +282 -0
- data/lib/apiphobic/authorization/authorizers/scope.rb +65 -0
- data/lib/apiphobic/authorization/transformers/json_api_to_rails_attributes.rb +51 -0
- data/lib/apiphobic/authorization/version.rb +7 -0
- data/lib/apiphobic/errors/unpermitted_inclusions.rb +30 -0
- data/lib/apiphobic/errors/unpermitted_sorts.rb +32 -0
- data/lib/apiphobic/json_api/relationship.rb +132 -0
- metadata +164 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b495a30be17fcd8a76aac415fcbf6d98e56f074c782fb8b97da5485ef5c87117
|
4
|
+
data.tar.gz: 4540fba22161f2ca8169e8daad77c4c868911e33a1792d58fb1bf66df66d1e35
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3fdbe8d14d7a0cc433d73c71a2292470ff50a13182681a0f56d18959944393a8884027367ef19518027c843daa66cf9344ec22e5648d64d8f2526a9b71d1090d
|
7
|
+
data.tar.gz: 2c9a91ca38d27f3b78ee561481c12c4aeed6f920e77e19ac96b8e348f37575f93e362a4cbb1285e4adb0dc7a50bc634fc6a1b74b31d17b6120b06ff41afab952
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
Binary file
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010-2016 The Kompanee, Ltd
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Authorization
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/authorization`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'authorization'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install authorization
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
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.
|
30
|
+
|
31
|
+
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).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jfelchner/authorization.
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apple_core/action_controller/resource_naming'
|
4
|
+
require 'apiphobic/resource/model'
|
5
|
+
|
6
|
+
module Apiphobic
|
7
|
+
module Authorization
|
8
|
+
module Resource
|
9
|
+
RESOURCE_COLLECTION_ACTIONS = %w{index}.freeze
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def authorizer_class
|
13
|
+
@authorizer_class ||= Object.const_get(authorizer_class_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def authorizer_parameters_class
|
17
|
+
@authorizer_parameters_class ||= \
|
18
|
+
Object.const_get(authorizer_class_name('Parameters'))
|
19
|
+
end
|
20
|
+
|
21
|
+
def authorizer_scope_class
|
22
|
+
@authorizer_scope_class ||= Object.const_get(authorizer_class_name('Scope'))
|
23
|
+
end
|
24
|
+
|
25
|
+
def authorized_scope_root_class
|
26
|
+
@authorized_scope_root_class ||= Object.const_get(singular_resource_class_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def authorizer_class_components(type = nil)
|
30
|
+
[
|
31
|
+
name_components['root_module'],
|
32
|
+
'Authorizers',
|
33
|
+
resource_name,
|
34
|
+
type,
|
35
|
+
]
|
36
|
+
.compact
|
37
|
+
end
|
38
|
+
|
39
|
+
def authorizer_class_name(type = nil)
|
40
|
+
authorizer_class_components(type).join('::')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.included(base)
|
45
|
+
base.include AppleCore::ActionController::ResourceNaming
|
46
|
+
base.extend ClassMethods
|
47
|
+
|
48
|
+
base.before_action :authorize
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def authorize
|
54
|
+
return if authorizer.public_send(authorization_query)
|
55
|
+
|
56
|
+
Erratum.fail(
|
57
|
+
'ForbiddenError',
|
58
|
+
resource_name: self.class.singular_resource_name,
|
59
|
+
resource_id: [params[:id]],
|
60
|
+
action: action_name,
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def authorized_parameters
|
65
|
+
@authorized_parameters ||= authorizer_parameters_class
|
66
|
+
.new(action: action_name,
|
67
|
+
token: token,
|
68
|
+
user: authorized_user,
|
69
|
+
issuer: authorized_issuer,
|
70
|
+
parameters: params)
|
71
|
+
.call
|
72
|
+
end
|
73
|
+
|
74
|
+
def authorized_scope
|
75
|
+
@authorized_scope ||= self
|
76
|
+
.class
|
77
|
+
.authorizer_scope_class
|
78
|
+
.new(action: action_name,
|
79
|
+
token: token,
|
80
|
+
user: authorized_user,
|
81
|
+
issuer: authorized_issuer,
|
82
|
+
parameters: authorized_parameters,
|
83
|
+
scope_root: authorized_scope_root_class)
|
84
|
+
.call
|
85
|
+
end
|
86
|
+
|
87
|
+
def authorized_attributes
|
88
|
+
@authorized_attributes ||= Authorization::Transformers::JsonApiToRailsAttributes
|
89
|
+
.new(parameters: authorized_parameters.slice(:data))
|
90
|
+
.call
|
91
|
+
end
|
92
|
+
|
93
|
+
def authorizer
|
94
|
+
@authorizer ||= self
|
95
|
+
.class
|
96
|
+
.authorizer_class
|
97
|
+
.new(action: action_name,
|
98
|
+
token: token,
|
99
|
+
user: authorized_user,
|
100
|
+
issuer: authorized_issuer,
|
101
|
+
parameters: authorized_parameters,
|
102
|
+
resource: authorized_resource)
|
103
|
+
end
|
104
|
+
|
105
|
+
def authorized_resource
|
106
|
+
return if RESOURCE_COLLECTION_ACTIONS.include?(action_name)
|
107
|
+
|
108
|
+
@authorized_resource ||= public_send(self.class.singular_resource_name)
|
109
|
+
end
|
110
|
+
|
111
|
+
def authorized_collection
|
112
|
+
return unless RESOURCE_COLLECTION_ACTIONS.include?(action_name)
|
113
|
+
|
114
|
+
@authorized_collection ||= \
|
115
|
+
Resource::Model
|
116
|
+
.new(resource: public_send(self.class.plural_resource_name),
|
117
|
+
parameters: authorized_parameters)
|
118
|
+
end
|
119
|
+
|
120
|
+
def authorized_user
|
121
|
+
current_user
|
122
|
+
end
|
123
|
+
|
124
|
+
def authorized_issuer
|
125
|
+
current_issuer
|
126
|
+
end
|
127
|
+
|
128
|
+
def authorization_query
|
129
|
+
@authorization_query ||= "able_to_#{action_name}?"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apiphobic
|
4
|
+
module Authorization
|
5
|
+
class Authorizer
|
6
|
+
attr_accessor :action,
|
7
|
+
:token,
|
8
|
+
:user,
|
9
|
+
:params,
|
10
|
+
:resource
|
11
|
+
|
12
|
+
# rubocop:disable Metrics/ParameterLists
|
13
|
+
def initialize(action:, token:, user:, issuer:, params:, resource:, **other)
|
14
|
+
self.action = action
|
15
|
+
self.token = token
|
16
|
+
self.user = user
|
17
|
+
self.params = params
|
18
|
+
self.resource = resource
|
19
|
+
|
20
|
+
other.each do |name, value|
|
21
|
+
public_send("#{name}=", value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
# rubocop:enable Metrics/ParameterLists
|
25
|
+
|
26
|
+
def able_to_index?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def able_to_show?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def able_to_create?
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
38
|
+
def able_to_update?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def able_to_destroy?
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,282 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apiphobic/errors/unpermitted_inclusions'
|
4
|
+
require 'apiphobic/errors/unpermitted_sorts'
|
5
|
+
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
7
|
+
module Apiphobic
|
8
|
+
module Authorization
|
9
|
+
module Authorizers
|
10
|
+
class Parameters
|
11
|
+
JSON_API_PARAMETERS = %i{include sort page data filter}.freeze
|
12
|
+
|
13
|
+
attr_accessor :action,
|
14
|
+
:token,
|
15
|
+
:user,
|
16
|
+
:raw_parameters
|
17
|
+
|
18
|
+
attr_writer :authorized_attributes,
|
19
|
+
:authorized_filters,
|
20
|
+
:authorized_inclusions,
|
21
|
+
:authorized_relationships,
|
22
|
+
:authorized_sorts
|
23
|
+
|
24
|
+
# rubocop:disable Metrics/ParameterLists
|
25
|
+
def initialize(action:, token:, user:, issuer:, parameters:, **other)
|
26
|
+
self.action = action
|
27
|
+
self.token = token
|
28
|
+
self.user = user
|
29
|
+
self.raw_parameters = parameters.slice(*JSON_API_PARAMETERS)
|
30
|
+
|
31
|
+
other.each do |name, value|
|
32
|
+
public_send("#{name}=", value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
# rubocop:enable Metrics/ParameterLists
|
36
|
+
|
37
|
+
def authorized_attributes
|
38
|
+
@authorized_attributes || []
|
39
|
+
end
|
40
|
+
|
41
|
+
def authorized_filters
|
42
|
+
@authorized_filters || []
|
43
|
+
end
|
44
|
+
|
45
|
+
def authorized_inclusions
|
46
|
+
@authorized_inclusions || []
|
47
|
+
end
|
48
|
+
|
49
|
+
def authorized_relationships
|
50
|
+
@authorized_relationships || []
|
51
|
+
end
|
52
|
+
|
53
|
+
def authorized_sorts
|
54
|
+
@authorized_sorts || []
|
55
|
+
end
|
56
|
+
|
57
|
+
def call
|
58
|
+
authorized_attributes.each do |attribute|
|
59
|
+
attribute = { name: attribute } unless attribute.is_a?(::Hash)
|
60
|
+
|
61
|
+
authorize_attribute(**attribute)
|
62
|
+
end
|
63
|
+
|
64
|
+
authorized_filters.each do |filter|
|
65
|
+
filter = { name: filter } unless filter.is_a?(::Hash)
|
66
|
+
|
67
|
+
authorize_filter(**filter)
|
68
|
+
end
|
69
|
+
|
70
|
+
authorized_relationships.each do |name|
|
71
|
+
name = { name: name } unless name.is_a?(::Hash)
|
72
|
+
|
73
|
+
authorize_relationship(**name)
|
74
|
+
end
|
75
|
+
|
76
|
+
authorize_inclusions(names: authorized_inclusions)
|
77
|
+
authorize_sorts(names: authorized_sorts)
|
78
|
+
|
79
|
+
raw_parameters.permit(*authorized_parameters)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def authorized_parameters
|
85
|
+
@authorized_parameters ||= [
|
86
|
+
{
|
87
|
+
data: [
|
88
|
+
:type,
|
89
|
+
:id,
|
90
|
+
{
|
91
|
+
attributes: nil,
|
92
|
+
relationships: nil,
|
93
|
+
},
|
94
|
+
],
|
95
|
+
filter: nil,
|
96
|
+
page: %i{
|
97
|
+
number
|
98
|
+
size
|
99
|
+
offset
|
100
|
+
limit
|
101
|
+
cursor
|
102
|
+
},
|
103
|
+
},
|
104
|
+
]
|
105
|
+
end
|
106
|
+
|
107
|
+
def authorize_attribute(**args)
|
108
|
+
authorize_parameter(value: raw_parameter_attribute_value(args[:name]),
|
109
|
+
authorized_parameters: authorized_parameter_attributes,
|
110
|
+
raw_parameters: raw_parameter_attributes,
|
111
|
+
**args)
|
112
|
+
end
|
113
|
+
|
114
|
+
def authorize_filter(**args)
|
115
|
+
authorize_parameter(value: raw_parameter_filter_value(args[:name]),
|
116
|
+
authorized_parameters: authorized_parameter_filters,
|
117
|
+
raw_parameters: raw_parameter_filters,
|
118
|
+
**args)
|
119
|
+
end
|
120
|
+
|
121
|
+
def authorize_inclusions(names:)
|
122
|
+
return if names.empty?
|
123
|
+
|
124
|
+
all_requested_inclusions_authorized = raw_parameter_inclusions
|
125
|
+
.to_s
|
126
|
+
.split(',')
|
127
|
+
.all? do |inclusion|
|
128
|
+
names.map(&:to_s).include?(inclusion.to_s)
|
129
|
+
end
|
130
|
+
|
131
|
+
fail Errors::UnpermittedInclusions.new(inclusions: raw_parameter_inclusions) \
|
132
|
+
unless all_requested_inclusions_authorized
|
133
|
+
|
134
|
+
authorized_parameters << :include
|
135
|
+
end
|
136
|
+
|
137
|
+
def authorize_sorts(names:)
|
138
|
+
return if names.empty?
|
139
|
+
|
140
|
+
all_requested_sorts_authorized = raw_parameter_sorts
|
141
|
+
.to_s
|
142
|
+
.delete('-')
|
143
|
+
.split(',')
|
144
|
+
.all? do |sort|
|
145
|
+
names.map(&:to_s).include?(sort.to_s)
|
146
|
+
end
|
147
|
+
|
148
|
+
fail Errors::UnpermittedSorts.new(sorts: raw_parameter_sorts) \
|
149
|
+
unless all_requested_sorts_authorized
|
150
|
+
|
151
|
+
authorized_parameters << :sort
|
152
|
+
end
|
153
|
+
|
154
|
+
def authorize_parameter(name:,
|
155
|
+
value:,
|
156
|
+
authorized_parameters:,
|
157
|
+
raw_parameters:,
|
158
|
+
override: { with: nil, if_admin: false, if_blank: false })
|
159
|
+
|
160
|
+
value = override_parameter(name: name,
|
161
|
+
value: value,
|
162
|
+
hash: raw_parameters,
|
163
|
+
override: override)
|
164
|
+
|
165
|
+
if value.class == ::Array
|
166
|
+
authorized_parameters[0][name] = []
|
167
|
+
else
|
168
|
+
authorized_parameters << name
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def authorize_relationship(name:, embedded_attributes: [])
|
173
|
+
relationship_data = raw_parameter_relationship_data(name)
|
174
|
+
to_many_relation = relationship_data.is_a?(::Array)
|
175
|
+
first_relation = if to_many_relation
|
176
|
+
relationship_data[0]
|
177
|
+
else
|
178
|
+
relationship_data || {}
|
179
|
+
end
|
180
|
+
embedded_relation = first_relation[:attributes]
|
181
|
+
|
182
|
+
authorized_parameter_relationships[name] = if relationship_data.nil?
|
183
|
+
[:data]
|
184
|
+
elsif embedded_relation
|
185
|
+
{
|
186
|
+
data: [
|
187
|
+
:id,
|
188
|
+
:type,
|
189
|
+
{
|
190
|
+
attributes: %i{__id__} +
|
191
|
+
embedded_attributes,
|
192
|
+
},
|
193
|
+
],
|
194
|
+
}
|
195
|
+
else
|
196
|
+
{ data: %i{type id} }
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def override_parameter(name:, value:, hash:, override:)
|
201
|
+
return value unless override[:with] &&
|
202
|
+
(!token.admin? || override[:if_admin]) &&
|
203
|
+
(!value.nil? || override[:if_blank])
|
204
|
+
|
205
|
+
hash[name] = override[:with]
|
206
|
+
|
207
|
+
override[:with]
|
208
|
+
end
|
209
|
+
|
210
|
+
def authorized_data_parameter
|
211
|
+
authorized_parameters[0][:data][2]
|
212
|
+
end
|
213
|
+
|
214
|
+
def authorized_parameter_attributes
|
215
|
+
authorized_data_parameter[:attributes] ||= [{}]
|
216
|
+
end
|
217
|
+
|
218
|
+
def authorized_parameter_relationships
|
219
|
+
authorized_data_parameter[:relationships] ||= {}
|
220
|
+
end
|
221
|
+
|
222
|
+
def authorized_parameter_filters
|
223
|
+
authorized_parameters[0][:filter] ||= [{}]
|
224
|
+
end
|
225
|
+
|
226
|
+
def authorized_parameter_inclusions
|
227
|
+
authorized_parameters[0][:include] ||= ''
|
228
|
+
end
|
229
|
+
|
230
|
+
def authorized_parameter_sorts
|
231
|
+
authorized_parameters[0][:sort] ||= ''
|
232
|
+
end
|
233
|
+
|
234
|
+
# rubocop:disable Layout/ExtraSpacing
|
235
|
+
def raw_parameter_attributes
|
236
|
+
@raw_parameter_attributes ||= begin
|
237
|
+
raw_parameters[:data] ||= {}
|
238
|
+
raw_parameters[:data][:attributes] ||= {}
|
239
|
+
|
240
|
+
raw_parameters[:data][:attributes]
|
241
|
+
end
|
242
|
+
end
|
243
|
+
# rubocop:enable Layout/ExtraSpacing
|
244
|
+
|
245
|
+
def raw_parameter_filters
|
246
|
+
@raw_parameter_filters ||= raw_parameters[:filter] ||= {}
|
247
|
+
end
|
248
|
+
|
249
|
+
def raw_parameter_inclusions
|
250
|
+
@raw_parameter_inclusions ||= raw_parameters[:include] ||= ''
|
251
|
+
end
|
252
|
+
|
253
|
+
def raw_parameter_relationships
|
254
|
+
@raw_parameter_relationships ||= raw_parameters
|
255
|
+
.fetch(:data, {})
|
256
|
+
.fetch(:relationships, {})
|
257
|
+
end
|
258
|
+
|
259
|
+
def raw_parameter_sorts
|
260
|
+
@raw_parameter_sorts ||= raw_parameters[:sort] ||= ''
|
261
|
+
end
|
262
|
+
|
263
|
+
def raw_parameter_attribute_value(name)
|
264
|
+
raw_parameter_attributes[name]
|
265
|
+
end
|
266
|
+
|
267
|
+
def raw_parameter_filter_value(name)
|
268
|
+
raw_parameter_filters[name]
|
269
|
+
end
|
270
|
+
|
271
|
+
def raw_parameter_relationship(name)
|
272
|
+
raw_parameter_relationships.fetch(name, {})
|
273
|
+
end
|
274
|
+
|
275
|
+
def raw_parameter_relationship_data(name)
|
276
|
+
raw_parameter_relationship(name).fetch(:data, nil)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
# rubocop:enable Metrics/ClassLength
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apple_core/refinements/string'
|
4
|
+
|
5
|
+
module Apiphobic
|
6
|
+
module Authorization
|
7
|
+
module Authorizers
|
8
|
+
class Scope
|
9
|
+
using ::AppleCore::Refinements::String
|
10
|
+
|
11
|
+
attr_accessor :action,
|
12
|
+
:token,
|
13
|
+
:user,
|
14
|
+
:raw_parameters,
|
15
|
+
:scope_root
|
16
|
+
|
17
|
+
# rubocop:disable Metrics/ParameterLists
|
18
|
+
def initialize(action:, token:, user:, issuer:, parameters:, scope_root:, **other)
|
19
|
+
self.action = action
|
20
|
+
self.token = token
|
21
|
+
self.user = user
|
22
|
+
self.raw_parameters = parameters
|
23
|
+
self.scope_root = scope_root
|
24
|
+
|
25
|
+
other.each do |name, value|
|
26
|
+
public_send("#{name}=", value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
# rubocop:enable Metrics/ParameterLists
|
30
|
+
|
31
|
+
def user_scope
|
32
|
+
scope_root.public_send("for_#{user_underscored_class_name}", scope_user_id)
|
33
|
+
end
|
34
|
+
|
35
|
+
def public_scope
|
36
|
+
scope_root.none
|
37
|
+
end
|
38
|
+
|
39
|
+
def call
|
40
|
+
if scope_user_id
|
41
|
+
user_scope
|
42
|
+
else
|
43
|
+
public_scope
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def scope_user_id
|
50
|
+
@scope_user_id ||= raw_parameters
|
51
|
+
.fetch(:filter, {})
|
52
|
+
.fetch(user_underscored_class_name, nil)
|
53
|
+
end
|
54
|
+
|
55
|
+
def user_underscored_class_name
|
56
|
+
@user_underscored_class_name ||= begin
|
57
|
+
base_user_class_name = user.class.name[/([^:]+)\z/, 1]
|
58
|
+
|
59
|
+
base_user_class_name.underscore.downcase
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apiphobic/json_api/relationship'
|
4
|
+
|
5
|
+
module Apiphobic
|
6
|
+
module Authorization
|
7
|
+
module Transformers
|
8
|
+
class JsonApiToRailsAttributes
|
9
|
+
attr_accessor :parameters
|
10
|
+
|
11
|
+
def initialize(parameters:)
|
12
|
+
self.parameters = parameters
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
attributes.merge(relationship_attributes)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def attributes
|
22
|
+
@attributes = parameters
|
23
|
+
.fetch(:data, {})
|
24
|
+
.fetch(:attributes, parameters_class.new)
|
25
|
+
end
|
26
|
+
|
27
|
+
def relationships
|
28
|
+
@relationships = parameters
|
29
|
+
.fetch(:data, {})
|
30
|
+
.fetch(:relationships, parameters_class.new)
|
31
|
+
end
|
32
|
+
|
33
|
+
def relationship_attributes
|
34
|
+
parameters_class.new.tap do |relationship_attributes|
|
35
|
+
relationships.each_pair do |name, relationship|
|
36
|
+
relationship_attributes.merge!(JsonApi::Relationship
|
37
|
+
.new(name: name, data: relationship)
|
38
|
+
.to_rails_attributes)
|
39
|
+
end
|
40
|
+
|
41
|
+
relationship_attributes.permit! if relationship_attributes.respond_to?(:permit!)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def parameters_class
|
46
|
+
parameters.class
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erratum/error'
|
4
|
+
|
5
|
+
module Apiphobic
|
6
|
+
module Errors
|
7
|
+
class UnpermittedInclusions < RuntimeError
|
8
|
+
include Erratum::Error
|
9
|
+
|
10
|
+
attr_accessor :inclusions
|
11
|
+
|
12
|
+
def http_status
|
13
|
+
422
|
14
|
+
end
|
15
|
+
|
16
|
+
def title
|
17
|
+
'Unpermitted Inclusion'
|
18
|
+
end
|
19
|
+
|
20
|
+
def detail
|
21
|
+
'One or more of the inclusions you attempted to pass via the "include" parameter ' \
|
22
|
+
'are either not available or not authorized.'
|
23
|
+
end
|
24
|
+
|
25
|
+
def source
|
26
|
+
{ inclusions: inclusions }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erratum/error'
|
4
|
+
|
5
|
+
module Apiphobic
|
6
|
+
module Errors
|
7
|
+
class UnpermittedSorts < RuntimeError
|
8
|
+
include Erratum::Error
|
9
|
+
|
10
|
+
attr_accessor :sorts
|
11
|
+
|
12
|
+
def http_status
|
13
|
+
422
|
14
|
+
end
|
15
|
+
|
16
|
+
def title
|
17
|
+
'Unpermitted Sort'
|
18
|
+
end
|
19
|
+
|
20
|
+
def detail
|
21
|
+
<<~HEREDOC.chomp.tr("\n", ' ')
|
22
|
+
One or more of the sorts you attempted to pass via the "sort" parameter
|
23
|
+
are either not available or not authorized.
|
24
|
+
HEREDOC
|
25
|
+
end
|
26
|
+
|
27
|
+
def source
|
28
|
+
{ sorts: sorts }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apple_core/refinements/string'
|
4
|
+
|
5
|
+
module Apiphobic
|
6
|
+
module JsonApi
|
7
|
+
class Relationship
|
8
|
+
using ::AppleCore::Refinements::String
|
9
|
+
|
10
|
+
attr_accessor :name,
|
11
|
+
:raw_relationship,
|
12
|
+
:data_class
|
13
|
+
|
14
|
+
def initialize(name:, data:, data_class: nil)
|
15
|
+
self.name = name
|
16
|
+
self.raw_relationship = data
|
17
|
+
self.data_class = data_class || data.class
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_rails_attributes
|
21
|
+
__send__("#{embedded}_#{type}_to_rails_attributes")
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def embedded_has_one_to_rails_attributes
|
27
|
+
attribute = "#{name.singularize}_attributes".to_sym
|
28
|
+
|
29
|
+
with_data_class(attribute, relationship_attributes_to_rails_attributes(data))
|
30
|
+
end
|
31
|
+
|
32
|
+
def referenced_has_one_to_rails_attributes
|
33
|
+
attribute = "#{name.singularize}_id".to_sym
|
34
|
+
|
35
|
+
with_data_class(attribute, data[:id])
|
36
|
+
end
|
37
|
+
|
38
|
+
def embedded_has_many_to_rails_attributes
|
39
|
+
attribute = "#{name.pluralize}_attributes".to_sym
|
40
|
+
|
41
|
+
has_many_attributes = data.map do |datum|
|
42
|
+
relationship_attributes_to_rails_attributes(datum)
|
43
|
+
end
|
44
|
+
|
45
|
+
with_data_class(attribute, has_many_attributes)
|
46
|
+
end
|
47
|
+
|
48
|
+
def referenced_has_many_to_rails_attributes
|
49
|
+
attribute = "#{name.singularize}_ids".to_sym
|
50
|
+
|
51
|
+
has_many_attributes = data.map { |d| d[:id] }
|
52
|
+
|
53
|
+
with_data_class(attribute, has_many_attributes)
|
54
|
+
end
|
55
|
+
|
56
|
+
def empty_empty_to_rails_attributes
|
57
|
+
attribute = name.to_sym
|
58
|
+
|
59
|
+
with_data_class(attribute, nil)
|
60
|
+
end
|
61
|
+
|
62
|
+
def empty_has_many_to_rails_attributes
|
63
|
+
attribute = "#{name.singularize}_ids".to_sym
|
64
|
+
|
65
|
+
with_data_class(attribute, [])
|
66
|
+
end
|
67
|
+
|
68
|
+
def relationship_attributes_to_rails_attributes(other)
|
69
|
+
other[:attributes].dup.tap do |attrs|
|
70
|
+
attrs.delete(:__id__)
|
71
|
+
attrs[:id] = other[:id] if other[:id]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def type
|
76
|
+
return 'has_many' if has_many?
|
77
|
+
return 'has_one' if has_one?
|
78
|
+
return 'empty' unless has_data?
|
79
|
+
end
|
80
|
+
|
81
|
+
def embedded
|
82
|
+
if referenced?
|
83
|
+
'referenced'
|
84
|
+
elsif embedded?
|
85
|
+
'embedded'
|
86
|
+
elsif !has_data?
|
87
|
+
'empty'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def has_many?
|
92
|
+
data.is_a?(::Array)
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_one?
|
96
|
+
data.is_a?(::Hash) || data.is_a?(::ActionController::Parameters)
|
97
|
+
rescue NameError
|
98
|
+
false
|
99
|
+
end
|
100
|
+
|
101
|
+
def referenced?
|
102
|
+
return false unless has_data?
|
103
|
+
|
104
|
+
!Array(data)[0][:attributes]
|
105
|
+
end
|
106
|
+
|
107
|
+
def embedded?
|
108
|
+
return false unless has_data?
|
109
|
+
|
110
|
+
Array(data)[0][:attributes]
|
111
|
+
end
|
112
|
+
|
113
|
+
def has_data?
|
114
|
+
@has_data ||= Array(data).compact.any?
|
115
|
+
end
|
116
|
+
|
117
|
+
def data
|
118
|
+
raw_relationship[:data]
|
119
|
+
end
|
120
|
+
|
121
|
+
def with_data_class(key, value)
|
122
|
+
if data_class == ::Hash
|
123
|
+
{ key => value }
|
124
|
+
elsif data_class == ::ActionController::Parameters
|
125
|
+
::ActionController::Parameters.new(key => value).tap(&:permit!)
|
126
|
+
end
|
127
|
+
rescue NameError
|
128
|
+
{ key => value }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
metadata
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: apiphobic-authorization
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- thegranddesign
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIDqjCCApKgAwIBAgIBATANBgkqhkiG9w0BAQUFADBNMREwDwYDVQQDDAhydWJ5
|
14
|
+
Z2VtczEjMCEGCgmSJomT8ixkARkWE2xpdmluZ2hpZ2hvbnRoZWJsb2cxEzARBgoJ
|
15
|
+
kiaJk/IsZAEZFgNjb20wHhcNMTcwODAyMjI1OTM1WhcNMTgwODAyMjI1OTM1WjBN
|
16
|
+
MREwDwYDVQQDDAhydWJ5Z2VtczEjMCEGCgmSJomT8ixkARkWE2xpdmluZ2hpZ2hv
|
17
|
+
bnRoZWJsb2cxEzARBgoJkiaJk/IsZAEZFgNjb20wggEiMA0GCSqGSIb3DQEBAQUA
|
18
|
+
A4IBDwAwggEKAoIBAQDtLa7+7p49gW15OgOyRZad/F92iZcMdDjZ2kAxZlviXgVe
|
19
|
+
PCtjfdURobH+YMdt++6eRkE25utIFqHyN51Shxfdc21T3fPQe/ZEoMyiJK4tYzbh
|
20
|
+
7VjNJG4ldvKKpS1p7iVz9imnyTxNwb0JaIOsOFCA04T0u6aCQi2acNvAPLviXk0q
|
21
|
+
xJ/CKjI4QUTZKVrBt8Q1Egrp2yzmEnSNftDuTbBb8m4vDR+w325CwbKCgycHJ1/g
|
22
|
+
YZ3FO76TzJuRVbsYS/bU5XKHVEpkeFmWBqEXsk4DuUIWLa6WZEJcoZf+YP+1pycG
|
23
|
+
7YqSbydpINtEdopD+EEI+g+zNJ4nSI8/eQcQyEjBAgMBAAGjgZQwgZEwCQYDVR0T
|
24
|
+
BAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFDWuVrg4ve0vLu71kqiGdyBnzJGV
|
25
|
+
MCsGA1UdEQQkMCKBIHJ1YnlnZW1zQGxpdmluZ2hpZ2hvbnRoZWJsb2cuY29tMCsG
|
26
|
+
A1UdEgQkMCKBIHJ1YnlnZW1zQGxpdmluZ2hpZ2hvbnRoZWJsb2cuY29tMA0GCSqG
|
27
|
+
SIb3DQEBBQUAA4IBAQDJIpHjbBPGiaY4wOHcXlltQ+BMmhWQNh+1fZtyajQd+7Ay
|
28
|
+
fv23mO7Mf25Q38gopQlpaODkfxq54Jt8FvQbr5RYRS4j+JEKb75NgrAtehd8USUd
|
29
|
+
CiJJGH+yvGNWug9IGZCGX91HIbTsLQ5IUUWQasC5jGP8nxXufUr9xgAJZZenewny
|
30
|
+
B2qKu8q1A/kj6cw62RCY7yBmUXxlcJBj8g+JKYAFbYYKUdQSzf50k9IiWLWunJM+
|
31
|
+
Y2GAoHKstmfIVhc4XHOPpmTd2o/C29O9oaRgjrkfQEhF/KvJ/PhoV5hvokzsCyI5
|
32
|
+
iUeXPfvrGD/itYIBCgk+fnzyQQ4QtE5hTQaWQ3o2
|
33
|
+
-----END CERTIFICATE-----
|
34
|
+
date: 2018-05-01 00:00:00.000000000 Z
|
35
|
+
dependencies:
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: apple_core
|
38
|
+
requirement: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '1.1'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.1'
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: erratum
|
52
|
+
requirement: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - "~>"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '3.1'
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '3.1'
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: actionpack
|
66
|
+
requirement: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - "~>"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '5.0'
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '5.0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - "~>"
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '3.7'
|
85
|
+
type: :development
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - "~>"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '3.7'
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: rspeckled
|
94
|
+
requirement: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - "~>"
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0.0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - "~>"
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0.0'
|
106
|
+
- !ruby/object:Gem::Dependency
|
107
|
+
name: timecop
|
108
|
+
requirement: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - "~>"
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 0.9.0
|
113
|
+
type: :development
|
114
|
+
prerelease: false
|
115
|
+
version_requirements: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - "~>"
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: 0.9.0
|
120
|
+
description: ''
|
121
|
+
email:
|
122
|
+
- rubygems@livinghighontheblog.com
|
123
|
+
executables: []
|
124
|
+
extensions: []
|
125
|
+
extra_rdoc_files: []
|
126
|
+
files:
|
127
|
+
- LICENSE.txt
|
128
|
+
- README.md
|
129
|
+
- lib/apiphobic-authorization.rb
|
130
|
+
- lib/apiphobic/authorization/authorizable_resource.rb
|
131
|
+
- lib/apiphobic/authorization/authorizer.rb
|
132
|
+
- lib/apiphobic/authorization/authorizers/parameters.rb
|
133
|
+
- lib/apiphobic/authorization/authorizers/scope.rb
|
134
|
+
- lib/apiphobic/authorization/transformers/json_api_to_rails_attributes.rb
|
135
|
+
- lib/apiphobic/authorization/version.rb
|
136
|
+
- lib/apiphobic/errors/unpermitted_inclusions.rb
|
137
|
+
- lib/apiphobic/errors/unpermitted_sorts.rb
|
138
|
+
- lib/apiphobic/json_api/relationship.rb
|
139
|
+
homepage: ''
|
140
|
+
licenses:
|
141
|
+
- MIT
|
142
|
+
metadata:
|
143
|
+
allowed_push_host: https://rubygems.org
|
144
|
+
post_install_message:
|
145
|
+
rdoc_options: []
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
requirements: []
|
159
|
+
rubyforge_project:
|
160
|
+
rubygems_version: 2.7.6
|
161
|
+
signing_key:
|
162
|
+
specification_version: 4
|
163
|
+
summary: Authorization for API Requests
|
164
|
+
test_files: []
|
metadata.gz.sig
ADDED
Binary file
|