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 +4 -4
- data/app/controllers/forest_liana/stats_controller.rb +47 -2
- data/app/services/forest_liana/permissions_checker.rb +52 -3
- data/lib/forest_liana/version.rb +1 -1
- data/spec/requests/stats_spec.rb +114 -0
- data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +3 -3
- data/spec/services/forest_liana/permissions_checker_acl_enabled_spec.rb +2 -2
- data/spec/services/forest_liana/permissions_checker_live_queries_spec.rb +131 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de6de5cae6e0ce03e49a351ee0ec5ead80b6ac45fc7f56a6b6912b663c7b85c3
|
4
|
+
data.tar.gz: 03d99b9f6ef599ec4c18446c50f9f4c7b24c09771e626954cdfb800ec25a55ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 :
|
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 :
|
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
|
10
|
-
|
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
|
data/lib/forest_liana/version.rb
CHANGED
@@ -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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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
|
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
|
+
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
|