forest_liana 7.0.0.beta.2 → 7.0.0.beta.6

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/forest_liana/actions_controller.rb +20 -18
  3. data/app/controllers/forest_liana/application_controller.rb +0 -9
  4. data/app/controllers/forest_liana/associations_controller.rb +2 -2
  5. data/app/controllers/forest_liana/resources_controller.rb +16 -6
  6. data/app/controllers/forest_liana/scopes_controller.rb +20 -0
  7. data/app/controllers/forest_liana/smart_actions_controller.rb +39 -6
  8. data/app/controllers/forest_liana/stats_controller.rb +5 -5
  9. data/app/services/forest_liana/apimap_sorter.rb +1 -0
  10. data/app/services/forest_liana/filters_parser.rb +8 -4
  11. data/app/services/forest_liana/has_many_dissociator.rb +2 -2
  12. data/app/services/forest_liana/has_many_getter.rb +2 -2
  13. data/app/services/forest_liana/leaderboard_stat_getter.rb +20 -14
  14. data/app/services/forest_liana/line_stat_getter.rb +5 -3
  15. data/app/services/forest_liana/permissions_checker.rb +42 -37
  16. data/app/services/forest_liana/permissions_formatter.rb +1 -1
  17. data/app/services/forest_liana/permissions_getter.rb +3 -6
  18. data/app/services/forest_liana/pie_stat_getter.rb +6 -3
  19. data/app/services/forest_liana/resource_getter.rb +6 -3
  20. data/app/services/forest_liana/resource_updater.rb +5 -2
  21. data/app/services/forest_liana/resources_getter.rb +6 -5
  22. data/app/services/forest_liana/scope_manager.rb +102 -0
  23. data/app/services/forest_liana/search_query_builder.rb +6 -3
  24. data/app/services/forest_liana/stat_getter.rb +2 -1
  25. data/app/services/forest_liana/token.rb +1 -0
  26. data/app/services/forest_liana/utils/beta_schema_utils.rb +1 -1
  27. data/app/services/forest_liana/value_stat_getter.rb +4 -2
  28. data/config/routes.rb +3 -1
  29. data/lib/forest_liana/bootstrapper.rb +4 -2
  30. data/lib/forest_liana/version.rb +1 -1
  31. data/spec/dummy/app/controllers/forest/islands_controller.rb +5 -0
  32. data/spec/dummy/config/routes.rb +4 -0
  33. data/spec/dummy/lib/forest_liana/collections/island.rb +7 -0
  34. data/spec/lib/forest_liana/bootstrapper_spec.rb +12 -0
  35. data/spec/requests/actions_controller_spec.rb +144 -23
  36. data/spec/requests/authentications_spec.rb +2 -1
  37. data/spec/requests/resources_spec.rb +2 -0
  38. data/spec/services/forest_liana/apimap_sorter_spec.rb +6 -4
  39. data/spec/services/forest_liana/filters_parser_spec.rb +1 -1
  40. data/spec/services/forest_liana/has_many_getter_spec.rb +116 -0
  41. data/spec/services/forest_liana/line_stat_getter_spec.rb +14 -6
  42. data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +45 -71
  43. data/spec/services/forest_liana/permissions_checker_acl_enabled_spec.rb +39 -63
  44. data/spec/services/forest_liana/permissions_checker_live_queries_spec.rb +3 -3
  45. data/spec/services/forest_liana/permissions_formatter_spec.rb +11 -11
  46. data/spec/services/forest_liana/pie_stat_getter_spec.rb +114 -0
  47. data/spec/services/forest_liana/resource_updater_spec.rb +116 -0
  48. data/spec/services/forest_liana/resources_getter_spec.rb +68 -1
  49. data/spec/services/forest_liana/scope_manager_spec.rb +232 -0
  50. data/spec/services/forest_liana/value_stat_getter_spec.rb +96 -0
  51. metadata +125 -118
  52. data/app/services/forest_liana/scope_validator.rb +0 -98
  53. data/test/services/forest_liana/has_many_getter_test.rb +0 -75
  54. data/test/services/forest_liana/pie_stat_getter_test.rb +0 -29
  55. data/test/services/forest_liana/resource_updater_test.rb +0 -86
  56. data/test/services/forest_liana/scope_validator_test.rb +0 -185
  57. data/test/services/forest_liana/value_stat_getter_test.rb +0 -71
@@ -23,10 +23,12 @@ module ForestLiana
23
23
  end
24
24
 
25
25
  def perform
26
- value = get_resource().eager_load(@includes)
26
+ value = get_resource().joins(@includes)
27
27
 
28
- unless @params[:filters].blank?
29
- value = FiltersParser.new(@params[:filters], value, @params[:timezone]).apply_filters
28
+ filters = ForestLiana::ScopeManager.append_scope_for_user(@params[:filters], @user, @resource.name)
29
+
30
+ unless filters.blank?
31
+ value = FiltersParser.new(filters, @resource, @params[:timezone]).apply_filters
30
32
  end
31
33
 
32
34
  Groupdate.week_start = :monday
@@ -1,13 +1,12 @@
1
1
  module ForestLiana
2
2
  class PermissionsChecker
3
3
  @@permissions_cached = Hash.new
4
- @@scopes_cached = Hash.new
4
+ @@renderings_cached = Hash.new
5
5
  @@roles_acl_activated = false
6
- # TODO: handle cache scopes per rendering
6
+
7
7
  @@expiration_in_seconds = (ENV['FOREST_PERMISSIONS_EXPIRATION_IN_SECONDS'] || 3600).to_i
8
8
 
9
- def initialize(resource, permission_name, rendering_id, user_id: nil, smart_action_request_info: nil, collection_list_parameters: nil, query_request_info: nil)
10
-
9
+ def initialize(resource, permission_name, rendering_id, user_id: nil, smart_action_request_info: nil, collection_list_parameters: Hash.new, query_request_info: nil)
11
10
  @collection_name = resource.present? ? ForestLiana.name_for(resource) : nil
12
11
  @permission_name = permission_name
13
12
  @rendering_id = rendering_id
@@ -39,13 +38,16 @@ module ForestLiana
39
38
  permissions['data'] = ForestLiana::PermissionsFormatter.convert_to_new_format(permissions['data'], @rendering_id)
40
39
  @@permissions_cached[@rendering_id] = permissions
41
40
  end
42
- add_scopes_to_cache(permissions)
41
+
42
+ # NOTICE: Add stats permissions to the RenderingPermissions
43
+ permissions['data']['renderings'][@rendering_id]['stats'] = permissions['stats']
44
+ add_rendering_permissions_to_cache(permissions)
43
45
  end
44
46
 
45
- def add_scopes_to_cache(permissions)
47
+ def add_rendering_permissions_to_cache(permissions)
46
48
  permissions['data']['renderings'].keys.each { |rendering_id|
47
- @@scopes_cached[rendering_id] = permissions['data']['renderings'][rendering_id]
48
- @@scopes_cached[rendering_id]['last_fetch'] = Time.now
49
+ @@renderings_cached[rendering_id] = permissions['data']['renderings'][rendering_id]
50
+ @@renderings_cached[rendering_id]['last_fetch'] = Time.now
49
51
  } if permissions['data']['renderings']
50
52
  end
51
53
 
@@ -59,20 +61,17 @@ module ForestLiana
59
61
  return stat_with_parameters_allowed?
60
62
  end
61
63
 
62
-
63
-
64
64
  if permissions && permissions[@collection_name] &&
65
65
  permissions[@collection_name]['collection']
66
66
  if @permission_name === 'actions'
67
67
  return smart_action_allowed?(permissions[@collection_name]['actions'])
68
68
  else
69
69
  if @permission_name === 'browseEnabled'
70
- refresh_scope_cache if scope_cache_expired?
71
- scope_permissions = get_scope_in_permissions
72
- if scope_permissions
73
- # NOTICE: current_scope will either contains conditions filter and
74
- # dynamic user values definition, or null for collection that does not use scopes
75
- return false unless are_scopes_valid?(scope_permissions)
70
+ refresh_rendering_cache if rendering_cache_expired?
71
+
72
+ # NOTICE: In this case we need to check that that query is allowed
73
+ if @collection_list_parameters[:segmentQuery].present?
74
+ return false unless segment_query_allowed?
76
75
  end
77
76
  end
78
77
  return is_user_allowed(permissions[@collection_name]['collection'][@permission_name])
@@ -82,23 +81,27 @@ module ForestLiana
82
81
  end
83
82
  end
84
83
 
85
- def get_scope_in_permissions
86
- @@scopes_cached[@rendering_id] &&
87
- @@scopes_cached[@rendering_id][@collection_name] &&
88
- @@scopes_cached[@rendering_id][@collection_name]['scope']
84
+ def get_segments_in_permissions
85
+ @@renderings_cached[@rendering_id] &&
86
+ @@renderings_cached[@rendering_id][@collection_name] &&
87
+ @@renderings_cached[@rendering_id][@collection_name]['segments']
89
88
  end
90
89
 
91
- def scope_cache_expired?
92
- return true unless @@scopes_cached[@rendering_id] && @@scopes_cached[@rendering_id]['last_fetch']
90
+ def rendering_cache_expired?
91
+ return true unless @@renderings_cached[@rendering_id] && @@renderings_cached[@rendering_id]['last_fetch']
93
92
 
94
- elapsed_seconds = date_difference_in_seconds(Time.now, @@scopes_cached[@rendering_id]['last_fetch'])
93
+ elapsed_seconds = date_difference_in_seconds(Time.now, @@renderings_cached[@rendering_id]['last_fetch'])
95
94
  elapsed_seconds >= @@expiration_in_seconds
96
95
  end
97
96
 
98
- # This will happen only on rolesACLActivated (as scope cache will always be up to date on disabled)
99
- def refresh_scope_cache
97
+ # This will happen only on rolesACLActivated (as segments cache will always be up to date on disabled)
98
+ def refresh_rendering_cache
100
99
  permissions = ForestLiana::PermissionsGetter::get_permissions_for_rendering(@rendering_id, rendering_specific_only: true)
101
- add_scopes_to_cache(permissions)
100
+
101
+ # NOTICE: Add stats permissions to the RenderingPermissions
102
+ permissions['data']['renderings'][@rendering_id]['stats'] = permissions['stats']
103
+
104
+ add_rendering_permissions_to_cache(permissions)
102
105
  end
103
106
 
104
107
  # When acl disabled permissions are stored and retrieved by rendering
@@ -112,12 +115,12 @@ module ForestLiana
112
115
  end
113
116
 
114
117
  def get_live_query_permissions_content
115
- permissions = get_permissions
118
+ permissions = @@renderings_cached[@rendering_id]
116
119
  permissions && permissions['stats'] && permissions['stats']['queries']
117
120
  end
118
-
121
+
119
122
  def get_stat_with_parameters_content(statPermissionType)
120
- permissions = get_permissions
123
+ permissions = @@renderings_cached[@rendering_id]
121
124
  permissions && permissions['stats'] && permissions['stats'][statPermissionType]
122
125
  end
123
126
 
@@ -154,11 +157,13 @@ module ForestLiana
154
157
  is_user_allowed(smart_action_permissions['triggerEnabled'])
155
158
  end
156
159
 
157
- def are_scopes_valid?(scope_permissions)
158
- return ForestLiana::ScopeValidator.new(
159
- scope_permissions['filter'],
160
- scope_permissions['dynamicScopesValues']['users']
161
- ).is_scope_in_request?(@collection_list_parameters)
160
+ def segment_query_allowed?
161
+ segments_queries_permissions = get_segments_in_permissions
162
+
163
+ return false unless segments_queries_permissions
164
+
165
+ # NOTICE: @query_request_info matching an existing segment query
166
+ return segments_queries_permissions.include? @collection_list_parameters[:segmentQuery]
162
167
  end
163
168
 
164
169
  def live_query_allowed?
@@ -166,7 +171,7 @@ module ForestLiana
166
171
 
167
172
  return false unless live_queries_permissions
168
173
 
169
- # NOTICE: @query_request_info matching an existing live query
174
+ # NOTICE: @query_request_info matching an existing live query
170
175
  return live_queries_permissions.include? @query_request_info
171
176
  end
172
177
 
@@ -177,7 +182,7 @@ module ForestLiana
177
182
  return false unless pool_permissions
178
183
 
179
184
  # NOTICE: equivalent to Object.values in js & removes nil values
180
- array_permission_infos = @query_request_info.values.filter_map{ |x| x unless x.nil? }
185
+ array_permission_infos = @query_request_info.values.select{ |x| !x.nil? }
181
186
 
182
187
  # NOTICE: Is there any pool_permissions containing the array_permission_infos
183
188
  return pool_permissions.any? {
@@ -201,7 +206,7 @@ module ForestLiana
201
206
  # Used only for testing purpose
202
207
  def self.empty_cache
203
208
  @@permissions_cached = Hash.new
204
- @@scopes_cached = Hash.new
209
+ @@renderings_cached = Hash.new
205
210
  @@roles_acl_activated = false
206
211
  @@expiration_in_seconds = (ENV['FOREST_PERMISSIONS_EXPIRATION_IN_SECONDS'] || 3600).to_i
207
212
  end
@@ -13,7 +13,7 @@ module ForestLiana
13
13
  'actions' => convert_actions_permissions_to_new_format(permissions[collection_name]['actions'])
14
14
  }
15
15
 
16
- permissions_new_format['renderings'][rendering_id][collection_name] = { 'scope' => permissions[collection_name]['scope'] }
16
+ permissions_new_format['renderings'][rendering_id][collection_name] = { 'segments' => permissions[collection_name]['segments'] }
17
17
  }
18
18
 
19
19
  permissions_new_format
@@ -23,19 +23,16 @@ module ForestLiana
23
23
  # },
24
24
  # },
25
25
  # },
26
- # rederings => {
26
+ # renderings => {
27
27
  # {rendering_id} => {
28
28
  # {collection_id} => {
29
- # scope => {
30
- # dynamicScopesValues => {},
31
- # filter => {}
32
- # }
29
+ # segments => ['query1', 'query2']
33
30
  # }
34
31
  # }
35
32
  # }
36
33
  # }
37
34
  # With `rendering_specific_only` this returns only the permissions related data specific to the provided rendering
38
- # For now this only includes scopes
35
+ # For now this only includes scopes (but scopes are not used anymore in permissions)
39
36
  def get_permissions_for_rendering(rendering_id, rendering_specific_only: false)
40
37
  begin
41
38
  query_parameters = { 'renderingId' => rendering_id }
@@ -7,8 +7,10 @@ module ForestLiana
7
7
  timezone_offset = @params[:timezone].to_i
8
8
  resource = get_resource().eager_load(@includes)
9
9
 
10
- unless @params[:filters].blank?
11
- resource = FiltersParser.new(@params[:filters], resource, @params[:timezone]).apply_filters
10
+ filters = ForestLiana::ScopeManager.append_scope_for_user(@params[:filters], @user, @resource.name)
11
+
12
+ unless filters.blank?
13
+ resource = FiltersParser.new(filters, resource, @params[:timezone]).apply_filters
12
14
  end
13
15
 
14
16
  result = resource
@@ -53,7 +55,8 @@ module ForestLiana
53
55
  if @params[:aggregate].downcase == 'sum'
54
56
  field = @params[:aggregate_field].downcase
55
57
  else
56
- field = Rails::VERSION::MAJOR >= 5 || @includes.size > 0 ? 'id' : 'all'
58
+ # `count_id` is required only for rails v5
59
+ field = Rails::VERSION::MAJOR == 5 || @includes.size > 0 ? 'id' : 'all'
57
60
  end
58
61
  "#{@params[:aggregate].downcase}_#{field} #{order}"
59
62
  end
@@ -2,16 +2,19 @@ module ForestLiana
2
2
  class ResourceGetter < BaseGetter
3
3
  attr_accessor :record
4
4
 
5
- def initialize(resource, params)
5
+ def initialize(resource, params, forest_user)
6
6
  @resource = resource
7
7
  @params = params
8
- @collection_name = ForestLiana.name_for(@resource)
8
+ @collection_name = ForestLiana.name_for(resource)
9
+ @user = forest_user
9
10
  @collection = get_collection(@collection_name)
10
11
  compute_includes()
11
12
  end
12
13
 
13
14
  def perform
14
- @record = get_resource().eager_load(@includes).find(@params[:id])
15
+ records = get_resource().eager_load(@includes)
16
+ scoped_records = ForestLiana::ScopeManager.apply_scopes_on_records(records, @user, @collection_name, @params[:timezone])
17
+ @record = scoped_records.find(@params[:id])
15
18
  end
16
19
  end
17
20
  end
@@ -3,15 +3,18 @@ module ForestLiana
3
3
  attr_accessor :record
4
4
  attr_accessor :errors
5
5
 
6
- def initialize(resource, params)
6
+ def initialize(resource, params, forest_user)
7
7
  @resource = resource
8
8
  @params = params
9
9
  @errors = nil
10
+ @user = forest_user
10
11
  end
11
12
 
12
13
  def perform
13
14
  begin
14
- @record = @resource.find(@params[:id])
15
+ collection_name = ForestLiana.name_for(@resource)
16
+ scoped_records = ForestLiana::ScopeManager.apply_scopes_on_records(@resource, @user, collection_name, @params[:timezone])
17
+ @record = scoped_records.find(@params[:id])
15
18
 
16
19
  if has_strong_parameter
17
20
  @record.update(resource_params)
@@ -4,7 +4,7 @@ module ForestLiana
4
4
  attr_reader :includes
5
5
  attr_reader :records_count
6
6
 
7
- def initialize(resource, params)
7
+ def initialize(resource, params, forest_user)
8
8
  @resource = resource
9
9
  @params = params
10
10
  @count_needs_includes = false
@@ -14,12 +14,13 @@ module ForestLiana
14
14
  @field_names_requested = field_names_requested
15
15
  get_segment
16
16
  compute_includes
17
- @search_query_builder = SearchQueryBuilder.new(@params, @includes, @collection)
17
+ @user = forest_user
18
+ @search_query_builder = SearchQueryBuilder.new(@params, @includes, @collection, forest_user)
18
19
 
19
20
  prepare_query
20
21
  end
21
22
 
22
- def self.get_ids_from_request(params)
23
+ def self.get_ids_from_request(params, user)
23
24
  attributes = params.dig('data', 'attributes')
24
25
  has_body_attributes = attributes != nil
25
26
  is_select_all_records_query = has_body_attributes && attributes[:all_records] == true
@@ -45,11 +46,11 @@ module ForestLiana
45
46
  collection: parent_collection_name,
46
47
  id: attributes[:parent_collection_id],
47
48
  association_name: attributes[:parent_association_name],
48
- }))
49
+ }), user)
49
50
  else
50
51
  collection_name = attributes[:collection_name]
51
52
  model = ForestLiana::SchemaUtils.find_model_from_collection_name(collection_name)
52
- resources_getter = ForestLiana::ResourcesGetter.new(model, attributes)
53
+ resources_getter = ForestLiana::ResourcesGetter.new(model, attributes, user)
53
54
  end
54
55
 
55
56
  # NOTICE: build IDs list.
@@ -0,0 +1,102 @@
1
+ module ForestLiana
2
+ class ScopeManager
3
+ @@scopes_cache = Hash.new
4
+ # 5 minutes exipration cache
5
+ @@scope_cache_expiration_delta = 300
6
+
7
+ def self.apply_scopes_on_records(records, forest_user, collection_name, timezone)
8
+ scope_filters = get_scope_for_user(forest_user, collection_name, as_string: true)
9
+
10
+ return records if scope_filters.blank?
11
+
12
+ FiltersParser.new(scope_filters, records, timezone).apply_filters
13
+ end
14
+
15
+ def self.append_scope_for_user(existing_filter, user, collection_name)
16
+ scope_filter = get_scope_for_user(user, collection_name, as_string: true)
17
+ filters = [existing_filter, scope_filter].compact
18
+
19
+ case filters.length
20
+ when 0
21
+ nil
22
+ when 1
23
+ filters[0]
24
+ else
25
+ "{\"aggregator\":\"and\",\"conditions\":[#{existing_filter},#{scope_filter}]}"
26
+ end
27
+ end
28
+
29
+ def self.get_scope_for_user(user, collection_name, as_string: false)
30
+ raise 'Missing required rendering_id' unless user['rendering_id']
31
+ raise 'Missing required collection_name' unless collection_name
32
+
33
+ collection_scope = get_collection_scope(user['rendering_id'], collection_name)
34
+
35
+ return nil unless collection_scope
36
+
37
+ filters = format_dynamic_values(user['id'], collection_scope)
38
+
39
+ as_string && filters ? JSON.generate(filters) : filters
40
+ end
41
+
42
+ def self.get_collection_scope(rendering_id, collection_name)
43
+ if !@@scopes_cache[rendering_id]
44
+ # when scope cache is unset wait for the refresh
45
+ refresh_scopes_cache(rendering_id)
46
+ elsif has_cache_expired?(rendering_id)
47
+ # when cache expired refresh the scopes without waiting for it
48
+ Thread.new { refresh_scopes_cache(rendering_id) }
49
+ end
50
+
51
+ @@scopes_cache[rendering_id][:scopes][collection_name]
52
+ end
53
+
54
+ def self.has_cache_expired?(rendering_id)
55
+ rendering_scopes = @@scopes_cache[rendering_id]
56
+ return true unless rendering_scopes
57
+
58
+ second_since_last_fetch = Time.now - rendering_scopes[:fetched_at]
59
+ second_since_last_fetch >= @@scope_cache_expiration_delta
60
+ end
61
+
62
+ def self.refresh_scopes_cache(rendering_id)
63
+ scopes = fetch_scopes(rendering_id)
64
+ @@scopes_cache[rendering_id] = {
65
+ :fetched_at => Time.now,
66
+ :scopes => scopes
67
+ }
68
+ end
69
+
70
+ def self.fetch_scopes(rendering_id)
71
+ query_parameters = { 'renderingId' => rendering_id }
72
+ response = ForestLiana::ForestApiRequester.get('/liana/scopes', query: query_parameters)
73
+
74
+ if response.is_a?(Net::HTTPOK)
75
+ JSON.parse(response.body)
76
+ else
77
+ raise 'Unable to fetch scopes'
78
+ end
79
+ end
80
+
81
+ def self.format_dynamic_values(user_id, collection_scope)
82
+ filter = collection_scope.dig('scope', 'filter')
83
+ return nil unless filter
84
+
85
+ dynamic_scopes_values = collection_scope.dig('scope', 'dynamicScopesValues')
86
+
87
+ # Only goes one level deep as required for now
88
+ filter['conditions'].map do |condition|
89
+ value = condition['value']
90
+ if value.is_a?(String) && value.start_with?('$currentUser')
91
+ condition['value'] = dynamic_scopes_values.dig('users', user_id, value)
92
+ end
93
+ end
94
+
95
+ filter
96
+ end
97
+
98
+ def self.invalidate_scope_cache(rendering_id)
99
+ @@scopes_cache.delete(rendering_id)
100
+ end
101
+ end
102
+ end
@@ -4,12 +4,13 @@ module ForestLiana
4
4
 
5
5
  attr_reader :fields_searched
6
6
 
7
- def initialize(params, includes, collection)
7
+ def initialize(params, includes, collection, user)
8
8
  @params = params
9
9
  @includes = includes
10
10
  @collection = collection
11
11
  @fields_searched = []
12
12
  @search = @params[:search]
13
+ @user = user
13
14
  end
14
15
 
15
16
  def perform(resource)
@@ -18,8 +19,10 @@ module ForestLiana
18
19
  ForestLiana::QueryHelper.get_tables_associated_to_relations_name(@resource)
19
20
  @records = search_param
20
21
 
21
- unless @params[:filters].blank?
22
- @records = FiltersParser.new(@params[:filters], @records, @params[:timezone]).apply_filters
22
+ filters = ForestLiana::ScopeManager.append_scope_for_user(@params[:filters], @user, @collection.name)
23
+
24
+ unless filters.blank?
25
+ @records = FiltersParser.new(filters, @records, @params[:timezone]).apply_filters
23
26
  end
24
27
 
25
28
  if @search