forest_liana 5.3.3 → 5.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 +4 -4
- data/app/controllers/forest_liana/resources_controller.rb +14 -17
- data/app/controllers/forest_liana/smart_actions_controller.rb +10 -5
- data/app/services/forest_liana/permissions_checker.rb +118 -56
- data/app/services/forest_liana/permissions_formatter.rb +52 -0
- data/app/services/forest_liana/permissions_getter.rb +52 -17
- data/app/services/forest_liana/scope_validator.rb +8 -7
- data/app/services/forest_liana/utils/beta_schema_utils.rb +13 -0
- data/lib/forest_liana/version.rb +1 -1
- data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +711 -0
- data/spec/services/forest_liana/permissions_checker_acl_enabled_spec.rb +831 -0
- data/spec/services/forest_liana/permissions_formatter_spec.rb +222 -0
- data/spec/services/forest_liana/permissions_getter_spec.rb +82 -0
- data/spec/spec_helper.rb +3 -0
- metadata +12 -2
@@ -32,9 +32,10 @@ module ForestLiana
|
|
32
32
|
def compute_condition_filters_from_scope(user_id)
|
33
33
|
computed_condition_filters = @scope_filters.clone
|
34
34
|
computed_condition_filters['conditions'].each do |condition|
|
35
|
-
if condition.include?('value') &&
|
36
|
-
!condition['value'].nil? &&
|
37
|
-
condition['value'].
|
35
|
+
if condition.include?('value') &&
|
36
|
+
!condition['value'].nil? &&
|
37
|
+
condition['value'].instance_of?(String) &&
|
38
|
+
condition['value'].start_with?('$') &&
|
38
39
|
@users_variable_values.include?(user_id)
|
39
40
|
condition['value'] = @users_variable_values[user_id][condition['value']]
|
40
41
|
end
|
@@ -51,9 +52,9 @@ module ForestLiana
|
|
51
52
|
ensure_valid_aggregation(node)
|
52
53
|
|
53
54
|
return is_scope_condition?(node) unless node['aggregator']
|
54
|
-
|
55
|
+
|
55
56
|
# NOTICE: Remove conditions that are not from the scope
|
56
|
-
filtered_conditions = node['conditions'].map { |condition|
|
57
|
+
filtered_conditions = node['conditions'].map { |condition|
|
57
58
|
search_scope_aggregation(condition)
|
58
59
|
}.select { |condition|
|
59
60
|
condition
|
@@ -61,7 +62,7 @@ module ForestLiana
|
|
61
62
|
|
62
63
|
# NOTICE: If there is only one condition filter left and its current aggregator is
|
63
64
|
# an "and", this condition filter is the searched scope
|
64
|
-
if (filtered_conditions.length == 1 &&
|
65
|
+
if (filtered_conditions.length == 1 &&
|
65
66
|
filtered_conditions.first.is_a?(Hash) &&
|
66
67
|
filtered_conditions.first.include?(:aggregator) &&
|
67
68
|
node['aggregator'] == 'and')
|
@@ -70,7 +71,7 @@ module ForestLiana
|
|
70
71
|
|
71
72
|
# NOTICE: Otherwise, validate if the current node is the scope and return nil
|
72
73
|
# if it's not
|
73
|
-
return (filtered_conditions.length == @scope_filters['conditions'].length &&
|
74
|
+
return (filtered_conditions.length == @scope_filters['conditions'].length &&
|
74
75
|
node['aggregator'] == @scope_filters['aggregator']) ?
|
75
76
|
{ aggregator: node['aggregator'], conditions: filtered_conditions } :
|
76
77
|
nil
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
module Utils
|
3
|
+
class BetaSchemaUtils
|
4
|
+
def self.find_action_from_endpoint(collection_name, endpoint, http_method)
|
5
|
+
collection = ForestLiana.apimap.find { |collection| collection.name.to_s == collection_name }
|
6
|
+
|
7
|
+
return nil unless collection
|
8
|
+
|
9
|
+
collection.actions.find { |action| action.endpoint == endpoint && action.http_method == http_method }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/forest_liana/version.rb
CHANGED
@@ -0,0 +1,711 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
describe PermissionsChecker do
|
3
|
+
before(:each) do
|
4
|
+
described_class.empty_cache
|
5
|
+
end
|
6
|
+
|
7
|
+
let(:user_id) { 1 }
|
8
|
+
let(:schema) {
|
9
|
+
[
|
10
|
+
ForestLiana::Model::Collection.new({
|
11
|
+
name: 'all_rights_collection',
|
12
|
+
fields: [],
|
13
|
+
actions: [
|
14
|
+
ForestLiana::Model::Action.new({
|
15
|
+
name: 'Test',
|
16
|
+
endpoint: 'forest/actions/Test',
|
17
|
+
http_method: 'POST'
|
18
|
+
}), ForestLiana::Model::Action.new({
|
19
|
+
name: 'TestPut',
|
20
|
+
endpoint: 'forest/actions/Test',
|
21
|
+
http_method: 'PUT'
|
22
|
+
}), ForestLiana::Model::Action.new({
|
23
|
+
name: 'TestRestricted',
|
24
|
+
endpoint: 'forest/actions/TestRestricted',
|
25
|
+
http_method: 'POST'
|
26
|
+
}), ForestLiana::Model::Action.new({
|
27
|
+
name: 'Test Default Values',
|
28
|
+
})
|
29
|
+
]
|
30
|
+
}), ForestLiana::Model::Collection.new({
|
31
|
+
name: 'no_rights_collection',
|
32
|
+
fields: [],
|
33
|
+
actions: [
|
34
|
+
ForestLiana::Model::Action.new({
|
35
|
+
name: 'Test',
|
36
|
+
endpoint: 'forest/actions/Test',
|
37
|
+
http_method: 'POST'
|
38
|
+
})
|
39
|
+
]
|
40
|
+
}), ForestLiana::Model::Collection.new({
|
41
|
+
name: 'custom',
|
42
|
+
fields: [],
|
43
|
+
actions: []
|
44
|
+
})
|
45
|
+
]
|
46
|
+
}
|
47
|
+
let(:default_api_permissions) {
|
48
|
+
{
|
49
|
+
"data" => {
|
50
|
+
"all_rights_collection" => {
|
51
|
+
"collection" => {
|
52
|
+
"list" => true,
|
53
|
+
"show" => true,
|
54
|
+
"create" => true,
|
55
|
+
"update" => true,
|
56
|
+
"delete" => true,
|
57
|
+
"export" => true,
|
58
|
+
"searchToEdit" => true
|
59
|
+
},
|
60
|
+
"actions" => {
|
61
|
+
"Test" => {
|
62
|
+
"allowed" => true,
|
63
|
+
"users" => nil
|
64
|
+
},
|
65
|
+
"TestPut" => {
|
66
|
+
"allowed" => false,
|
67
|
+
"users" => nil
|
68
|
+
},
|
69
|
+
"TestRestricted" => {
|
70
|
+
"allowed" => true,
|
71
|
+
"users" => [1]
|
72
|
+
},
|
73
|
+
"Test Default Values" => {
|
74
|
+
"allowed" => true,
|
75
|
+
"users" => nil
|
76
|
+
},
|
77
|
+
},
|
78
|
+
"scope" => nil
|
79
|
+
},
|
80
|
+
"no_rights_collection" => {
|
81
|
+
"collection" => {
|
82
|
+
"list" => false,
|
83
|
+
"show" => false,
|
84
|
+
"create" => false,
|
85
|
+
"update" => false,
|
86
|
+
"delete" => false,
|
87
|
+
"export" => false,
|
88
|
+
"searchToEdit" => false
|
89
|
+
},
|
90
|
+
"actions" => {
|
91
|
+
"Test" => {
|
92
|
+
"allowed" => false,
|
93
|
+
"users" => nil
|
94
|
+
}
|
95
|
+
},
|
96
|
+
"scope" => nil
|
97
|
+
},
|
98
|
+
},
|
99
|
+
"meta" => {
|
100
|
+
"rolesACLActivated" => false
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
before do
|
106
|
+
allow(ForestLiana).to receive(:name_for).and_return(collection_name)
|
107
|
+
allow(ForestLiana).to receive(:apimap).and_return(schema)
|
108
|
+
end
|
109
|
+
|
110
|
+
describe 'handling cache' do
|
111
|
+
let(:collection_name) { 'all_rights_collection' }
|
112
|
+
let(:fake_ressource) { nil }
|
113
|
+
let(:default_rendering_id) { 1 }
|
114
|
+
|
115
|
+
context 'when calling twice the same permissions' do
|
116
|
+
before do
|
117
|
+
# clones is called to duplicate the returned value and not use to same (which results in an error
|
118
|
+
# as the permissions is edited through the formatter)
|
119
|
+
allow(ForestLiana::PermissionsGetter).to receive(:get_permissions_for_rendering) { default_api_permissions.clone }
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'after expiration time' do
|
123
|
+
before do
|
124
|
+
allow(ENV).to receive(:[]).with('FOREST_PERMISSIONS_EXPIRATION_IN_SECONDS').and_return('-1')
|
125
|
+
# Needed to enforce ENV stub
|
126
|
+
described_class.empty_cache
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should call the API twice' do
|
130
|
+
described_class.new(fake_ressource, 'exportEnabled', default_rendering_id, user_id: user_id).is_authorized?
|
131
|
+
described_class.new(fake_ressource, 'exportEnabled', default_rendering_id, user_id: user_id).is_authorized?
|
132
|
+
|
133
|
+
expect(ForestLiana::PermissionsGetter).to have_received(:get_permissions_for_rendering).twice
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'before expiration time' do
|
138
|
+
it 'should call the API only once' do
|
139
|
+
described_class.new(fake_ressource, 'exportEnabled', default_rendering_id, user_id: user_id).is_authorized?
|
140
|
+
described_class.new(fake_ressource, 'exportEnabled', default_rendering_id, user_id: user_id).is_authorized?
|
141
|
+
|
142
|
+
expect(ForestLiana::PermissionsGetter).to have_received(:get_permissions_for_rendering).once
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'with permissions coming from 2 different renderings' do
|
148
|
+
let(:collection_name) { 'custom' }
|
149
|
+
let(:api_permissions_rendering_1) {
|
150
|
+
{
|
151
|
+
"data" => {
|
152
|
+
"custom" => {
|
153
|
+
"collection" => {
|
154
|
+
"list" => true,
|
155
|
+
"show" => true,
|
156
|
+
"create" => true,
|
157
|
+
"update" => true,
|
158
|
+
"delete" => true,
|
159
|
+
"export" => true,
|
160
|
+
"searchToEdit" => true
|
161
|
+
},
|
162
|
+
"actions" => { },
|
163
|
+
"scope" => nil
|
164
|
+
},
|
165
|
+
},
|
166
|
+
"meta" => {
|
167
|
+
"rolesACLActivated" => false
|
168
|
+
}
|
169
|
+
}
|
170
|
+
}
|
171
|
+
let(:api_permissions_rendering_2) {
|
172
|
+
api_permissions_rendering_2 = api_permissions_rendering_1.deep_dup
|
173
|
+
api_permissions_rendering_2['data']['custom']['collection']['export'] = false
|
174
|
+
api_permissions_rendering_2
|
175
|
+
}
|
176
|
+
let(:authorized_to_export_rendering_1) { described_class.new(fake_ressource, 'exportEnabled', 1, user_id: user_id).is_authorized? }
|
177
|
+
let(:authorized_to_export_rendering_2) { described_class.new(fake_ressource, 'exportEnabled', 2, user_id: user_id).is_authorized? }
|
178
|
+
|
179
|
+
before do
|
180
|
+
allow(ForestLiana::PermissionsGetter).to receive(:get_permissions_for_rendering)
|
181
|
+
allow(ForestLiana::PermissionsGetter).to receive(:get_permissions_for_rendering).with(1).and_return(api_permissions_rendering_1)
|
182
|
+
allow(ForestLiana::PermissionsGetter).to receive(:get_permissions_for_rendering).with(2).and_return(api_permissions_rendering_2)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should return 2 different values' do
|
186
|
+
expect(authorized_to_export_rendering_1).to eq true
|
187
|
+
expect(authorized_to_export_rendering_2).to eq false
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
context 'scopes cache' do
|
194
|
+
let(:fake_ressource) { nil }
|
195
|
+
let(:rendering_id) { 1 }
|
196
|
+
let(:collection_name) { 'custom' }
|
197
|
+
let(:scope_permissions) { { rendering_id => { 'custom' => nil } } }
|
198
|
+
let(:api_permissions) {
|
199
|
+
{
|
200
|
+
"data" => {
|
201
|
+
"custom" => {
|
202
|
+
"collection" => {
|
203
|
+
"list" => true,
|
204
|
+
"show" => true,
|
205
|
+
"create" => true,
|
206
|
+
"update" => true,
|
207
|
+
"delete" => true,
|
208
|
+
"export" => true,
|
209
|
+
"searchToEdit" => true
|
210
|
+
},
|
211
|
+
"actions" => { },
|
212
|
+
"scope" => nil
|
213
|
+
},
|
214
|
+
},
|
215
|
+
"meta" => {
|
216
|
+
"rolesACLActivated" => false
|
217
|
+
}
|
218
|
+
}
|
219
|
+
}
|
220
|
+
let(:api_permissions_scope_only) {
|
221
|
+
{
|
222
|
+
"data" => {
|
223
|
+
'collections' => { },
|
224
|
+
'renderings' => scope_permissions
|
225
|
+
},
|
226
|
+
"meta" => {
|
227
|
+
"rolesACLActivated" => false
|
228
|
+
}
|
229
|
+
}
|
230
|
+
}
|
231
|
+
|
232
|
+
before do
|
233
|
+
# clones is called to duplicate the returned value and not use to same (which results in an error
|
234
|
+
# as the permissions is edited through the formatter)
|
235
|
+
allow(ForestLiana::PermissionsGetter).to receive(:get_permissions_for_rendering).with(rendering_id) { api_permissions.clone }
|
236
|
+
allow(ForestLiana::PermissionsGetter).to receive(:get_permissions_for_rendering).with(rendering_id, rendering_specific_only: true).and_return(api_permissions_scope_only)
|
237
|
+
end
|
238
|
+
|
239
|
+
context 'when checking once for authorization' do
|
240
|
+
context 'when checking browseEnabled' do
|
241
|
+
context 'when expiration value is set to its default' do
|
242
|
+
it 'should not call the API to refresh the scopes cache' do
|
243
|
+
described_class.new(fake_ressource, 'browseEnabled', rendering_id, user_id: user_id).is_authorized?
|
244
|
+
|
245
|
+
expect(ForestLiana::PermissionsGetter).to have_received(:get_permissions_for_rendering).with(rendering_id).once
|
246
|
+
expect(ForestLiana::PermissionsGetter).not_to have_received(:get_permissions_for_rendering).with(rendering_id, rendering_specific_only: true)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'when expiration value is set in the past' do
|
251
|
+
before do
|
252
|
+
allow(ENV).to receive(:[]).with('FOREST_PERMISSIONS_EXPIRATION_IN_SECONDS').and_return('-1')
|
253
|
+
# Needed to enforce ENV stub
|
254
|
+
described_class.empty_cache
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'should call the API to refresh the scopes cache' do
|
258
|
+
described_class.new(fake_ressource, 'browseEnabled', rendering_id, user_id: user_id).is_authorized?
|
259
|
+
|
260
|
+
expect(ForestLiana::PermissionsGetter).to have_received(:get_permissions_for_rendering).with(rendering_id).once
|
261
|
+
expect(ForestLiana::PermissionsGetter).to have_received(:get_permissions_for_rendering).with(rendering_id, rendering_specific_only: true).once
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Only browse permission requires scopes
|
267
|
+
context 'when checking exportEnabled' do
|
268
|
+
context 'when expiration value is set in the past' do
|
269
|
+
before do
|
270
|
+
allow(ENV).to receive(:[]).with('FOREST_PERMISSIONS_EXPIRATION_IN_SECONDS').and_return('-1')
|
271
|
+
# Needed to enforce ENV stub
|
272
|
+
described_class.empty_cache
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'should NOT call the API to refresh the scopes cache' do
|
277
|
+
described_class.new(fake_ressource, 'exportEnabled', rendering_id, user_id: user_id).is_authorized?
|
278
|
+
|
279
|
+
expect(ForestLiana::PermissionsGetter).to have_received(:get_permissions_for_rendering).with(rendering_id).once
|
280
|
+
expect(ForestLiana::PermissionsGetter).not_to have_received(:get_permissions_for_rendering).with(rendering_id, rendering_specific_only: true)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context 'when checking twice for authorization' do
|
286
|
+
context 'on the same rendering' do
|
287
|
+
context 'when scopes permission has NOT expired' do
|
288
|
+
it 'should NOT call the API to refresh the scopes permissions' do
|
289
|
+
described_class.new(fake_ressource, 'browseEnabled', rendering_id, user_id: user_id).is_authorized?
|
290
|
+
described_class.new(fake_ressource, 'browseEnabled', rendering_id, user_id: user_id).is_authorized?
|
291
|
+
|
292
|
+
expect(ForestLiana::PermissionsGetter).to have_received(:get_permissions_for_rendering).with(rendering_id).once
|
293
|
+
expect(ForestLiana::PermissionsGetter).not_to have_received(:get_permissions_for_rendering).with(rendering_id, rendering_specific_only: true)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
context 'when scopes permission has expired' do
|
298
|
+
before do
|
299
|
+
allow(ENV).to receive(:[]).with('FOREST_PERMISSIONS_EXPIRATION_IN_SECONDS').and_return('-1')
|
300
|
+
# Needed to enforce ENV stub
|
301
|
+
described_class.empty_cache
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'should call the API to refresh the scopes permissions' do
|
305
|
+
described_class.new(fake_ressource, 'browseEnabled', rendering_id, user_id: user_id).is_authorized?
|
306
|
+
described_class.new(fake_ressource, 'browseEnabled', rendering_id, user_id: user_id).is_authorized?
|
307
|
+
|
308
|
+
expect(ForestLiana::PermissionsGetter).to have_received(:get_permissions_for_rendering).with(rendering_id).twice
|
309
|
+
expect(ForestLiana::PermissionsGetter).to have_received(:get_permissions_for_rendering).with(rendering_id, rendering_specific_only: true).twice
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
context 'on two different renderings' do
|
315
|
+
let(:other_rendering_id) { 2 }
|
316
|
+
let(:api_permissions_scope_only) {
|
317
|
+
{
|
318
|
+
"data" => {
|
319
|
+
'collections' => { },
|
320
|
+
'renderings' => {
|
321
|
+
'2' => { 'custom' => nil }
|
322
|
+
}
|
323
|
+
},
|
324
|
+
"meta" => {
|
325
|
+
"rolesACLActivated" => false
|
326
|
+
}
|
327
|
+
}
|
328
|
+
}
|
329
|
+
let(:api_permissions_copy) { api_permissions.clone }
|
330
|
+
|
331
|
+
before do
|
332
|
+
allow(ForestLiana::PermissionsGetter).to receive(:get_permissions_for_rendering).with(other_rendering_id).and_return(api_permissions_copy)
|
333
|
+
allow(ForestLiana::PermissionsGetter).to receive(:get_permissions_for_rendering).with(other_rendering_id, rendering_specific_only: true).and_return(api_permissions_scope_only)
|
334
|
+
end
|
335
|
+
|
336
|
+
it 'should not call the API to refresh the scopes permissions' do
|
337
|
+
described_class.new(fake_ressource, 'browseEnabled', rendering_id, user_id: user_id).is_authorized?
|
338
|
+
described_class.new(fake_ressource, 'browseEnabled', other_rendering_id, user_id: user_id).is_authorized?
|
339
|
+
|
340
|
+
expect(ForestLiana::PermissionsGetter).to have_received(:get_permissions_for_rendering).with(rendering_id).once
|
341
|
+
expect(ForestLiana::PermissionsGetter).to have_received(:get_permissions_for_rendering).with(other_rendering_id).once
|
342
|
+
expect(ForestLiana::PermissionsGetter).not_to have_received(:get_permissions_for_rendering).with(rendering_id, rendering_specific_only: true)
|
343
|
+
expect(ForestLiana::PermissionsGetter).not_to have_received(:get_permissions_for_rendering).with(other_rendering_id, rendering_specific_only: true)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
describe '#is_authorized?' do
|
350
|
+
# Resource is only used to retrieve the collection name as it's stubbed it does not
|
351
|
+
# need to be defined
|
352
|
+
let(:fake_ressource) { nil }
|
353
|
+
let(:default_rendering_id) { nil }
|
354
|
+
let(:api_permissions) { default_api_permissions }
|
355
|
+
let(:collection_name) { 'all_rights_collection' }
|
356
|
+
|
357
|
+
before do
|
358
|
+
allow(ForestLiana::PermissionsGetter).to receive(:get_permissions_for_rendering).and_return(api_permissions)
|
359
|
+
end
|
360
|
+
|
361
|
+
context 'when permissions does NOT have rolesACLActivated' do
|
362
|
+
describe 'exportEnabled permission' do
|
363
|
+
subject { described_class.new(fake_ressource, 'exportEnabled', default_rendering_id, user_id: user_id) }
|
364
|
+
|
365
|
+
context 'when user has the required permission' do
|
366
|
+
it 'should be authorized' do
|
367
|
+
expect(subject.is_authorized?).to be true
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
context 'when user has not the required permission' do
|
372
|
+
let(:collection_name) { 'no_rights_collection' }
|
373
|
+
|
374
|
+
it 'should NOT be authorized' do
|
375
|
+
expect(subject.is_authorized?).to be false
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
describe 'browseEnabled permission' do
|
381
|
+
let(:collection_name) { 'custom' }
|
382
|
+
subject { described_class.new(fake_ressource, 'browseEnabled', default_rendering_id, user_id: user_id) }
|
383
|
+
let(:scope_permissions) { nil }
|
384
|
+
let(:default_api_permissions) {
|
385
|
+
{
|
386
|
+
"data" => {
|
387
|
+
"custom" => {
|
388
|
+
"collection" => collection_permissions,
|
389
|
+
"actions" => { },
|
390
|
+
"scope" => scope_permissions
|
391
|
+
},
|
392
|
+
},
|
393
|
+
"meta" => {
|
394
|
+
"rolesACLActivated" => false
|
395
|
+
}
|
396
|
+
}
|
397
|
+
}
|
398
|
+
|
399
|
+
context 'when user has list permission' do
|
400
|
+
let(:collection_permissions) {
|
401
|
+
{
|
402
|
+
"list" => true,
|
403
|
+
"show" => false,
|
404
|
+
"create" => false,
|
405
|
+
"update" => false,
|
406
|
+
"delete" => false,
|
407
|
+
"export" => false,
|
408
|
+
"searchToEdit" => false
|
409
|
+
}
|
410
|
+
}
|
411
|
+
|
412
|
+
it 'should be authorized' do
|
413
|
+
expect(subject.is_authorized?).to be true
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
context 'when user has searchToEdit permission' do
|
418
|
+
let(:collection_permissions) {
|
419
|
+
{
|
420
|
+
"list" => false,
|
421
|
+
"show" => false,
|
422
|
+
"create" => false,
|
423
|
+
"update" => false,
|
424
|
+
"delete" => false,
|
425
|
+
"export" => false,
|
426
|
+
"searchToEdit" => true
|
427
|
+
}
|
428
|
+
}
|
429
|
+
|
430
|
+
it 'should be authorized' do
|
431
|
+
expect(subject.is_authorized?).to be true
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
context 'when user has not the list nor the searchToEdit permission' do
|
436
|
+
let(:collection_permissions) {
|
437
|
+
{
|
438
|
+
"list" => false,
|
439
|
+
"show" => false,
|
440
|
+
"create" => false,
|
441
|
+
"update" => false,
|
442
|
+
"delete" => false,
|
443
|
+
"export" => false,
|
444
|
+
"searchToEdit" => false
|
445
|
+
}
|
446
|
+
}
|
447
|
+
|
448
|
+
it 'should be NOT authorized' do
|
449
|
+
expect(subject.is_authorized?).to be false
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
context 'when providing collection_list_parameters' do
|
454
|
+
let(:collection_permissions) {
|
455
|
+
{
|
456
|
+
"list" => true,
|
457
|
+
"show" => false,
|
458
|
+
"create" => false,
|
459
|
+
"update" => false,
|
460
|
+
"delete" => false,
|
461
|
+
"export" => false,
|
462
|
+
"searchToEdit" => false
|
463
|
+
}
|
464
|
+
}
|
465
|
+
let(:collection_list_parameters) { { :user_id => "1", :filters => nil } }
|
466
|
+
|
467
|
+
subject {
|
468
|
+
described_class.new(
|
469
|
+
fake_ressource,
|
470
|
+
'browseEnabled',
|
471
|
+
default_rendering_id,
|
472
|
+
user_id: user_id,
|
473
|
+
collection_list_parameters: collection_list_parameters
|
474
|
+
)
|
475
|
+
}
|
476
|
+
|
477
|
+
context 'when user has the required permission' do
|
478
|
+
it 'should be authorized' do
|
479
|
+
expect(subject.is_authorized?).to be true
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
context 'when user has not the required permission' do
|
484
|
+
let(:collection_permissions) {
|
485
|
+
{
|
486
|
+
"list" => false,
|
487
|
+
"show" => false,
|
488
|
+
"create" => false,
|
489
|
+
"update" => false,
|
490
|
+
"delete" => false,
|
491
|
+
"export" => false,
|
492
|
+
"searchToEdit" => false
|
493
|
+
}
|
494
|
+
}
|
495
|
+
|
496
|
+
it 'should NOT be authorized' do
|
497
|
+
expect(subject.is_authorized?).to be false
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
context 'when scopes are defined' do
|
502
|
+
let(:scope_permissions) { { 'dynamicScopesValues' => {}, 'filter' => { 'aggregator' => 'and', 'conditions' => [condition] } }}
|
503
|
+
let(:collection_list_parameters) { { :user_id => "1", :filters => JSON.generate(condition) } }
|
504
|
+
|
505
|
+
context 'when scopes are passing validation' do
|
506
|
+
context 'when scope value is a string' do
|
507
|
+
let(:condition) { { 'field' => 'field_1', 'operator' => 'equal', 'value' => true } }
|
508
|
+
|
509
|
+
it 'should return true' do
|
510
|
+
expect(subject.is_authorized?).to be true
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
context 'when scope value is a boolean' do
|
515
|
+
let(:condition) { { 'field' => 'field_1', 'operator' => 'equal', 'value' => 'true' } }
|
516
|
+
|
517
|
+
it 'should return true' do
|
518
|
+
expect(subject.is_authorized?).to be true
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
context 'when scopes are NOT passing validation' do
|
524
|
+
let(:condition) { { 'field' => 'field_1', 'operator' => 'equal', 'value' => true } }
|
525
|
+
let(:other_condition) {
|
526
|
+
{
|
527
|
+
aggregator: 'and',
|
528
|
+
conditions: [
|
529
|
+
{ field: 'name', value: 'john', operator: 'equal' },
|
530
|
+
{ field: 'price', value: '2500', operator: 'equal' }
|
531
|
+
]
|
532
|
+
}
|
533
|
+
}
|
534
|
+
let(:collection_list_parameters) {
|
535
|
+
{
|
536
|
+
:user_id => "1",
|
537
|
+
:filters => JSON.generate(other_condition)
|
538
|
+
}
|
539
|
+
}
|
540
|
+
|
541
|
+
|
542
|
+
it 'should return false' do
|
543
|
+
expect(subject.is_authorized?).to be false
|
544
|
+
end
|
545
|
+
end
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
describe 'readEnabled permission' do
|
551
|
+
subject { described_class.new(fake_ressource, 'readEnabled', default_rendering_id, user_id: user_id) }
|
552
|
+
|
553
|
+
context 'when user has the required permission' do
|
554
|
+
it 'should be authorized' do
|
555
|
+
expect(subject.is_authorized?).to be true
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
context 'when user has not the required permission' do
|
560
|
+
let(:collection_name) { 'no_rights_collection' }
|
561
|
+
|
562
|
+
it 'should NOT be authorized' do
|
563
|
+
expect(subject.is_authorized?).to be false
|
564
|
+
end
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
describe 'addEnabled permission' do
|
569
|
+
subject { described_class.new(fake_ressource, 'addEnabled', default_rendering_id, user_id: user_id) }
|
570
|
+
|
571
|
+
context 'when user has the required permission' do
|
572
|
+
it 'should be authorized' do
|
573
|
+
expect(subject.is_authorized?).to be true
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
context 'when user has not the required permission' do
|
578
|
+
let(:collection_name) { 'no_rights_collection' }
|
579
|
+
|
580
|
+
it 'should NOT be authorized' do
|
581
|
+
expect(subject.is_authorized?).to be false
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
describe 'editEnabled permission' do
|
587
|
+
subject { described_class.new(fake_ressource, 'editEnabled', default_rendering_id, user_id: user_id) }
|
588
|
+
|
589
|
+
context 'when user has the required permission' do
|
590
|
+
it 'should be authorized' do
|
591
|
+
expect(subject.is_authorized?).to be true
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
context 'when user has not the required permission' do
|
596
|
+
let(:collection_name) { 'no_rights_collection' }
|
597
|
+
|
598
|
+
it 'should NOT be authorized' do
|
599
|
+
expect(subject.is_authorized?).to be false
|
600
|
+
end
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
describe 'deleteEnabled permission' do
|
605
|
+
subject { described_class.new(fake_ressource, 'deleteEnabled', default_rendering_id, user_id: user_id) }
|
606
|
+
|
607
|
+
context 'when user has the required permission' do
|
608
|
+
it 'should be authorized' do
|
609
|
+
expect(subject.is_authorized?).to be true
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
context 'when user has not the required permission' do
|
614
|
+
let(:collection_name) { 'no_rights_collection' }
|
615
|
+
|
616
|
+
it 'should NOT be authorized' do
|
617
|
+
expect(subject.is_authorized?).to be false
|
618
|
+
end
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
describe 'actions permission' do
|
623
|
+
let(:smart_action_request_info) { { endpoint: 'forest/actions/Test', http_method: 'POST' } }
|
624
|
+
subject {
|
625
|
+
described_class.new(
|
626
|
+
fake_ressource,
|
627
|
+
'actions',
|
628
|
+
default_rendering_id,
|
629
|
+
user_id: user_id,
|
630
|
+
smart_action_request_info: smart_action_request_info
|
631
|
+
)
|
632
|
+
}
|
633
|
+
|
634
|
+
context 'when user has the required permission' do
|
635
|
+
|
636
|
+
it 'should be authorized' do
|
637
|
+
expect(subject.is_authorized?).to be true
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
context 'when user has not the required permission' do
|
642
|
+
let(:collection_name) { 'no_rights_collection' }
|
643
|
+
|
644
|
+
it 'should NOT be authorized' do
|
645
|
+
expect(subject.is_authorized?).to be false
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
context 'when endpoint is missing from smart action parameters' do
|
650
|
+
let(:smart_action_request_info) { { http_method: 'POST' } }
|
651
|
+
|
652
|
+
it 'user should NOT be authorized' do
|
653
|
+
expect(subject.is_authorized?).to be false
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
context 'when http_method is missing from smart action parameters' do
|
658
|
+
let(:smart_action_request_info) { { endpoint: 'forest/actions/Test' } }
|
659
|
+
|
660
|
+
it 'user should NOT be authorized' do
|
661
|
+
expect(subject.is_authorized?).to be false
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
context 'when the provided endpoint is not part of the schema' do
|
666
|
+
let(:smart_action_request_info) { { endpoint: 'forest/actions/Test', http_method: 'DELETE' } }
|
667
|
+
|
668
|
+
it 'user should NOT be authorized' do
|
669
|
+
expect(subject.is_authorized?).to be false
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
context 'when the action permissions contains a list of user ids' do
|
674
|
+
context 'when user id is NOT part of the authorized users' do
|
675
|
+
let(:user_id) { 2 }
|
676
|
+
let(:smart_action_request_info) { { endpoint: 'forest/actions/TestRestricted', http_method: 'POST' } }
|
677
|
+
|
678
|
+
it 'user should NOT be authorized' do
|
679
|
+
expect(subject.is_authorized?).to be false
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
683
|
+
context 'when user id is part of the authorized users' do
|
684
|
+
let(:smart_action_request_info) { { endpoint: 'forest/actions/TestRestricted', http_method: 'POST' } }
|
685
|
+
|
686
|
+
it 'user should be authorized' do
|
687
|
+
expect(subject.is_authorized?).to be true
|
688
|
+
end
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
context 'when the action has been created with default http endpoint and method in the schema' do
|
693
|
+
let(:smart_action_request_info) { { endpoint: 'forest/actions/test-default-values', http_method: 'POST' } }
|
694
|
+
|
695
|
+
it 'user should be authorized' do
|
696
|
+
expect(subject.is_authorized?).to be true
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
context 'when the action has the same enpoint as an other' do
|
701
|
+
let(:smart_action_request_info) { { endpoint: 'forest/actions/Test', http_method: 'PUT' } }
|
702
|
+
|
703
|
+
it 'user should NOT be authorized' do
|
704
|
+
expect(subject.is_authorized?).to be false
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|
708
|
+
end
|
709
|
+
end
|
710
|
+
end
|
711
|
+
end
|