forest_liana 6.0.5 → 6.1.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: 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