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 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