active_record_api-rest 1.0.3 → 1.0.4
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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/active_record_api/rest/access_denied_exception.rb +13 -12
- data/lib/active_record_api/rest/application_policy.rb +36 -8
- data/lib/active_record_api/rest/bad_session_exception.rb +6 -0
- data/lib/active_record_api/rest/controller.rb +11 -5
- data/lib/active_record_api/rest/graceful_errors.rb +7 -3
- data/lib/active_record_api/rest/index_controller.rb +1 -1
- data/lib/active_record_api/rest/spec/rest_controller_shared_example.rb +62 -66
- data/lib/active_record_api/rest/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6c7bfa36f4f4e1cf3a3013003dc091864a44e6a2f0df9c47886ce37b7c4a88f
|
4
|
+
data.tar.gz: 5929dfad290e2789a5acbfa574cde379c409d4de125679966c0ed5e98f230edf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a524c311c0cd8ecda10d96d82a9900b68853e108c96d59109e5493dce0052a6ee31348807a2f423a0234ce011bbf272eccf3d39ca2063e095ca585ef951033b1
|
7
|
+
data.tar.gz: b3c7af25eb3864129781d0cdc564c33a65ba14da22001601b5450a517267466fa865e1dd63b0b48d6d26b89137c61b9f227c8a06ddbfe7675ba50abe5ce9b719
|
data/Gemfile.lock
CHANGED
@@ -1,12 +1,13 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
1
|
+
module ActiveRecordApi
|
2
|
+
module Rest
|
3
|
+
class AccessDeniedException < Exception
|
4
|
+
attr_reader :action
|
5
|
+
attr_reader :controller
|
6
|
+
def initialize(controller, action, message)
|
7
|
+
super(message)
|
8
|
+
@action = action
|
9
|
+
@controller = controller
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,32 +1,60 @@
|
|
1
1
|
module ActiveRecordApi
|
2
2
|
module Rest
|
3
3
|
class ApplicationPolicy
|
4
|
+
class Scope
|
5
|
+
attr_reader :user, :scope
|
6
|
+
|
7
|
+
def initialize(user, scope)
|
8
|
+
@user = user
|
9
|
+
@scope = scope
|
10
|
+
end
|
11
|
+
|
12
|
+
def resolve
|
13
|
+
@scope.where(id: false)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
4
17
|
attr_reader :user
|
5
|
-
attr_reader :
|
18
|
+
attr_reader :model_class
|
19
|
+
|
20
|
+
READ = 'read'.freeze
|
21
|
+
MANAGE = 'manage'.freeze
|
6
22
|
|
7
|
-
def initialize(user,
|
23
|
+
def initialize(user, model_class)
|
8
24
|
@user = user
|
9
|
-
@
|
25
|
+
@model_class = model_class
|
10
26
|
end
|
11
27
|
|
12
28
|
def index?
|
13
|
-
|
29
|
+
can?(READ)
|
14
30
|
end
|
15
31
|
|
16
32
|
def show?
|
17
|
-
|
33
|
+
can?(READ)
|
18
34
|
end
|
19
35
|
|
20
36
|
def create?
|
21
|
-
|
37
|
+
can?(MANAGE)
|
22
38
|
end
|
23
39
|
|
24
40
|
def update?
|
25
|
-
|
41
|
+
can?(MANAGE)
|
26
42
|
end
|
27
43
|
|
28
44
|
def destroy?
|
29
|
-
|
45
|
+
can?(MANAGE)
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def can?(action)
|
51
|
+
user.full_permissions.include?("#{service_name}__#{@model_class.name.downcase}:#{action}")
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def service_name
|
57
|
+
File.basename(Rails.root.to_s)
|
30
58
|
end
|
31
59
|
end
|
32
60
|
end
|
@@ -60,7 +60,7 @@ module ActiveRecordApi
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def load_model
|
63
|
-
@model ||= model_klass.find(params[:id])
|
63
|
+
@model ||= scope_filter(model_klass).find(params[:id])
|
64
64
|
end
|
65
65
|
|
66
66
|
def initialize_model
|
@@ -68,10 +68,16 @@ module ActiveRecordApi
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def authorize
|
71
|
-
|
72
|
-
raise
|
73
|
-
|
74
|
-
|
71
|
+
raise BadSessionException.new(controller_name, action_name) if current_user.nil?
|
72
|
+
raise AccessDeniedException.new(controller_name, action_name, 'Insufficient permissions') unless policy.send("#{action_name}?")
|
73
|
+
end
|
74
|
+
|
75
|
+
def scope_filter(scope)
|
76
|
+
"#{policy.class.name}::Scope".constantize.new(current_user, scope).resolve
|
77
|
+
end
|
78
|
+
|
79
|
+
def policy
|
80
|
+
@policy ||= ("#{controller_name.classify}Policy".safe_constantize) ? "#{controller_name.classify}Policy".constantize.new(current_user, model_klass) : ApplicationPolicy.new(current_user, model_klass)
|
75
81
|
end
|
76
82
|
end
|
77
83
|
end
|
@@ -8,9 +8,13 @@ module ActiveRecordApi
|
|
8
8
|
render status: :not_found, json: { base: exception.message }
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
rescue_from BadSessionException do |exception|
|
12
|
+
render status: :unauthorized, json: { base: "No user for session on #{exception.action} #{exception.controller}" }
|
13
|
+
end
|
14
|
+
|
15
|
+
rescue_from AccessDeniedException do |exception|
|
16
|
+
render status: :forbidden, json: { base: "Access denied on #{exception.action} #{exception.controller}", message: exception.message }
|
17
|
+
end
|
14
18
|
end
|
15
19
|
end
|
16
20
|
end
|
@@ -1,15 +1,14 @@
|
|
1
1
|
shared_examples 'all::rest::actions' do
|
2
|
-
let(:authorize_read) { mock_valid_auth(permission_name(model_klass, '
|
3
|
-
let(:unauthorize_read) { mock_valid_auth(permission_name(model_klass, '')
|
4
|
-
let(:authorize_create) { mock_valid_auth(permission_name(model_klass, '
|
5
|
-
let(:unauthorize_create) { mock_valid_auth(permission_name(model_klass, '')
|
6
|
-
let(:authorize_update) { mock_valid_auth(permission_name(model_klass, '
|
7
|
-
let(:unauthorize_update) { mock_valid_auth(permission_name(model_klass, '')
|
8
|
-
let(:authorize_delete) { mock_valid_auth(permission_name(model_klass, '
|
9
|
-
let(:unauthorize_delete) { mock_valid_auth(permission_name(model_klass, '')
|
2
|
+
let(:authorize_read) { mock_valid_auth([permission_name(model_klass, 'read')]) }
|
3
|
+
let(:unauthorize_read) { mock_valid_auth([permission_name(model_klass, '')]) }
|
4
|
+
let(:authorize_create) { mock_valid_auth([permission_name(model_klass, 'manage')]) }
|
5
|
+
let(:unauthorize_create) { mock_valid_auth([permission_name(model_klass, '')]) }
|
6
|
+
let(:authorize_update) { mock_valid_auth([permission_name(model_klass, 'manage')]) }
|
7
|
+
let(:unauthorize_update) { mock_valid_auth([permission_name(model_klass, '')]) }
|
8
|
+
let(:authorize_delete) { mock_valid_auth([permission_name(model_klass, 'manage')]) }
|
9
|
+
let(:unauthorize_delete) { mock_valid_auth([permission_name(model_klass, '')]) }
|
10
10
|
let(:factory_symbol) { model_klass.to_s.underscore.to_sym }
|
11
|
-
let(:
|
12
|
-
let(:model_array) { [model] + create_list(factory_symbol, 4, organization_id: model.organization_id) }
|
11
|
+
let(:model_array) { [model] + create_list(factory_symbol, 4) }
|
13
12
|
let(:base_model_klass) do
|
14
13
|
if model_klass.superclass == ApplicationRecord
|
15
14
|
model_klass
|
@@ -59,14 +58,21 @@ shared_examples 'get::show' do
|
|
59
58
|
it { expect(JSON.parse(response.body)['base']).to include "Couldn't find #{base_model_klass} with 'id'=-1" }
|
60
59
|
end
|
61
60
|
end
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
61
|
+
context 'when not authorized' do
|
62
|
+
before(:each) do
|
63
|
+
unauthorize_read
|
64
|
+
get :show, params: { id: model.to_param }
|
65
|
+
end
|
66
|
+
it { expect(response.status).to eq 403 }
|
67
|
+
it { expect(JSON.parse(response.body)['base']).to include 'Access denied on show' }
|
68
|
+
end
|
69
|
+
context 'when bad session' do
|
70
|
+
before(:each) do
|
71
|
+
get :show, params: { id: model.to_param }
|
72
|
+
end
|
73
|
+
it { expect(response.status).to eq 401 }
|
74
|
+
it { expect(JSON.parse(response.body)['base']).to include 'No user for session on show' }
|
75
|
+
end
|
70
76
|
end
|
71
77
|
end
|
72
78
|
|
@@ -98,27 +104,17 @@ shared_examples 'get::index' do
|
|
98
104
|
it { expect(response.headers['x-link-next']).to eq "#{host}#{request.path}?limit=1&previous_id=#{next_previous_id}" }
|
99
105
|
it { expect(response.body).to be_json_eql model_klass.where('id > ?', previous_id).limit(1).map { |o| serializer.new(o) }.to_json }
|
100
106
|
end
|
101
|
-
|
102
|
-
# context 'when only access some of the data' do
|
103
|
-
# before(:each) do
|
104
|
-
# index_non_accessible_data
|
105
|
-
# get :index
|
106
|
-
# end
|
107
|
-
# it { expect(response.status).to eq 200 }
|
108
|
-
# it { expect(response.headers['x-total']).to eq 1 }
|
109
|
-
# it { expect(response.body).to be_json_eql [serializer.new(model)].to_json }
|
110
|
-
# end
|
111
107
|
end
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
108
|
+
context 'when not authorized' do
|
109
|
+
before(:each) do
|
110
|
+
unauthorize_read
|
111
|
+
end
|
112
|
+
before(:each) do
|
113
|
+
get :index
|
114
|
+
end
|
115
|
+
it { expect(response.status).to eq 403 }
|
116
|
+
it { expect(JSON.parse(response.body)['base']).to include 'Access denied on index' }
|
117
|
+
end
|
122
118
|
end
|
123
119
|
end
|
124
120
|
|
@@ -154,14 +150,14 @@ shared_examples 'put::update' do
|
|
154
150
|
it { expect(response.body).to be_json_eql({ base: 'Extra parameters are not allow: foobars' }.to_json) }
|
155
151
|
end
|
156
152
|
end
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
153
|
+
context 'when not authorized' do
|
154
|
+
before(:each) do
|
155
|
+
unauthorize_update
|
156
|
+
get :show, params: new_attributes
|
157
|
+
end
|
158
|
+
it { expect(response.status).to eq 403 }
|
159
|
+
it { expect(JSON.parse(response.body)['base']).to include 'Access denied on show' }
|
160
|
+
end
|
165
161
|
end
|
166
162
|
end
|
167
163
|
|
@@ -192,14 +188,14 @@ shared_examples 'post::create' do
|
|
192
188
|
end
|
193
189
|
end
|
194
190
|
end
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
191
|
+
context 'when not authorized' do
|
192
|
+
before(:each) do
|
193
|
+
unauthorize_create
|
194
|
+
post :create, params: new_attributes
|
195
|
+
end
|
196
|
+
it { expect(response.status).to eq 403 }
|
197
|
+
it { expect(JSON.parse(response.body)['base']).to include 'Access denied on create' }
|
198
|
+
end
|
203
199
|
end
|
204
200
|
end
|
205
201
|
|
@@ -210,19 +206,19 @@ shared_examples 'delete::delete' do
|
|
210
206
|
authorize_delete
|
211
207
|
end
|
212
208
|
it 'remove record' do
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
209
|
+
model
|
210
|
+
before_count = model_klass.count
|
211
|
+
delete :destroy, params: { id: model.id }
|
212
|
+
expect(model_klass.count).to eq(before_count - 1)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
context 'when not authorized' do
|
216
|
+
before(:each) do
|
217
|
+
unauthorize_delete
|
218
|
+
delete :destroy, params: { id: model.id }
|
217
219
|
end
|
220
|
+
it { expect(response.status).to eq 403 }
|
221
|
+
it { expect(JSON.parse(response.body)['base']).to include 'Access denied on destroy' }
|
218
222
|
end
|
219
|
-
# context 'when not authorized' do
|
220
|
-
# before(:each) do
|
221
|
-
# unauthorize_delete
|
222
|
-
# delete :destroy, params: { id: model.id }
|
223
|
-
# end
|
224
|
-
# it { expect(response.status).to eq 403 }
|
225
|
-
# it { expect(JSON.parse(response.body)['base']).to include 'Access denied on destroy' }
|
226
|
-
# end
|
227
223
|
end
|
228
224
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_api-rest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Full Measure Education
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-03-
|
11
|
+
date: 2019-03-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|