forest_liana 5.1.3 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 403847f23770b011b6551b87228a5d1f12cff04d9fa96b2389137279517ee48c
4
- data.tar.gz: 2900ede987dfbff932097a05f367e1247a37ac2a00be6e611f6ee14b730c15c2
3
+ metadata.gz: 64ca1f513502df98ecedaeeacddf85af31720c10aa6c60b4444ed83affd57536
4
+ data.tar.gz: 4f7422a68c827a5439796946b43c7d687dc59d9c98a104b6540a025ceb88d520
5
5
  SHA512:
6
- metadata.gz: c5ee8dd97d27c5c78f6b9146cf98f41d1dbc6f72c4e4e70d662fae4d4b1f3b6d45f4339f6a9f43d74d84d193fb9001bce8662f5f6c2846b5f49ccbdec7cc8a22
7
- data.tar.gz: 423aea81ffed1126246a21ba1205a514d45b7fd5f924cd041be01f94b003099c1e6079af83660e6fd405f9d2e8481634677d772ac53eaf7ae793a15f5a4776aa
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(@resource, 'list', @rendering_id)
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(@resource, 'list', @rendering_id)
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
@@ -1,3 +1,3 @@
1
1
  module ForestLiana
2
- VERSION = "5.1.3"
2
+ VERSION = "5.2.0"
3
3
  end
@@ -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.1.3
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-05-19 00:00:00.000000000 Z
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