forest_liana 7.8.0 → 8.0.0.beta.2

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/forest_liana/actions_controller.rb +5 -3
  3. data/app/controllers/forest_liana/application_controller.rb +15 -0
  4. data/app/controllers/forest_liana/resources_controller.rb +31 -57
  5. data/app/controllers/forest_liana/smart_actions_controller.rb +44 -58
  6. data/app/controllers/forest_liana/stats_controller.rb +14 -58
  7. data/app/services/forest_liana/ability/exceptions/access_denied.rb +16 -0
  8. data/app/services/forest_liana/ability/exceptions/action_condition_error.rb +16 -0
  9. data/app/services/forest_liana/ability/exceptions/require_approval.rb +18 -0
  10. data/app/services/forest_liana/ability/exceptions/trigger_forbidden.rb +16 -0
  11. data/app/services/forest_liana/ability/fetch.rb +23 -0
  12. data/app/services/forest_liana/ability/permission/request_permission.rb +19 -0
  13. data/app/services/forest_liana/ability/permission/smart_action_checker.rb +71 -0
  14. data/app/services/forest_liana/ability/permission.rb +148 -0
  15. data/app/services/forest_liana/ability.rb +24 -0
  16. data/app/services/forest_liana/filters_parser.rb +7 -7
  17. data/app/services/forest_liana/leaderboard_stat_getter.rb +7 -7
  18. data/app/services/forest_liana/line_stat_getter.rb +8 -8
  19. data/app/services/forest_liana/pie_stat_getter.rb +17 -17
  20. data/app/services/forest_liana/stat_getter.rb +1 -2
  21. data/app/services/forest_liana/value_stat_getter.rb +7 -7
  22. data/lib/forest_liana/bootstrapper.rb +1 -1
  23. data/lib/forest_liana/version.rb +1 -1
  24. data/spec/dummy/lib/forest_liana/collections/island.rb +1 -1
  25. data/spec/requests/actions_controller_spec.rb +3 -4
  26. data/spec/requests/count_spec.rb +5 -9
  27. data/spec/requests/resources_spec.rb +55 -11
  28. data/spec/requests/stats_spec.rb +103 -42
  29. data/spec/services/forest_liana/ability/ability_spec.rb +48 -0
  30. data/spec/services/forest_liana/ability/permission/smart_action_checker_spec.rb +357 -0
  31. data/spec/services/forest_liana/ability/permission_spec.rb +332 -0
  32. data/spec/services/forest_liana/filters_parser_spec.rb +0 -12
  33. data/spec/services/forest_liana/line_stat_getter_spec.rb +9 -9
  34. data/spec/services/forest_liana/pie_stat_getter_spec.rb +7 -7
  35. data/spec/services/forest_liana/value_stat_getter_spec.rb +11 -11
  36. data/spec/spec_helper.rb +1 -0
  37. metadata +33 -17
  38. data/app/services/forest_liana/permissions_checker.rb +0 -223
  39. data/app/services/forest_liana/permissions_formatter.rb +0 -52
  40. data/app/services/forest_liana/permissions_getter.rb +0 -59
  41. data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +0 -713
  42. data/spec/services/forest_liana/permissions_checker_acl_enabled_spec.rb +0 -845
  43. data/spec/services/forest_liana/permissions_checker_live_queries_spec.rb +0 -175
  44. data/spec/services/forest_liana/permissions_formatter_spec.rb +0 -222
  45. data/spec/services/forest_liana/permissions_getter_spec.rb +0 -83
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82f94463ae1662d3d4eeac62d1b68b5c3a6895adfce97c87e99eb7312d8b0966
4
- data.tar.gz: fa9f1794cde3218d3c7aa27231549493203a49876957c170fb26ee7c1b4baf73
3
+ metadata.gz: 8c3bc9feb8975c2d4bb725c5badda68b4d880fcc3a2553cc2d1d6164f5e56366
4
+ data.tar.gz: b1f6e70a0723450674b53944f933036fea21b4a9b6cceab806cf32cd7654896a
5
5
  SHA512:
6
- metadata.gz: 9f30218cb8f5df0e921c754fa1d0b08f79194da46ebe7dac74bffb5343fdf8dd0797ef2542bd41d4bd240aff04821ac0bdd6bdf307375ef3c9a8d978ef07d6f9
7
- data.tar.gz: 7192187737dfb2a67f724bb5c444072f707b3ed02f8c69dc216f4504e2e249ffebe683dff9719f405dca9960f4a44b196e1bb1ccbe021cd62bec4cc36b687621
6
+ metadata.gz: 42a2ae2c6800ea8cf501ee3896554852cd677003d2aea5353fbc2d74d5dee34ee55d02079f8a9a31949af19d7a6ecc8c97e8be3a15a9844aa2ae11ff2fb75e73
7
+ data.tar.gz: 2a97e185ccde89910027740ea833a8e3373f4cb2466a16c2023554534be4ddc3c2419d40015be0edbecb6283a304d4ef4b80472fcea98b99649a122b28fb9c2e
@@ -2,12 +2,14 @@ module ForestLiana
2
2
  class ActionsController < ApplicationController
3
3
 
4
4
  def get_smart_action_hook_request
5
- begin
5
+ if params[:data] && params[:data][:attributes] && params[:data][:attributes][:collection_name]
6
6
  params[:data][:attributes]
7
- rescue => error
7
+ else
8
+ error = 'parameters data attributes missing'
8
9
  FOREST_REPORTER.report error
9
10
  FOREST_LOGGER.error "Smart Action hook request error: #{error}"
10
- {}
11
+
12
+ raise ForestLiana::Errors::HTTP422Error.new("Error in smart action load hook: cannot retrieve action from collection")
11
13
  end
12
14
  end
13
15
 
@@ -3,6 +3,9 @@ require 'csv'
3
3
 
4
4
  module ForestLiana
5
5
  class ApplicationController < ForestLiana::BaseController
6
+ rescue_from ForestLiana::Ability::Exceptions::AccessDenied, with: :render_error
7
+ rescue_from ForestLiana::Errors::HTTP422Error, with: :render_error
8
+
6
9
  def self.papertrail?
7
10
  Object.const_get('PaperTrail::Version').is_a?(Class) rescue false
8
11
  end
@@ -96,6 +99,18 @@ module ForestLiana
96
99
 
97
100
  private
98
101
 
102
+ def render_error(exception)
103
+ errors = {
104
+ status: exception.error_code,
105
+ detail: exception.message,
106
+ }
107
+
108
+ errors['name'] = exception.name if exception.try(:name)
109
+ errors['data'] = exception.data if exception.try(:data)
110
+
111
+ render json: { errors: [errors] }, status: exception.status
112
+ end
113
+
99
114
  def force_utf8_encoding(json)
100
115
  if json['data'].class == Array
101
116
  # NOTICE: Collection of records case
@@ -1,5 +1,6 @@
1
1
  module ForestLiana
2
2
  class ResourcesController < ForestLiana::ApplicationController
3
+ include ForestLiana::Ability
3
4
  begin
4
5
  prepend ResourcesExtensions
5
6
  rescue NameError
@@ -14,24 +15,11 @@ module ForestLiana
14
15
  end
15
16
 
16
17
  def index
18
+ action = request.format == 'csv' ? 'export' : 'browse'
19
+ forest_authorize!(action, forest_user, @resource)
17
20
  begin
18
- if request.format == 'csv'
19
- checker = ForestLiana::PermissionsChecker.new(@resource, 'exportEnabled', @rendering_id, user: forest_user)
20
- return head :forbidden unless checker.is_authorized?
21
- else
22
- checker = ForestLiana::PermissionsChecker.new(
23
- @resource,
24
- 'browseEnabled',
25
- @rendering_id,
26
- user: forest_user,
27
- collection_list_parameters: get_collection_list_permission_info(forest_user, request)
28
- )
29
- return head :forbidden unless checker.is_authorized?
30
- end
31
-
32
21
  getter = ForestLiana::ResourcesGetter.new(@resource, params, forest_user)
33
22
  getter.perform
34
-
35
23
  respond_to do |format|
36
24
  format.json { render_jsonapi(getter) }
37
25
  format.csv { render_csv(getter, @resource) }
@@ -55,16 +43,8 @@ module ForestLiana
55
43
 
56
44
  def count
57
45
  find_resource
46
+ forest_authorize!('browse', forest_user, @resource)
58
47
  begin
59
- checker = ForestLiana::PermissionsChecker.new(
60
- @resource,
61
- 'browseEnabled',
62
- @rendering_id,
63
- user: forest_user,
64
- collection_list_parameters: get_collection_list_permission_info(forest_user, request)
65
- )
66
- return head :forbidden unless checker.is_authorized?
67
-
68
48
  getter = ForestLiana::ResourcesGetter.new(@resource, params, forest_user)
69
49
  getter.count
70
50
 
@@ -88,10 +68,8 @@ module ForestLiana
88
68
  end
89
69
 
90
70
  def show
71
+ forest_authorize!('read', forest_user, @resource)
91
72
  begin
92
- checker = ForestLiana::PermissionsChecker.new(@resource, 'readEnabled', @rendering_id, user: forest_user)
93
- return head :forbidden unless checker.is_authorized?
94
-
95
73
  getter = ForestLiana::ResourceGetter.new(@resource, params, forest_user)
96
74
  getter.perform
97
75
 
@@ -106,10 +84,8 @@ module ForestLiana
106
84
  end
107
85
 
108
86
  def create
87
+ forest_authorize!('add', forest_user, @resource)
109
88
  begin
110
- checker = ForestLiana::PermissionsChecker.new(@resource, 'addEnabled', @rendering_id, user: forest_user)
111
- return head :forbidden unless checker.is_authorized?
112
-
113
89
  creator = ForestLiana::ResourceCreator.new(@resource, params)
114
90
  creator.perform
115
91
 
@@ -130,10 +106,8 @@ module ForestLiana
130
106
  end
131
107
 
132
108
  def update
109
+ forest_authorize!('edit', forest_user, @resource)
133
110
  begin
134
- checker = ForestLiana::PermissionsChecker.new(@resource, 'editEnabled', @rendering_id, user: forest_user)
135
- return head :forbidden unless checker.is_authorized?
136
-
137
111
  updater = ForestLiana::ResourceUpdater.new(@resource, params, forest_user)
138
112
  updater.perform
139
113
 
@@ -154,37 +128,37 @@ module ForestLiana
154
128
  end
155
129
 
156
130
  def destroy
157
- checker = ForestLiana::PermissionsChecker.new(@resource, 'deleteEnabled', @rendering_id, user: forest_user)
158
- return head :forbidden unless checker.is_authorized?
131
+ forest_authorize!('delete', forest_user, @resource)
132
+ begin
133
+ collection_name = ForestLiana.name_for(@resource)
134
+ scoped_records = ForestLiana::ScopeManager.apply_scopes_on_records(@resource, forest_user, collection_name, params[:timezone])
159
135
 
160
- collection_name = ForestLiana.name_for(@resource)
161
- scoped_records = ForestLiana::ScopeManager.apply_scopes_on_records(@resource, forest_user, collection_name, params[:timezone])
162
-
163
- unless scoped_records.exists?(params[:id])
164
- return render serializer: nil, json: { status: 404 }, status: :not_found
165
- end
136
+ unless scoped_records.exists?(params[:id])
137
+ return render serializer: nil, json: { status: 404 }, status: :not_found
138
+ end
166
139
 
167
- scoped_records.destroy(params[:id])
140
+ scoped_records.destroy(params[:id])
168
141
 
169
- head :no_content
170
- rescue => error
171
- FOREST_REPORTER.report error
172
- FOREST_LOGGER.error "Record Destroy error: #{error}\n#{format_stacktrace(error)}"
173
- internal_server_error
142
+ head :no_content
143
+ rescue => error
144
+ FOREST_REPORTER.report error
145
+ FOREST_LOGGER.error "Record Destroy error: #{error}\n#{format_stacktrace(error)}"
146
+ internal_server_error
147
+ end
174
148
  end
175
149
 
176
150
  def destroy_bulk
177
- checker = ForestLiana::PermissionsChecker.new(@resource, 'deleteEnabled', @rendering_id, user: forest_user)
178
- return head :forbidden unless checker.is_authorized?
179
-
180
- ids = ForestLiana::ResourcesGetter.get_ids_from_request(params, forest_user)
181
- @resource.destroy(ids) if ids&.any?
151
+ forest_authorize!('delete', forest_user, @resource)
152
+ begin
153
+ ids = ForestLiana::ResourcesGetter.get_ids_from_request(params, forest_user)
154
+ @resource.destroy(ids) if ids&.any?
182
155
 
183
- head :no_content
184
- rescue => error
185
- FOREST_REPORTER.report error
186
- FOREST_LOGGER.error "Records Destroy error: #{error}\n#{format_stacktrace(error)}"
187
- internal_server_error
156
+ head :no_content
157
+ rescue => error
158
+ FOREST_REPORTER.report error
159
+ FOREST_LOGGER.error "Records Destroy error: #{error}\n#{format_stacktrace(error)}"
160
+ internal_server_error
161
+ end
188
162
  end
189
163
 
190
164
  private
@@ -1,5 +1,9 @@
1
1
  module ForestLiana
2
2
  class SmartActionsController < ForestLiana::ApplicationController
3
+ rescue_from ForestLiana::Ability::Exceptions::TriggerForbidden, with: :render_error
4
+ rescue_from ForestLiana::Ability::Exceptions::RequireApproval, with: :render_error
5
+ rescue_from ForestLiana::Ability::Exceptions::ActionConditionError, with: :render_error
6
+ include ForestLiana::Ability
3
7
  if Rails::VERSION::MAJOR < 4
4
8
  before_filter :smart_action_pre_perform_checks
5
9
  else
@@ -8,9 +12,17 @@ module ForestLiana
8
12
 
9
13
  private
10
14
 
15
+ def smart_action_pre_perform_checks
16
+ get_smart_action_request
17
+ find_resource
18
+ check_permission_for_smart_route
19
+ ensure_record_ids_in_scope
20
+ end
21
+
11
22
  def get_smart_action_request
12
23
  begin
13
24
  params[:data][:attributes]
25
+ @parameters = ForestLiana::Ability::Permission::RequestPermission::decodeSignedApprovalRequest(params.permit!)
14
26
  rescue => error
15
27
  FOREST_REPORTER.report error
16
28
  FOREST_LOGGER.error "Smart Action execution error: #{error}"
@@ -18,29 +30,52 @@ module ForestLiana
18
30
  end
19
31
  end
20
32
 
21
- def smart_action_pre_perform_checks
22
- check_permission_for_smart_route
23
- ensure_record_ids_in_scope
33
+ def find_resource
34
+ begin
35
+ @resource = SchemaUtils.find_model_from_collection_name(@parameters[:data][:attributes][:collection_name])
36
+ if @resource.nil? || !SchemaUtils.model_included?(@resource) ||
37
+ !@resource.ancestors.include?(ActiveRecord::Base)
38
+ render serializer: nil, json: { status: 404 }, status: :not_found
39
+ end
40
+ @resource
41
+ rescue => error
42
+ FOREST_REPORTER.report error
43
+ FOREST_LOGGER.error "Find Collection error: #{error}\n#{format_stacktrace(error)}"
44
+ render serializer: nil, json: { status: 404 }, status: :not_found
45
+ end
46
+ end
47
+
48
+ def check_permission_for_smart_route
49
+ smart_action_request = @parameters[:data][:attributes]
50
+ if !smart_action_request.nil? && smart_action_request.has_key?(:smart_action_id)
51
+ forest_authorize!(
52
+ 'action',
53
+ forest_user,
54
+ @resource,
55
+ {parameters: params, endpoint: request.fullpath.split('?').first, http_method: request.request_method}
56
+ )
57
+ else
58
+ FOREST_LOGGER.error 'Smart action execution error: Unable to retrieve the smart action id.'
59
+ render serializer: nil, json: { status: 400 }, status: :bad_request
60
+ end
24
61
  end
25
62
 
26
63
  def ensure_record_ids_in_scope
27
64
  begin
28
- attributes = get_smart_action_request
65
+ attributes = @parameters[:data][:attributes]
29
66
 
30
67
  # if performing a `selectAll` let the `get_ids_from_request` handle the scopes
31
68
  return if attributes[:all_records]
32
69
 
33
- resource = find_resource(attributes[:collection_name])
34
-
35
70
  # user is using the composite_primary_keys gem
36
- if resource.primary_key.kind_of?(Array)
71
+ if @resource.primary_key.kind_of?(Array)
37
72
  # TODO: handle primary keys
38
73
  return
39
74
  end
40
75
 
41
- filter = JSON.generate({ 'field' => resource.primary_key, 'operator' => 'in', 'value' => attributes[:ids] })
76
+ filter = JSON.generate({ 'field' => @resource.primary_key, 'operator' => 'in', 'value' => attributes[:ids] })
42
77
 
43
- resources_getter = ForestLiana::ResourcesGetter.new(resource, { :filters => filter, :timezone => attributes[:timezone] }, forest_user)
78
+ resources_getter = ForestLiana::ResourcesGetter.new(@resource, { :filters => filter, :timezone => attributes[:timezone] }, forest_user)
44
79
 
45
80
  # resources getter will return records inside the scope. if the length differs then ids are out of scope
46
81
  return if resources_getter.count == attributes[:ids].length
@@ -53,54 +88,5 @@ module ForestLiana
53
88
  render serializer: nil, json: { error: 'Smart Action: failed to evaluate permissions' }, status: :internal_server_error
54
89
  end
55
90
  end
56
-
57
- def check_permission_for_smart_route
58
- begin
59
-
60
- smart_action_request = get_smart_action_request
61
- if !smart_action_request.nil? && smart_action_request.has_key?(:smart_action_id)
62
- checker = ForestLiana::PermissionsChecker.new(
63
- find_resource(smart_action_request[:collection_name]),
64
- 'actions',
65
- @rendering_id,
66
- user: forest_user,
67
- smart_action_request_info: get_smart_action_request_info
68
- )
69
- return head :forbidden unless checker.is_authorized?
70
- else
71
- FOREST_LOGGER.error 'Smart action execution error: Unable to retrieve the smart action id.'
72
- render serializer: nil, json: { status: 400 }, status: :bad_request
73
- end
74
- rescue => error
75
- FOREST_REPORTER.report error
76
- FOREST_LOGGER.error "Smart Action execution error: #{error}"
77
- render serializer: nil, json: { status: 400 }, status: :bad_request
78
- end
79
- end
80
-
81
- def find_resource(collection_name)
82
- begin
83
- resource = SchemaUtils.find_model_from_collection_name(collection_name)
84
-
85
- if resource.nil? || !SchemaUtils.model_included?(resource) ||
86
- !resource.ancestors.include?(ActiveRecord::Base)
87
- render serializer: nil, json: { status: 404 }, status: :not_found
88
- end
89
- resource
90
- rescue => error
91
- FOREST_REPORTER.report error
92
- FOREST_LOGGER.error "Find Collection error: #{error}\n#{format_stacktrace(error)}"
93
- render serializer: nil, json: { status: 404 }, status: :not_found
94
- end
95
- end
96
-
97
- # smart action permissions are retrieved from the action's endpoint and http_method
98
- def get_smart_action_request_info
99
- {
100
- # trim query params to get the endpoint
101
- endpoint: request.fullpath.split('?').first,
102
- http_method: request.request_method
103
- }
104
- end
105
91
  end
106
92
  end
@@ -1,22 +1,6 @@
1
1
  module ForestLiana
2
2
  class StatsController < ForestLiana::ApplicationController
3
- if Rails::VERSION::MAJOR < 4
4
- before_filter only: [:get] do
5
- find_resource()
6
- check_permission('statWithParameters')
7
- end
8
- before_filter only: [:get_with_live_query] do
9
- check_permission('liveQueries')
10
- end
11
- else
12
- before_action only: [:get] do
13
- find_resource()
14
- check_permission('statWithParameters')
15
- end
16
- before_action only: [:get_with_live_query] do
17
- check_permission('liveQueries')
18
- end
19
- end
3
+ include ForestLiana::Ability
20
4
 
21
5
  CHART_TYPE_VALUE = 'Value'
22
6
  CHART_TYPE_PIE = 'Pie'
@@ -24,7 +8,14 @@ module ForestLiana
24
8
  CHART_TYPE_LEADERBOARD = 'Leaderboard'
25
9
  CHART_TYPE_OBJECTIVE = 'Objective'
26
10
 
11
+ if Rails::VERSION::MAJOR < 4
12
+ before_filter :find_resource, only: :get
13
+ else
14
+ before_action :find_resource, only: :get
15
+ end
16
+
27
17
  def get
18
+ forest_authorize!('chart', forest_user, nil, {parameters: params})
28
19
  case params[:type]
29
20
  when CHART_TYPE_VALUE
30
21
  stat = ValueStatGetter.new(@resource, params, forest_user)
@@ -47,6 +38,7 @@ module ForestLiana
47
38
  end
48
39
 
49
40
  def get_with_live_query
41
+ forest_authorize!('chart', forest_user, nil, {parameters: params})
50
42
  begin
51
43
  stat = QueryStatGetter.new(params)
52
44
  stat.perform
@@ -67,54 +59,18 @@ module ForestLiana
67
59
  end
68
60
  end
69
61
 
62
+ def params
63
+ super.permit!
64
+ end
65
+
70
66
  private
71
67
 
72
68
  def find_resource
73
- @resource = SchemaUtils.find_model_from_collection_name(
74
- params[:collection])
69
+ @resource = SchemaUtils.find_model_from_collection_name(params[:collection])
75
70
 
76
71
  if @resource.nil? || !@resource.ancestors.include?(ActiveRecord::Base)
77
72
  render json: {status: 404}, status: :not_found, serializer: nil
78
73
  end
79
74
  end
80
-
81
- def get_live_query_request_info
82
- params['query']
83
- end
84
-
85
- def get_stat_parameter_request_info
86
- parameters = Rails::VERSION::MAJOR < 5 ? params.dup : params.permit(params.keys).to_h;
87
-
88
- # Notice: Removes useless properties
89
- parameters.delete('timezone');
90
- parameters.delete('controller');
91
- parameters.delete('action');
92
-
93
- # NOTICE: Remove the field information from group_by_field => collection:id
94
- if parameters['group_by_field']
95
- parameters['group_by_field'] = parameters['group_by_field'].split(':').first
96
- end
97
-
98
- return parameters;
99
- end
100
-
101
- def check_permission(permission_name)
102
- begin
103
- query_request = permission_name == 'liveQueries' ? get_live_query_request_info : get_stat_parameter_request_info;
104
- checker = ForestLiana::PermissionsChecker.new(
105
- nil,
106
- permission_name,
107
- @rendering_id,
108
- user: forest_user,
109
- query_request_info: query_request
110
- )
111
-
112
- return head :forbidden unless checker.is_authorized?
113
- rescue => error
114
- FOREST_REPORTER.report error
115
- FOREST_LOGGER.error "Stats execution error: #{error}"
116
- render serializer: nil, json: { status: 400 }, status: :bad_request
117
- end
118
- end
119
75
  end
120
76
  end
@@ -0,0 +1,16 @@
1
+ module ForestLiana
2
+ module Ability
3
+ module Exceptions
4
+ class AccessDenied < ForestLiana::Errors::ExpectedError
5
+ def initialize
6
+ super(
7
+ 403,
8
+ :forbidden,
9
+ 'You don\'t have permission to access this resource',
10
+ 'AccessDenied'
11
+ )
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module ForestLiana
2
+ module Ability
3
+ module Exceptions
4
+ class ActionConditionError < ForestLiana::Errors::ExpectedError
5
+ def initialize
6
+ super(
7
+ 409,
8
+ :conflict,
9
+ 'The conditions to trigger this action cannot be verified. Please contact an administrator.',
10
+ 'InvalidActionConditionError'
11
+ )
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module ForestLiana
2
+ module Ability
3
+ module Exceptions
4
+ class RequireApproval < ForestLiana::Errors::ExpectedError
5
+ attr_reader :data
6
+ def initialize(data)
7
+ @data = data
8
+ super(
9
+ 403,
10
+ :forbidden,
11
+ 'This action requires to be approved.',
12
+ 'CustomActionRequiresApprovalError',
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ module ForestLiana
2
+ module Ability
3
+ module Exceptions
4
+ class TriggerForbidden < ForestLiana::Errors::ExpectedError
5
+ def initialize
6
+ super(
7
+ 403,
8
+ :forbidden,
9
+ 'You don\'t have the permission to trigger this action',
10
+ 'CustomActionTriggerForbiddenError'
11
+ )
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ module ForestLiana
2
+ module Ability
3
+ module Fetch
4
+ def get_permissions(route)
5
+ begin
6
+ response = ForestLiana::ForestApiRequester.get(route)
7
+
8
+ if response.is_a?(Net::HTTPOK)
9
+ JSON.parse(response.body)
10
+ else
11
+ raise "Forest API returned an #{ForestLiana::Errors::HTTPErrorHelper.format(response)}"
12
+ end
13
+ rescue => exception
14
+ FOREST_REPORTER.report exception
15
+ FOREST_LOGGER.error 'Cannot retrieve the permissions from the Forest server.'
16
+ FOREST_LOGGER.error 'Which was caused by:'
17
+ ForestLiana::Errors::ExceptionHelper.recursively_print(exception, margin: ' ', is_error: true)
18
+ nil
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ require 'jwt'
2
+
3
+ module ForestLiana
4
+ module Ability
5
+ module Permission
6
+ class RequestPermission
7
+ def self.decodeSignedApprovalRequest(params)
8
+ if (params[:data][:attributes][:signed_approval_request])
9
+ decode_parameters = JWT.decode(params[:data][:attributes][:signed_approval_request], ForestLiana.env_secret, true, { algorithm: 'HS256' }).try(:first)
10
+
11
+ ActionController::Parameters.new(decode_parameters)
12
+ else
13
+ params
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,71 @@
1
+ module ForestLiana
2
+ module Ability
3
+ module Permission
4
+ class SmartActionChecker
5
+
6
+ def initialize(parameters, collection, smart_action, user)
7
+ @parameters = parameters
8
+ @collection = collection
9
+ @smart_action = smart_action
10
+ @user = user
11
+ end
12
+
13
+ def can_execute?
14
+ if @parameters[:data][:attributes][:signed_approval_request].present? && @smart_action['userApprovalEnabled'].include?(@user['roleId'])
15
+ can_approve?
16
+ else
17
+ can_trigger?
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def can_approve?
24
+ @parameters = RequestPermission.decodeSignedApprovalRequest(@parameters)
25
+ if ((@smart_action['userApprovalConditions'].empty? || match_conditions('userApprovalConditions')) &&
26
+ (@parameters[:data][:attributes][:requester_id] != @user['id'] || @smart_action['selfApprovalEnabled'].include?(@user['roleId']))
27
+ )
28
+ return true
29
+ end
30
+
31
+ raise ForestLiana::Ability::Exceptions::TriggerForbidden.new
32
+ end
33
+
34
+ def can_trigger?
35
+ if @smart_action['triggerEnabled'].include?(@user['roleId']) && @smart_action['approvalRequired'].exclude?(@user['roleId'])
36
+ return true if @smart_action['triggerConditions'].empty? || match_conditions('triggerConditions')
37
+ elsif @smart_action['approvalRequired'].include?(@user['roleId'])
38
+ if @smart_action['approvalRequiredConditions'].empty? || match_conditions('approvalRequiredConditions')
39
+ raise ForestLiana::Ability::Exceptions::RequireApproval.new(@smart_action['userApprovalEnabled'])
40
+ end
41
+ end
42
+
43
+ raise ForestLiana::Ability::Exceptions::TriggerForbidden.new
44
+ end
45
+
46
+ def match_conditions(condition_name)
47
+ begin
48
+ attributes = @parameters[:data][:attributes]
49
+ records = FiltersParser.new(
50
+ @smart_action[condition_name][0]['filter'].to_json,
51
+ @collection,
52
+ @parameters[:timezone],
53
+ @parameters
54
+ ).apply_filters
55
+
56
+ if attributes[:all_records]
57
+ records = records.where.not(id: attributes[:all_records_ids_excluded])
58
+ else
59
+ # check if the ids are present into the request of activeRecord
60
+ records = records.where(id: attributes[:ids])
61
+ end
62
+
63
+ records.select(@collection.table_name + '.id').count == attributes[:ids].count
64
+ rescue
65
+ raise ForestLiana::Ability::Exceptions::ActionConditionError.new
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end