forest_liana 6.0.5 → 6.1.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: 95b8a3f6be7165cf4debb95a67e676c4846cd53dd5226ef6550b0f4a9ccab656
4
- data.tar.gz: eb8b01c7bd02b0c828fd5aa7e27ee8677ff5b1711fab9dc96512d607f5562fac
3
+ metadata.gz: de6de5cae6e0ce03e49a351ee0ec5ead80b6ac45fc7f56a6b6912b663c7b85c3
4
+ data.tar.gz: 03d99b9f6ef599ec4c18446c50f9f4c7b24c09771e626954cdfb800ec25a55ff
5
5
  SHA512:
6
- metadata.gz: fbdf538274940ded194b63d76426557241122eb3ac5e1f6704e37fa295d4a3eece921d0f0c28546e919c454f580733ab9778960d21a1d758cb19f84ef4bd0771
7
- data.tar.gz: 2540d9a5c6289e23ac9d9d383c2d99f86883c2f493e32e5df5de0c659957696d329ff8f60a9339e7136d783ce3cae04e1d4499f418699e70901e65241a0116b4
6
+ metadata.gz: ea5e991a8476f95d7571ff1bf5e9dfef006c119c67857d18967f80810417db15122f2b64e922906e95d1401b5e71df099705718f431ec481de5a9da6c0d7310f
7
+ data.tar.gz: 7fde6b7720bdf881a3fc5bfcab28d4ca05092fe7a45a5dc8d3c40d4576ff4b0bb6c423eb500b4bc485181840ef8685b5167ab92501aa1533adbbda7e3f28ac5f
@@ -1,9 +1,21 @@
1
1
  module ForestLiana
2
2
  class StatsController < ForestLiana::ApplicationController
3
3
  if Rails::VERSION::MAJOR < 4
4
- before_filter :find_resource, except: [:get_with_live_query]
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
5
11
  else
6
- before_action :find_resource, except: [:get_with_live_query]
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
7
19
  end
8
20
 
9
21
  CHART_TYPE_VALUE = 'Value'
@@ -64,5 +76,38 @@ module ForestLiana
64
76
  render json: {status: 404}, status: :not_found, serializer: nil
65
77
  end
66
78
  end
79
+
80
+ def get_live_query_request_info
81
+ params['query']
82
+ end
83
+
84
+ def get_stat_parameter_request_info
85
+ parameters = Rails::VERSION::MAJOR < 5 ? params.dup : params.permit(params.keys).to_h;
86
+
87
+ # Notice: Removes useless properties
88
+ parameters.delete('timezone');
89
+ parameters.delete('controller');
90
+ parameters.delete('action');
91
+
92
+ return parameters;
93
+ end
94
+
95
+ def check_permission(permission_name)
96
+ begin
97
+ query_request = permission_name == 'liveQueries' ? get_live_query_request_info : get_stat_parameter_request_info;
98
+ checker = ForestLiana::PermissionsChecker.new(
99
+ nil,
100
+ permission_name,
101
+ @rendering_id,
102
+ user_id: forest_user['id'],
103
+ query_request_info: query_request
104
+ )
105
+
106
+ return head :forbidden unless checker.is_authorized?
107
+ rescue => error
108
+ FOREST_LOGGER.error "Stats execution error: #{error}"
109
+ render serializer: nil, json: { status: 400 }, status: :bad_request
110
+ end
111
+ end
67
112
  end
68
113
  end
@@ -6,13 +6,16 @@ module ForestLiana
6
6
  # TODO: handle cache scopes per rendering
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:, smart_action_request_info: nil, collection_list_parameters: nil)
10
- @user_id = user_id
11
- @collection_name = ForestLiana.name_for(resource)
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
+
11
+ @collection_name = resource.present? ? ForestLiana.name_for(resource) : nil
12
12
  @permission_name = permission_name
13
13
  @rendering_id = rendering_id
14
+
15
+ @user_id = user_id
14
16
  @smart_action_request_info = smart_action_request_info
15
17
  @collection_list_parameters = collection_list_parameters
18
+ @query_request_info = query_request_info
16
19
  end
17
20
 
18
21
  def is_authorized?
@@ -48,6 +51,16 @@ module ForestLiana
48
51
 
49
52
  def is_allowed
50
53
  permissions = get_permissions_content
54
+
55
+ # NOTICE: check liveQueries permissions
56
+ if @permission_name === 'liveQueries'
57
+ return live_query_allowed?
58
+ elsif @permission_name === 'statWithParameters'
59
+ return stat_with_parameters_allowed?
60
+ end
61
+
62
+
63
+
51
64
  if permissions && permissions[@collection_name] &&
52
65
  permissions[@collection_name]['collection']
53
66
  if @permission_name === 'actions'
@@ -98,6 +111,16 @@ module ForestLiana
98
111
  permissions && permissions['data'] && permissions['data']['collections']
99
112
  end
100
113
 
114
+ def get_live_query_permissions_content
115
+ permissions = get_permissions
116
+ permissions && permissions['stats'] && permissions['stats']['queries']
117
+ end
118
+
119
+ def get_stat_with_parameters_content(statPermissionType)
120
+ permissions = get_permissions
121
+ permissions && permissions['stats'] && permissions['stats'][statPermissionType]
122
+ end
123
+
101
124
  def get_last_fetch
102
125
  permissions = get_permissions
103
126
  permissions && permissions['last_fetch']
@@ -138,6 +161,32 @@ module ForestLiana
138
161
  ).is_scope_in_request?(@collection_list_parameters)
139
162
  end
140
163
 
164
+ def live_query_allowed?
165
+ live_queries_permissions = get_live_query_permissions_content
166
+
167
+ return false unless live_queries_permissions
168
+
169
+ # NOTICE: @query_request_info matching an existing live query
170
+ return live_queries_permissions.include? @query_request_info
171
+ end
172
+
173
+ def stat_with_parameters_allowed?
174
+ permissionType = @query_request_info['type'].downcase + 's'
175
+ pool_permissions = get_stat_with_parameters_content(permissionType)
176
+
177
+ return false unless pool_permissions
178
+
179
+ # NOTICE: equivalent to Object.values in js
180
+ array_query_request_info = @query_request_info.values
181
+
182
+ # NOTICE: pool_permissions contains the @query_request_info
183
+ # we use the intersection between statPermission and @query_request_info
184
+ return pool_permissions.any? {
185
+ |statPermission|
186
+ (array_query_request_info & statPermission.values) == array_query_request_info;
187
+ }
188
+ end
189
+
141
190
  def date_difference_in_seconds(date1, date2)
142
191
  (date1 - date2).to_i
143
192
  end
@@ -1,3 +1,3 @@
1
1
  module ForestLiana
2
- VERSION = "6.0.5"
2
+ VERSION = "6.1.0"
3
3
  end
@@ -0,0 +1,114 @@
1
+ require 'rails_helper'
2
+ require 'json'
3
+
4
+ describe "Stats", type: :request do
5
+
6
+ token = JWT.encode({
7
+ id: 38,
8
+ email: 'michael.kelso@that70.show',
9
+ first_name: 'Michael',
10
+ last_name: 'Kelso',
11
+ team: 'Operations',
12
+ rendering_id: 16,
13
+ exp: Time.now.to_i + 2.weeks.to_i
14
+ }, ForestLiana.auth_secret, 'HS256')
15
+
16
+ headers = {
17
+ 'Accept' => 'application/json',
18
+ 'Content-Type' => 'application/json',
19
+ 'Authorization' => "Bearer #{token}"
20
+ }
21
+
22
+ let(:schema) {
23
+ [
24
+ ForestLiana::Model::Collection.new({
25
+ name: 'Products',
26
+ fields: [],
27
+ actions: []
28
+ })
29
+ ]
30
+ }
31
+
32
+ before do
33
+ allow(ForestLiana).to receive(:apimap).and_return(schema)
34
+ end
35
+
36
+ before(:each) do
37
+ allow(ForestLiana::IpWhitelist).to receive(:retrieve) { true }
38
+ allow(ForestLiana::IpWhitelist).to receive(:is_ip_whitelist_retrieved) { true }
39
+ allow(ForestLiana::IpWhitelist).to receive(:is_ip_valid) { true }
40
+
41
+ allow_any_instance_of(ForestLiana::PermissionsChecker).to receive(:is_authorized?) { true }
42
+
43
+ allow_any_instance_of(ForestLiana::ValueStatGetter).to receive(:perform) { true }
44
+ allow_any_instance_of(ForestLiana::QueryStatGetter).to receive(:perform) { true }
45
+ end
46
+
47
+
48
+
49
+ describe 'POST /stats/:collection' do
50
+ params = { type: 'Value', collection: 'User', aggregate: 'Count' }
51
+
52
+ it 'should respond 200' do
53
+ data = ForestLiana::Model::Stat.new(value: { countCurrent: 0, countPrevious: 0 })
54
+ allow_any_instance_of(ForestLiana::ValueStatGetter).to receive(:record) { data }
55
+ # NOTICE: bypass : find_resource error
56
+ allow_any_instance_of(ForestLiana::StatsController).to receive(:find_resource) { true }
57
+ allow(ForestLiana::QueryHelper).to receive(:get_one_association_names_symbol) { true }
58
+
59
+ post '/forest/stats/Products', params: JSON.dump(params), headers: headers
60
+ expect(response.status).to eq(200)
61
+ end
62
+
63
+ it 'should respond 401 with no headers' do
64
+ post '/forest/stats/Products', params: JSON.dump(params)
65
+ expect(response.status).to eq(401)
66
+ end
67
+
68
+ it 'should respond 404 with non existing collection' do
69
+ allow_any_instance_of(ForestLiana::ValueStatGetter).to receive(:record) { nil }
70
+
71
+ post '/forest/stats/NoCollection', params: {}, headers: headers
72
+ expect(response.status).to eq(404)
73
+ end
74
+
75
+ # it 'should respond 403 Forbidden' do
76
+ # allow_any_instance_of(ForestLiana::PermissionsChecker).to receive(:is_authorized?) { false }
77
+
78
+ # post '/forest/stats/Products', params: JSON.dump(params), headers: headers
79
+ # expect(response.status).to eq(403)
80
+ # end
81
+ end
82
+
83
+ describe 'POST /stats' do
84
+ params = { query: 'SELECT COUNT(*) AS value FROM products;' }
85
+
86
+ it 'should respond 200' do
87
+ data = ForestLiana::Model::Stat.new(value: { value: 0, objective: 0 })
88
+ allow_any_instance_of(ForestLiana::QueryStatGetter).to receive(:record) { data }
89
+
90
+ post '/forest/stats', params: JSON.dump(params), headers: headers
91
+ expect(response.status).to eq(200)
92
+ end
93
+
94
+ it 'should respond 401 with no headers' do
95
+ post '/forest/stats', params: JSON.dump(params)
96
+ expect(response.status).to eq(401)
97
+ end
98
+
99
+ it 'should respond 403 Forbidden' do
100
+ allow_any_instance_of(ForestLiana::PermissionsChecker).to receive(:is_authorized?) { false }
101
+
102
+ post '/forest/stats', params: JSON.dump(params), headers: headers
103
+ expect(response.status).to eq(403)
104
+ end
105
+
106
+ it 'should respond 422 with unprocessable query' do
107
+ allow_any_instance_of(ForestLiana::QueryStatGetter).to receive(:perform) { raise ForestLiana::Errors::LiveQueryError.new }
108
+
109
+ post '/forest/stats', params: JSON.dump(params), headers: headers
110
+ expect(response.status).to eq(422)
111
+ end
112
+ end
113
+
114
+ end
@@ -109,7 +109,7 @@ module ForestLiana
109
109
 
110
110
  describe 'handling cache' do
111
111
  let(:collection_name) { 'all_rights_collection' }
112
- let(:fake_ressource) { nil }
112
+ let(:fake_ressource) { collection_name }
113
113
  let(:default_rendering_id) { 1 }
114
114
 
115
115
  context 'when calling twice the same permissions' do
@@ -191,7 +191,7 @@ module ForestLiana
191
191
 
192
192
 
193
193
  context 'scopes cache' do
194
- let(:fake_ressource) { nil }
194
+ let(:fake_ressource) { collection_name }
195
195
  let(:rendering_id) { 1 }
196
196
  let(:collection_name) { 'custom' }
197
197
  let(:scope_permissions) { { rendering_id => { 'custom' => nil } } }
@@ -349,7 +349,7 @@ module ForestLiana
349
349
  describe '#is_authorized?' do
350
350
  # Resource is only used to retrieve the collection name as it's stubbed it does not
351
351
  # need to be defined
352
- let(:fake_ressource) { nil }
352
+ let(:fake_ressource) { collection_name }
353
353
  let(:default_rendering_id) { nil }
354
354
  let(:api_permissions) { default_api_permissions }
355
355
  let(:collection_name) { 'all_rights_collection' }
@@ -132,7 +132,7 @@ module ForestLiana
132
132
 
133
133
  describe 'handling cache' do
134
134
  let(:collection_name) { 'all_rights_collection_boolean' }
135
- let(:fake_ressource) { nil }
135
+ let(:fake_ressource) { collection_name }
136
136
  let(:default_rendering_id) { 1 }
137
137
 
138
138
  context 'collections cache' do
@@ -396,7 +396,7 @@ module ForestLiana
396
396
  describe '#is_authorized?' do
397
397
  # Resource is only used to retrieve the collection name as it's stub it does not
398
398
  # need to be defined
399
- let(:fake_ressource) { nil }
399
+ let(:fake_ressource) { collection_name }
400
400
  let(:default_rendering_id) { nil }
401
401
  let(:api_permissions) { default_api_permissions }
402
402
 
@@ -0,0 +1,131 @@
1
+ module ForestLiana
2
+ describe PermissionsChecker do
3
+ before(:each) do
4
+ described_class.empty_cache
5
+ end
6
+
7
+ let(:user_id) { 1 }
8
+ let(:schema) {
9
+ [
10
+ ForestLiana::Model::Collection.new({
11
+ name: 'all_rights_collection_boolean',
12
+ fields: [],
13
+ actions: [
14
+ ForestLiana::Model::Action.new({
15
+ name: 'Test',
16
+ endpoint: 'forest/actions/Test',
17
+ http_method: 'POST'
18
+ })
19
+ ]
20
+ })
21
+ ]
22
+ }
23
+ let(:scope_permissions) { nil }
24
+ let(:default_api_permissions) {
25
+ {
26
+ "data" => {
27
+ 'collections' => {
28
+ "all_rights_collection_boolean" => {
29
+ "collection" => {
30
+ "browseEnabled" => true,
31
+ "readEnabled" => true,
32
+ "editEnabled" => true,
33
+ "addEnabled" => true,
34
+ "deleteEnabled" => true,
35
+ "exportEnabled" => true
36
+ },
37
+ "actions" => {
38
+ "Test" => {
39
+ "triggerEnabled" => true
40
+ },
41
+ }
42
+ },
43
+ },
44
+ 'renderings' => scope_permissions
45
+ },
46
+ "meta" => {
47
+ "rolesACLActivated" => true
48
+ },
49
+ "stats" => {
50
+ "queries" => [
51
+ 'SELECT COUNT(*) AS value FROM products;',
52
+ 'SELECT COUNT(*) AS value FROM sometings;'
53
+ ],
54
+ "values" => [
55
+ {
56
+ "type" => "Value",
57
+ "collection" => "Product",
58
+ "aggregate" => "Count"
59
+ }
60
+ ],
61
+ },
62
+ }
63
+ }
64
+ let(:default_rendering_id) { 1 }
65
+
66
+ before do
67
+ allow(ForestLiana).to receive(:apimap).and_return(schema)
68
+ end
69
+
70
+ describe '#is_authorized?' do
71
+ # Resource is only used to retrieve the collection name as it's stub it does not
72
+ # need to be defined
73
+ let(:fake_ressource) { nil }
74
+ let(:default_rendering_id) { nil }
75
+ let(:api_permissions) { default_api_permissions }
76
+
77
+ before do
78
+ allow(ForestLiana::PermissionsGetter).to receive(:get_permissions_for_rendering).and_return(api_permissions)
79
+ end
80
+
81
+ context 'when permissions liveQueries' do
82
+ context 'contains the query' do
83
+ request_info = {
84
+ "type" => "Value",
85
+ "collection" => "Product",
86
+ "aggregate" => "Count"
87
+ };
88
+ subject { described_class.new(fake_ressource, 'liveQueries', default_rendering_id, user_id: user_id, query_request_info: 'SELECT COUNT(*) AS value FROM sometings;') }
89
+
90
+ it 'should be authorized' do
91
+ expect(subject.is_authorized?).to be true
92
+ end
93
+ end
94
+
95
+ context 'does not contains the query' do
96
+ subject { described_class.new(fake_ressource, 'liveQueries', default_rendering_id, user_id: user_id, query_request_info: 'SELECT * FROM products WHERE category = Gifts OR 1=1-- AND released = 1') }
97
+ it 'should NOT be authorized' do
98
+ expect(subject.is_authorized?).to be false
99
+ end
100
+ end
101
+ end
102
+
103
+ context 'when permissions statWithParameters' do
104
+ context 'contains the stat with the same parameters' do
105
+ request_info = {
106
+ "type" => "Value",
107
+ "collection" => "Product",
108
+ "aggregate" => "Count"
109
+ };
110
+ subject { described_class.new(fake_ressource, 'statWithParameters', default_rendering_id, user_id: user_id, query_request_info: request_info) }
111
+
112
+ it 'should be authorized' do
113
+ expect(subject.is_authorized?).to be true
114
+ end
115
+ end
116
+
117
+ context 'does not contains the stat with the same parameters' do
118
+ other_request_info = {
119
+ "type" => "Leaderboard",
120
+ "collection" => "Product",
121
+ "aggregate" => "Sum"
122
+ };
123
+ subject { described_class.new(fake_ressource, 'statWithParameters', default_rendering_id, user_id: user_id, query_request_info: other_request_info) }
124
+ it 'should NOT be authorized' do
125
+ expect(subject.is_authorized?).to be false
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ 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: 6.0.5
4
+ version: 6.1.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: 2021-03-11 00:00:00.000000000 Z
11
+ date: 2021-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -364,11 +364,13 @@ files:
364
364
  - spec/requests/actions_controller_spec.rb
365
365
  - spec/requests/authentications_spec.rb
366
366
  - spec/requests/resources_spec.rb
367
+ - spec/requests/stats_spec.rb
367
368
  - spec/services/forest_liana/apimap_sorter_spec.rb
368
369
  - spec/services/forest_liana/filters_parser_spec.rb
369
370
  - spec/services/forest_liana/ip_whitelist_checker_spec.rb
370
371
  - spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb
371
372
  - spec/services/forest_liana/permissions_checker_acl_enabled_spec.rb
373
+ - spec/services/forest_liana/permissions_checker_live_queries_spec.rb
372
374
  - spec/services/forest_liana/permissions_formatter_spec.rb
373
375
  - spec/services/forest_liana/permissions_getter_spec.rb
374
376
  - spec/services/forest_liana/schema_adapter_spec.rb
@@ -625,8 +627,10 @@ test_files:
625
627
  - spec/requests/resources_spec.rb
626
628
  - spec/requests/authentications_spec.rb
627
629
  - spec/requests/actions_controller_spec.rb
630
+ - spec/requests/stats_spec.rb
628
631
  - spec/spec_helper.rb
629
632
  - spec/services/forest_liana/filters_parser_spec.rb
633
+ - spec/services/forest_liana/permissions_checker_live_queries_spec.rb
630
634
  - spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb
631
635
  - spec/services/forest_liana/schema_adapter_spec.rb
632
636
  - spec/services/forest_liana/permissions_checker_acl_enabled_spec.rb