forest_liana 5.1.3 → 5.2.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 +24 -2
- data/app/services/forest_liana/permissions_checker.rb +13 -1
- data/app/services/forest_liana/scope_validator.rb +97 -0
- data/lib/forest_liana/version.rb +1 -1
- data/test/services/forest_liana/scope_validator_test.rb +185 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 64ca1f513502df98ecedaeeacddf85af31720c10aa6c60b4444ed83affd57536
|
4
|
+
data.tar.gz: 4f7422a68c827a5439796946b43c7d687dc59d9c98a104b6540a025ceb88d520
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5bf573c94f3f2e25403c752f9808ea9b4a6fcd02f886423bdc79b049605063edee39fbaf0ca1b4f74b7423c94936c5fcd831c1165196a3612c812e51c362a29a
|
7
|
+
data.tar.gz: 0ad2cfe651f34a16538c7f753eac53d1e54090ffe8b2d3b9ecf31e2638adb564df2c96150b5166403dd391d2b56fca3e4fd75ee34884e91937fefec06a33c416
|
@@ -22,7 +22,13 @@ module ForestLiana
|
|
22
22
|
checker = ForestLiana::PermissionsChecker.new(@resource, 'searchToEdit', @rendering_id)
|
23
23
|
return head :forbidden unless checker.is_authorized?
|
24
24
|
else
|
25
|
-
checker = ForestLiana::PermissionsChecker.new(
|
25
|
+
checker = ForestLiana::PermissionsChecker.new(
|
26
|
+
@resource,
|
27
|
+
'list',
|
28
|
+
@rendering_id,
|
29
|
+
nil,
|
30
|
+
get_collection_list_permission_info(forest_user, request)
|
31
|
+
)
|
26
32
|
return head :forbidden unless checker.is_authorized?
|
27
33
|
end
|
28
34
|
|
@@ -51,7 +57,13 @@ module ForestLiana
|
|
51
57
|
|
52
58
|
def count
|
53
59
|
begin
|
54
|
-
checker = ForestLiana::PermissionsChecker.new(
|
60
|
+
checker = ForestLiana::PermissionsChecker.new(
|
61
|
+
@resource,
|
62
|
+
'list',
|
63
|
+
@rendering_id,
|
64
|
+
nil,
|
65
|
+
get_collection_list_permission_info(forest_user, request)
|
66
|
+
)
|
55
67
|
return head :forbidden unless checker.is_authorized?
|
56
68
|
|
57
69
|
getter = ForestLiana::ResourcesGetter.new(@resource, params)
|
@@ -232,5 +244,15 @@ module ForestLiana
|
|
232
244
|
collection_name = ForestLiana.name_for(@resource)
|
233
245
|
@collection ||= ForestLiana.apimap.find { |collection| collection.name.to_s == collection_name }
|
234
246
|
end
|
247
|
+
|
248
|
+
# NOTICE: Return a formatted object containing the request condition filters and
|
249
|
+
# the user id used by the scope validator class to validate if scope is
|
250
|
+
# in request
|
251
|
+
def get_collection_list_permission_info(user, collection_list_request)
|
252
|
+
{
|
253
|
+
user_id: user['id'],
|
254
|
+
filters: collection_list_request[:filters],
|
255
|
+
}
|
256
|
+
end
|
235
257
|
end
|
236
258
|
end
|
@@ -3,11 +3,12 @@ module ForestLiana
|
|
3
3
|
@@permissions_per_rendering = Hash.new
|
4
4
|
@@expiration_in_seconds = (ENV['FOREST_PERMISSIONS_EXPIRATION_IN_SECONDS'] || 3600).to_i
|
5
5
|
|
6
|
-
def initialize(resource, permission_name, rendering_id, smart_action_parameters = nil)
|
6
|
+
def initialize(resource, permission_name, rendering_id, smart_action_parameters = nil, collection_list_parameters = nil)
|
7
7
|
@collection_name = ForestLiana.name_for(resource)
|
8
8
|
@permission_name = permission_name
|
9
9
|
@rendering_id = rendering_id
|
10
10
|
@smart_action_parameters = smart_action_parameters
|
11
|
+
@collection_list_parameters = collection_list_parameters
|
11
12
|
end
|
12
13
|
|
13
14
|
def is_authorized?
|
@@ -46,12 +47,23 @@ module ForestLiana
|
|
46
47
|
return @allowed && (@users.nil?|| @users.include?(@user_id.to_i));
|
47
48
|
end
|
48
49
|
|
50
|
+
def collection_list_allowed?(scope_permissions)
|
51
|
+
return ForestLiana::ScopeValidator.new(
|
52
|
+
scope_permissions['filter'],
|
53
|
+
scope_permissions['dynamicScopesValues']['users']
|
54
|
+
).is_scope_in_request?(@collection_list_parameters)
|
55
|
+
end
|
56
|
+
|
49
57
|
def is_allowed?
|
50
58
|
permissions = get_permissions
|
51
59
|
if permissions && permissions[@collection_name] &&
|
52
60
|
permissions[@collection_name]['collection']
|
53
61
|
if @permission_name === 'actions'
|
54
62
|
return smart_action_allowed?(permissions[@collection_name]['actions'])
|
63
|
+
# NOTICE: Permissions[@collection_name]['scope'] will either contains conditions filter and
|
64
|
+
# dynamic user values definition, or null for collection that does not use scopes
|
65
|
+
elsif @permission_name === 'list' and permissions[@collection_name]['scope']
|
66
|
+
return collection_list_allowed?(permissions[@collection_name]['scope'])
|
55
67
|
else
|
56
68
|
return permissions[@collection_name]['collection'][@permission_name]
|
57
69
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
class ScopeValidator
|
3
|
+
def initialize(scope_permissions, users_variable_values)
|
4
|
+
@scope_filters = scope_permissions
|
5
|
+
@users_variable_values = users_variable_values
|
6
|
+
end
|
7
|
+
|
8
|
+
def is_scope_in_request?(scope_request)
|
9
|
+
begin
|
10
|
+
filters = JSON.parse(scope_request[:filters])
|
11
|
+
rescue JSON::ParserError
|
12
|
+
raise ForestLiana::Errors::HTTP422Error.new('Invalid filters JSON format')
|
13
|
+
end
|
14
|
+
@computed_scope = compute_condition_filters_from_scope(scope_request[:user_id])
|
15
|
+
|
16
|
+
# NOTICE: Perfom a travel in the request condition filters tree to find the scope
|
17
|
+
tagged_scope_filters = get_scope_found_in_request(filters)
|
18
|
+
|
19
|
+
# NOTICE: Permission system always send an aggregator even if there is only one condition
|
20
|
+
# In that case, if the condition is valid, then request was not edited
|
21
|
+
return !tagged_scope_filters.nil? if @scope_filters['conditions'].length == 1
|
22
|
+
|
23
|
+
# NOTICE: If there is more than one condition, do a final validation on the condition filters
|
24
|
+
return tagged_scope_filters != nil &&
|
25
|
+
tagged_scope_filters[:aggregator] == @scope_filters['aggregator'] &&
|
26
|
+
tagged_scope_filters[:conditions] &&
|
27
|
+
tagged_scope_filters[:conditions].length == @scope_filters['conditions'].length
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def compute_condition_filters_from_scope(user_id)
|
33
|
+
computed_condition_filters = @scope_filters.clone
|
34
|
+
computed_condition_filters['conditions'].each do |condition|
|
35
|
+
if condition.include?('value') &&
|
36
|
+
!condition['value'].nil? &&
|
37
|
+
condition['value'].start_with?('$') &&
|
38
|
+
@users_variable_values.include?(user_id)
|
39
|
+
condition['value'] = @users_variable_values[user_id][condition['value']]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
return computed_condition_filters
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_scope_found_in_request(filters)
|
46
|
+
return nil unless filters
|
47
|
+
return search_scope_aggregation(filters)
|
48
|
+
end
|
49
|
+
|
50
|
+
def search_scope_aggregation(node)
|
51
|
+
ensure_valid_aggregation(node)
|
52
|
+
|
53
|
+
return is_scope_condition?(node) unless node['aggregator']
|
54
|
+
|
55
|
+
# NOTICE: Remove conditions that are not from the scope
|
56
|
+
filtered_conditions = node['conditions'].map { |condition|
|
57
|
+
search_scope_aggregation(condition)
|
58
|
+
}.select { |condition|
|
59
|
+
condition
|
60
|
+
}
|
61
|
+
|
62
|
+
# NOTICE: If there is only one condition filter left and its current aggregator is
|
63
|
+
# an "and", this condition filter is the searched scope
|
64
|
+
if (filtered_conditions.length == 1 &&
|
65
|
+
filtered_conditions.first.is_a?(Hash) &&
|
66
|
+
filtered_conditions.first.include?(:aggregator) &&
|
67
|
+
node['aggregator'] == 'and')
|
68
|
+
return filtered_conditions.first
|
69
|
+
end
|
70
|
+
|
71
|
+
# NOTICE: Otherwise, validate if the current node is the scope and return nil
|
72
|
+
# if it's not
|
73
|
+
return (filtered_conditions.length == @scope_filters['conditions'].length &&
|
74
|
+
node['aggregator'] == @scope_filters['aggregator']) ?
|
75
|
+
{ aggregator: node['aggregator'], conditions: filtered_conditions } :
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def is_scope_condition?(condition)
|
80
|
+
ensure_valid_condition(condition)
|
81
|
+
return @computed_scope['conditions'].include?(condition)
|
82
|
+
end
|
83
|
+
|
84
|
+
def ensure_valid_aggregation(node)
|
85
|
+
raise ForestLiana::Errors::HTTP422Error.new('Filters cannot be a raw value') unless node.is_a?(Hash)
|
86
|
+
raise_empty_condition_in_filter_error if node.empty?
|
87
|
+
end
|
88
|
+
|
89
|
+
def ensure_valid_condition(condition)
|
90
|
+
raise_empty_condition_in_filter_error if condition.empty?
|
91
|
+
raise ForestLiana::Errors::HTTP422Error.new('Condition cannot be a raw value') unless condition.is_a?(Hash)
|
92
|
+
unless condition['field'].is_a?(String) and condition['operator'].is_a?(String)
|
93
|
+
raise ForestLiana::Errors::HTTP422Error.new('Invalid condition format')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/forest_liana/version.rb
CHANGED
@@ -0,0 +1,185 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
class ScopeValidatorTest < ActiveSupport::TestCase
|
3
|
+
test 'Request with aggregated condition filters should be allowed if it matches the scope exactly' do
|
4
|
+
scope_validator = ForestLiana::ScopeValidator.new({
|
5
|
+
'aggregator' => 'and',
|
6
|
+
'conditions' => [
|
7
|
+
{ 'field' => 'name', 'value' => 'john', 'operator' => 'equal' },
|
8
|
+
{ 'field' => 'price', 'value' => '2500', 'operator' => 'equal' }
|
9
|
+
]
|
10
|
+
}, [])
|
11
|
+
|
12
|
+
allowed = scope_validator.is_scope_in_request?({
|
13
|
+
user_id: '1',
|
14
|
+
filters: JSON.generate({
|
15
|
+
aggregator: 'and',
|
16
|
+
conditions: [
|
17
|
+
{ field: 'name', value: 'john', operator: 'equal' },
|
18
|
+
{ field: 'price', value: '2500', operator: 'equal' }
|
19
|
+
]
|
20
|
+
})
|
21
|
+
})
|
22
|
+
assert allowed == true
|
23
|
+
end
|
24
|
+
|
25
|
+
test 'Request with simple condition filter should be allowed if it matches the scope exactly' do
|
26
|
+
scope_validator = ForestLiana::ScopeValidator.new({
|
27
|
+
'aggregator' => 'and',
|
28
|
+
'conditions' => [
|
29
|
+
{ 'field' => 'field', 'value' => 'value', 'operator' => 'equal' }
|
30
|
+
]
|
31
|
+
}, [])
|
32
|
+
allowed = scope_validator.is_scope_in_request?({
|
33
|
+
user_id: '1',
|
34
|
+
filters: JSON.generate({
|
35
|
+
field: 'field', value: 'value', operator: 'equal'
|
36
|
+
})
|
37
|
+
})
|
38
|
+
assert allowed == true
|
39
|
+
end
|
40
|
+
|
41
|
+
test 'Request with multiples condition filters should be allowed if it contains the scope ' do
|
42
|
+
scope_validator = ForestLiana::ScopeValidator.new({
|
43
|
+
'aggregator' => 'and',
|
44
|
+
'conditions' => [
|
45
|
+
{ 'field' => 'name', 'value' => 'doe', 'operator' => 'equal' }
|
46
|
+
]
|
47
|
+
}, []
|
48
|
+
)
|
49
|
+
|
50
|
+
allowed = scope_validator.is_scope_in_request?({
|
51
|
+
user_id: '1',
|
52
|
+
filters: JSON.generate({
|
53
|
+
aggregator: 'and',
|
54
|
+
conditions: [
|
55
|
+
{ field: 'name', value: 'doe', operator: 'equal' },
|
56
|
+
{ field: 'field2', value: 'value2', operator: 'equal' }
|
57
|
+
]
|
58
|
+
})
|
59
|
+
})
|
60
|
+
assert allowed == true
|
61
|
+
end
|
62
|
+
|
63
|
+
test 'Request with dynamic user values should be allowed if it matches the scope exactly' do
|
64
|
+
scope_validator = ForestLiana::ScopeValidator.new({
|
65
|
+
'aggregator' => 'and',
|
66
|
+
'conditions' => [
|
67
|
+
{ 'field' => 'name', 'value' => '$currentUser.lastname', 'operator' => 'equal' }
|
68
|
+
],
|
69
|
+
}, {
|
70
|
+
'1' => { '$currentUser.lastname' => 'john' }
|
71
|
+
})
|
72
|
+
|
73
|
+
allowed = scope_validator.is_scope_in_request?({
|
74
|
+
user_id: '1',
|
75
|
+
filters: JSON.generate({
|
76
|
+
'field' => 'name', 'value' => 'john', 'operator' => 'equal'
|
77
|
+
})
|
78
|
+
})
|
79
|
+
assert allowed == true
|
80
|
+
end
|
81
|
+
|
82
|
+
test 'Request with multiples aggregation and dynamic values should be allowed if it contains the scope' do
|
83
|
+
scope_validator = ForestLiana::ScopeValidator.new({
|
84
|
+
'aggregator' => 'or',
|
85
|
+
'conditions' => [
|
86
|
+
{ 'field' => 'price', 'value' => '2500', 'operator' => 'equal' },
|
87
|
+
{ 'field' => 'name', 'value' => '$currentUser.lastname', 'operator' => 'equal' }
|
88
|
+
]
|
89
|
+
}, {
|
90
|
+
'1' => { '$currentUser.lastname' => 'john' }
|
91
|
+
})
|
92
|
+
|
93
|
+
allowed = scope_validator.is_scope_in_request?({
|
94
|
+
user_id: '1',
|
95
|
+
filters: JSON.generate({
|
96
|
+
aggregator: 'and',
|
97
|
+
conditions: [
|
98
|
+
{ field: 'field', value: 'value', operator: 'equal' },
|
99
|
+
{
|
100
|
+
aggregator: 'or',
|
101
|
+
conditions: [
|
102
|
+
{ field: 'price', value: '2500', operator: 'equal' },
|
103
|
+
{ field: 'name', value: 'john', operator: 'equal' }
|
104
|
+
]
|
105
|
+
}
|
106
|
+
]
|
107
|
+
})
|
108
|
+
})
|
109
|
+
assert allowed == true
|
110
|
+
end
|
111
|
+
|
112
|
+
test 'Request that does not match the expect scope should not be allowed' do
|
113
|
+
scope_validator = ForestLiana::ScopeValidator.new({
|
114
|
+
'aggregator' => 'and',
|
115
|
+
'conditions' => [
|
116
|
+
{ 'field' => 'name', 'value' => 'john', 'operator' => 'equal' },
|
117
|
+
{ 'field' => 'price', 'value' => '2500', 'operator' => 'equal' }
|
118
|
+
]
|
119
|
+
}, [])
|
120
|
+
|
121
|
+
allowed = scope_validator.is_scope_in_request?({
|
122
|
+
user_id: '1',
|
123
|
+
filters: JSON.generate({
|
124
|
+
aggregator: 'and',
|
125
|
+
conditions: [
|
126
|
+
{ field: 'name', value: 'definitely_not_john', operator: 'equal' },
|
127
|
+
{ field: 'price', value: '0', operator: 'equal' }
|
128
|
+
]
|
129
|
+
})
|
130
|
+
})
|
131
|
+
assert allowed == false
|
132
|
+
end
|
133
|
+
|
134
|
+
test 'Request that are missing part of the scope should not be allowed' do
|
135
|
+
scope_validator = ForestLiana::ScopeValidator.new({
|
136
|
+
'aggregator' => 'and',
|
137
|
+
'conditions' => [
|
138
|
+
{ 'field' => 'name', 'value' => 'john', 'operator' => 'equal' },
|
139
|
+
{ 'field' => 'price', 'value' => '2500', 'operator' => 'equal' }
|
140
|
+
]
|
141
|
+
}, [])
|
142
|
+
|
143
|
+
allowed = scope_validator.is_scope_in_request?({
|
144
|
+
user_id: '1',
|
145
|
+
filters: JSON.generate({
|
146
|
+
aggregator: 'and',
|
147
|
+
conditions: [
|
148
|
+
{ field: 'name', value: 'john', operator: 'equal' },
|
149
|
+
]
|
150
|
+
})
|
151
|
+
})
|
152
|
+
assert allowed == false
|
153
|
+
end
|
154
|
+
|
155
|
+
test 'Request that does not have a top aggregator being "and" should not be allowed' do
|
156
|
+
scope_validator = ForestLiana::ScopeValidator.new({
|
157
|
+
'aggregator' => 'and',
|
158
|
+
'conditions' => [
|
159
|
+
{ 'field' => 'price', 'value' => '2500', 'operator' => 'equal' },
|
160
|
+
{ 'field' => 'name', 'value' => '$currentUser.lastname', 'operator' => 'equal' }
|
161
|
+
]
|
162
|
+
}, {
|
163
|
+
'1' => { '$currentUser.lastname' => 'john' }
|
164
|
+
})
|
165
|
+
|
166
|
+
allowed = scope_validator.is_scope_in_request?({
|
167
|
+
user_id: '1',
|
168
|
+
filters: JSON.generate({
|
169
|
+
aggregator: 'or',
|
170
|
+
conditions: [
|
171
|
+
{ field: 'field', value: 'value', operator: 'equal' },
|
172
|
+
{
|
173
|
+
aggregator: 'and',
|
174
|
+
conditions: [
|
175
|
+
{ field: 'price', value: '2500', operator: 'equal' },
|
176
|
+
{ field: 'name', value: 'john', operator: 'equal' }
|
177
|
+
]
|
178
|
+
}
|
179
|
+
]
|
180
|
+
})
|
181
|
+
})
|
182
|
+
assert allowed == false
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forest_liana
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sandro Munda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -262,6 +262,7 @@ files:
|
|
262
262
|
- app/services/forest_liana/resources_getter.rb
|
263
263
|
- app/services/forest_liana/schema_adapter.rb
|
264
264
|
- app/services/forest_liana/schema_utils.rb
|
265
|
+
- app/services/forest_liana/scope_validator.rb
|
265
266
|
- app/services/forest_liana/search_query_builder.rb
|
266
267
|
- app/services/forest_liana/stat_getter.rb
|
267
268
|
- app/services/forest_liana/stripe_base_getter.rb
|
@@ -432,6 +433,7 @@ files:
|
|
432
433
|
- test/services/forest_liana/resource_updater_test.rb
|
433
434
|
- test/services/forest_liana/resources_getter_test.rb
|
434
435
|
- test/services/forest_liana/schema_adapter_test.rb
|
436
|
+
- test/services/forest_liana/scope_validator_test.rb
|
435
437
|
- test/services/forest_liana/value_stat_getter_test.rb
|
436
438
|
- test/test_helper.rb
|
437
439
|
homepage: https://github.com/ForestAdmin/forest-rails
|
@@ -469,6 +471,7 @@ test_files:
|
|
469
471
|
- test/fixtures/has_many_field.yml
|
470
472
|
- test/fixtures/string_field.yml
|
471
473
|
- test/services/forest_liana/resources_getter_test.rb
|
474
|
+
- test/services/forest_liana/scope_validator_test.rb
|
472
475
|
- test/services/forest_liana/schema_adapter_test.rb
|
473
476
|
- test/services/forest_liana/has_many_getter_test.rb
|
474
477
|
- test/services/forest_liana/value_stat_getter_test.rb
|