forest_admin_agent 1.0.0.pre.beta.21 → 1.0.0.pre.beta.22
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 +4 -4
- data/forest_admin_agent.gemspec +2 -1
- data/lib/forest_admin_agent/auth/oauth2/forest_provider.rb +1 -1
- data/lib/forest_admin_agent/auth/oidc_client_manager.rb +3 -2
- data/lib/forest_admin_agent/builder/agent_factory.rb +7 -7
- data/lib/forest_admin_agent/http/Exceptions/authentication_open_id_client.rb +1 -1
- data/lib/forest_admin_agent/http/Exceptions/conflict_error.rb +14 -0
- data/lib/forest_admin_agent/http/Exceptions/forbidden_error.rb +14 -0
- data/lib/forest_admin_agent/http/Exceptions/not_found_error.rb +1 -1
- data/lib/forest_admin_agent/http/Exceptions/require_approval.rb +15 -0
- data/lib/forest_admin_agent/http/forest_admin_api_requester.rb +32 -4
- data/lib/forest_admin_agent/routes/abstract_authenticated_route.rb +1 -0
- data/lib/forest_admin_agent/routes/resources/count.rb +7 -4
- data/lib/forest_admin_agent/routes/resources/delete.rb +5 -1
- data/lib/forest_admin_agent/routes/resources/list.rb +9 -3
- data/lib/forest_admin_agent/routes/resources/related/associate_related.rb +10 -1
- data/lib/forest_admin_agent/routes/resources/related/count_related.rb +2 -1
- data/lib/forest_admin_agent/routes/resources/related/dissociate_related.rb +2 -0
- data/lib/forest_admin_agent/routes/resources/related/list_related.rb +9 -1
- data/lib/forest_admin_agent/routes/resources/related/update_related.rb +8 -1
- data/lib/forest_admin_agent/routes/resources/show.rb +5 -3
- data/lib/forest_admin_agent/routes/resources/store.rb +1 -0
- data/lib/forest_admin_agent/routes/resources/update.rb +4 -3
- data/lib/forest_admin_agent/services/permissions.rb +264 -0
- data/lib/forest_admin_agent/services/smart_action_checker.rb +95 -0
- data/lib/forest_admin_agent/services/sse_cache_invalidation.rb +45 -0
- data/lib/forest_admin_agent/utils/context_variables.rb +39 -0
- data/lib/forest_admin_agent/utils/context_variables_injector.rb +53 -0
- data/lib/forest_admin_agent/utils/schema/schema_emitter.rb +1 -1
- data/lib/forest_admin_agent/version.rb +1 -1
- data/lib/forest_admin_agent.rb +1 -0
- metadata +27 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5714273d6e7144f00eb531b7a93d0e908698bd1dac7984dc47affd15a7514f6
|
4
|
+
data.tar.gz: 3b47af0daae4bc4e02b944f0d21be13aa18fe5e7abb9bfcfa03f2ea3272028bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93044527fdfb1a06f49408044479258b1cccb6af15c8025cf26310f01c684ae6b5c4657bd25496eb69d911eaa77daae83ec82ec551469bd15cacea61a3b46687
|
7
|
+
data.tar.gz: f93863c6d28342854e2e453795bca3c3892c6a09c0d91a44fcb42fae57b107c17f6daa3e9694d04e0862bb46e1f95ff2d14f04c199ab0c2a5436434ca5090ca4
|
data/forest_admin_agent.gemspec
CHANGED
@@ -36,10 +36,11 @@ admin work on any Ruby application."
|
|
36
36
|
spec.add_dependency "activesupport", ">= 6.1"
|
37
37
|
spec.add_dependency "dry-container", "~> 0.11"
|
38
38
|
spec.add_dependency "faraday", "~> 2.7"
|
39
|
+
spec.add_dependency "filecache", "~> 1.0"
|
39
40
|
spec.add_dependency "ipaddress", "~> 0.8.3"
|
40
41
|
spec.add_dependency "jsonapi-serializers", "~> 1.0"
|
41
42
|
spec.add_dependency "jwt", "~> 2.7"
|
42
|
-
spec.add_dependency "
|
43
|
+
spec.add_dependency "ld-eventsource", "~> 2.2"
|
43
44
|
spec.add_dependency "mono_logger", "~> 1.1"
|
44
45
|
spec.add_dependency "openid_connect", "~> 2.2"
|
45
46
|
spec.add_dependency "rake", "~> 13.0"
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'filecache'
|
1
2
|
require 'openid_connect'
|
2
3
|
require_relative 'oauth2/oidc_config'
|
3
4
|
require_relative 'oauth2/forest_provider'
|
@@ -18,8 +19,8 @@ module ForestAdminAgent
|
|
18
19
|
private
|
19
20
|
|
20
21
|
def setup_cache(env_secret, config_agent)
|
21
|
-
|
22
|
-
|
22
|
+
cache = FileCache.new('auth_issuer', (config_agent[:cache_dir]).to_s, TTL)
|
23
|
+
cache.get_or_set env_secret do
|
23
24
|
oidc_config = retrieve_config(config_agent[:forest_server_url])
|
24
25
|
credentials = register(
|
25
26
|
config_agent[:env_secret],
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'dry-container'
|
2
|
-
require '
|
2
|
+
require 'filecache'
|
3
3
|
|
4
4
|
module ForestAdminAgent
|
5
5
|
module Builder
|
@@ -21,7 +21,7 @@ module ForestAdminAgent
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def add_datasource(datasource)
|
24
|
-
datasource.collections.
|
24
|
+
datasource.collections.each_value { |collection| @customizer.add_collection(collection) }
|
25
25
|
self
|
26
26
|
end
|
27
27
|
|
@@ -40,9 +40,9 @@ module ForestAdminAgent
|
|
40
40
|
if !schema_is_know || force
|
41
41
|
# Logger::log('Info', 'schema was updated, sending new version');
|
42
42
|
client = ForestAdminAgent::Http::ForestAdminApiRequester.new
|
43
|
-
client.post('/forest/apimaps', schema)
|
44
|
-
schema_file_hash_cache =
|
45
|
-
schema_file_hash_cache.
|
43
|
+
client.post('/forest/apimaps', schema.to_json)
|
44
|
+
schema_file_hash_cache = FileCache.new('app', @options[:cache_dir].to_s, TTL_SCHEMA)
|
45
|
+
schema_file_hash_cache.get_or_set 'value' do
|
46
46
|
schema[:meta][:schemaFileHash]
|
47
47
|
end
|
48
48
|
@container.register(:schema_file_hash, schema_file_hash_cache)
|
@@ -59,11 +59,11 @@ module ForestAdminAgent
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def build_cache
|
62
|
-
@container.register(:cache,
|
62
|
+
@container.register(:cache, FileCache.new('app', @options[:cache_dir].to_s, TTL_SCHEMA))
|
63
63
|
return unless @has_env_secret
|
64
64
|
|
65
65
|
cache = @container.resolve(:cache)
|
66
|
-
cache.
|
66
|
+
cache.get_or_set 'config' do
|
67
67
|
@options.to_h
|
68
68
|
end
|
69
69
|
end
|
@@ -5,7 +5,7 @@ module ForestAdminAgent
|
|
5
5
|
attr_reader :error, :error_description, :state
|
6
6
|
|
7
7
|
def initialize(error, error_description, state)
|
8
|
-
super
|
8
|
+
super(error, 401, error_description)
|
9
9
|
@error = error
|
10
10
|
@error_description = error_description
|
11
11
|
@state = state
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Http
|
3
|
+
module Exceptions
|
4
|
+
class ForbiddenError < HttpException
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(message, name = 'ForbiddenError')
|
8
|
+
@name = name
|
9
|
+
super(403, 'Forbidden', message)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Http
|
3
|
+
module Exceptions
|
4
|
+
class RequireApproval < HttpException
|
5
|
+
attr_reader :name, :data
|
6
|
+
|
7
|
+
def initialize(message, name = 'RequireApproval', data = [])
|
8
|
+
@name = name
|
9
|
+
@data = data
|
10
|
+
super(403, 'Forbidden', message)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -3,6 +3,8 @@ require 'faraday'
|
|
3
3
|
module ForestAdminAgent
|
4
4
|
module Http
|
5
5
|
class ForestAdminApiRequester
|
6
|
+
include ForestAdminDatasourceToolkit::Exceptions
|
7
|
+
|
6
8
|
def initialize
|
7
9
|
@headers = {
|
8
10
|
'Content-Type' => 'application/json',
|
@@ -16,12 +18,38 @@ module ForestAdminAgent
|
|
16
18
|
)
|
17
19
|
end
|
18
20
|
|
19
|
-
def get(url, params)
|
20
|
-
@client.get(url, params
|
21
|
+
def get(url, params = nil)
|
22
|
+
@client.get(url, params)
|
23
|
+
end
|
24
|
+
|
25
|
+
def post(url, params = nil)
|
26
|
+
@client.post(url, params)
|
21
27
|
end
|
22
28
|
|
23
|
-
def
|
24
|
-
|
29
|
+
def handle_response_error(error)
|
30
|
+
raise error if error.is_a?(ForestException)
|
31
|
+
|
32
|
+
if error.response[:message]&.include?('certificate')
|
33
|
+
raise ForestException,
|
34
|
+
'ForestAdmin server TLS certificate cannot be verified. Please check that your system time is set properly.'
|
35
|
+
end
|
36
|
+
|
37
|
+
if error.response[:status].zero? || error.response[:status] == 502
|
38
|
+
raise ForestException, 'Failed to reach ForestAdmin server. Are you online?'
|
39
|
+
end
|
40
|
+
|
41
|
+
if error.response[:status] == 404
|
42
|
+
raise ForestException,
|
43
|
+
'ForestAdmin server failed to find the project related to the envSecret you configured. Can you check that you copied it properly in the Forest initialization?'
|
44
|
+
end
|
45
|
+
|
46
|
+
if error.response[:status] == 503
|
47
|
+
raise ForestException,
|
48
|
+
'Forest is in maintenance for a few minutes. We are upgrading your experience in the forest. We just need a few more minutes to get it right.'
|
49
|
+
end
|
50
|
+
|
51
|
+
raise ForestException,
|
52
|
+
'An unexpected error occurred while contacting the ForestAdmin server. Please contact support@forestadmin.com for further investigations.'
|
25
53
|
end
|
26
54
|
end
|
27
55
|
end
|
@@ -3,8 +3,9 @@ require 'jsonapi-serializers'
|
|
3
3
|
module ForestAdminAgent
|
4
4
|
module Routes
|
5
5
|
module Resources
|
6
|
-
class Count <
|
6
|
+
class Count < AbstractAuthenticatedRoute
|
7
7
|
include ForestAdminAgent::Builder
|
8
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
|
8
9
|
def setup_routes
|
9
10
|
add_route('forest_count', 'get', '/:collection_name/count', ->(args) { handle_request(args) })
|
10
11
|
|
@@ -13,12 +14,14 @@ module ForestAdminAgent
|
|
13
14
|
|
14
15
|
def handle_request(args = {})
|
15
16
|
build(args)
|
17
|
+
@permissions.can?(:browse, @collection)
|
16
18
|
|
17
19
|
if @collection.is_countable?
|
18
|
-
|
19
|
-
|
20
|
+
filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
|
21
|
+
condition_tree: @permissions.get_scope(@collection)
|
22
|
+
)
|
20
23
|
aggregation = ForestAdminDatasourceToolkit::Components::Query::Aggregation.new(operation: 'Count')
|
21
|
-
result = @collection.aggregate(caller, filter, aggregation)
|
24
|
+
result = @collection.aggregate(@caller, filter, aggregation)
|
22
25
|
|
23
26
|
return {
|
24
27
|
name: args[:params]['collection_name'],
|
@@ -7,6 +7,7 @@ module ForestAdminAgent
|
|
7
7
|
class Delete < AbstractAuthenticatedRoute
|
8
8
|
include ForestAdminAgent::Builder
|
9
9
|
include ForestAdminDatasourceToolkit::Components::Query
|
10
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
|
10
11
|
|
11
12
|
def setup_routes
|
12
13
|
add_route('forest_delete_bulk', 'delete', '/:collection_name', ->(args) { handle_request_bulk(args) })
|
@@ -17,6 +18,7 @@ module ForestAdminAgent
|
|
17
18
|
|
18
19
|
def handle_request(args = {})
|
19
20
|
build(args)
|
21
|
+
@permissions.can?(:delete, @collection)
|
20
22
|
id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
|
21
23
|
delete_records(args, { ids: [id], are_excluded: false })
|
22
24
|
|
@@ -25,6 +27,7 @@ module ForestAdminAgent
|
|
25
27
|
|
26
28
|
def handle_request_bulk(args = {})
|
27
29
|
build(args)
|
30
|
+
@permissions.can?(:delete, @collection)
|
28
31
|
selection_ids = Utils::Id.parse_selection_ids(@collection, args[:params], with_key: true)
|
29
32
|
delete_records(args, selection_ids)
|
30
33
|
|
@@ -38,7 +41,8 @@ module ForestAdminAgent
|
|
38
41
|
condition_tree: ConditionTree::ConditionTreeFactory.intersect(
|
39
42
|
[
|
40
43
|
Utils::QueryStringParser.parse_condition_tree(@collection, args),
|
41
|
-
condition_tree_ids
|
44
|
+
condition_tree_ids,
|
45
|
+
@permissions.get_scope(@collection)
|
42
46
|
]
|
43
47
|
)
|
44
48
|
)
|
@@ -5,6 +5,7 @@ module ForestAdminAgent
|
|
5
5
|
module Resources
|
6
6
|
class List < AbstractAuthenticatedRoute
|
7
7
|
include ForestAdminAgent::Builder
|
8
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
|
8
9
|
def setup_routes
|
9
10
|
add_route('forest_list', 'get', '/:collection_name', ->(args) { handle_request(args) })
|
10
11
|
|
@@ -13,14 +14,19 @@ module ForestAdminAgent
|
|
13
14
|
|
14
15
|
def handle_request(args = {})
|
15
16
|
build(args)
|
16
|
-
|
17
|
+
@permissions.can?(:browse, @collection)
|
17
18
|
|
18
19
|
filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
|
19
|
-
condition_tree:
|
20
|
+
condition_tree: ConditionTreeFactory.intersect([
|
21
|
+
@permissions.get_scope(@collection),
|
22
|
+
ForestAdminAgent::Utils::QueryStringParser.parse_condition_tree(
|
23
|
+
@collection, args
|
24
|
+
)
|
25
|
+
]),
|
20
26
|
page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args)
|
21
27
|
)
|
22
28
|
projection = ForestAdminAgent::Utils::QueryStringParser.parse_projection_with_pks(@collection, args)
|
23
|
-
records = @collection.list(caller, filter, projection)
|
29
|
+
records = @collection.list(@caller, filter, projection)
|
24
30
|
|
25
31
|
{
|
26
32
|
name: args[:params]['collection_name'],
|
@@ -8,6 +8,7 @@ module ForestAdminAgent
|
|
8
8
|
include ForestAdminAgent::Builder
|
9
9
|
include ForestAdminDatasourceToolkit::Utils
|
10
10
|
include ForestAdminDatasourceToolkit::Components::Query
|
11
|
+
|
11
12
|
def setup_routes
|
12
13
|
add_route(
|
13
14
|
'forest_related_associate',
|
@@ -21,6 +22,7 @@ module ForestAdminAgent
|
|
21
22
|
|
22
23
|
def handle_request(args = {})
|
23
24
|
build(args)
|
25
|
+
@permissions.can?(:edit, @collection)
|
24
26
|
|
25
27
|
parent_id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
|
26
28
|
target_relation_id = Utils::Id.unpack_id(@child_collection, args[:params]['data'][0]['id'], with_key: true)
|
@@ -40,7 +42,14 @@ module ForestAdminAgent
|
|
40
42
|
def associate_one_to_many(relation, parent_id, target_relation_id)
|
41
43
|
id = Schema.primary_keys(@child_collection)[0]
|
42
44
|
value = Collection.get_value(@child_collection, @caller, target_relation_id, id)
|
43
|
-
filter = Filter.new(
|
45
|
+
filter = Filter.new(
|
46
|
+
condition_tree: ConditionTree::ConditionTreeFactory.intersect(
|
47
|
+
[
|
48
|
+
ConditionTree::Nodes::ConditionTreeLeaf.new(id, 'Equal', value),
|
49
|
+
@permissions.get_scope(@collection)
|
50
|
+
]
|
51
|
+
)
|
52
|
+
)
|
44
53
|
value = Collection.get_value(@collection, @caller, parent_id, relation.origin_key_target)
|
45
54
|
|
46
55
|
@child_collection.update(@caller, filter, { relation.origin_key => value })
|
@@ -21,9 +21,10 @@ module ForestAdminAgent
|
|
21
21
|
|
22
22
|
def handle_request(args = {})
|
23
23
|
build(args)
|
24
|
+
@permissions.can?(:browse, @collection)
|
24
25
|
|
25
26
|
if @child_collection.is_countable?
|
26
|
-
filter = Filter.new
|
27
|
+
filter = Filter.new(condition_tree: @permissions.get_scope(@collection))
|
27
28
|
id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
|
28
29
|
result = Collection.aggregate_relation(
|
29
30
|
@collection,
|
@@ -21,6 +21,7 @@ module ForestAdminAgent
|
|
21
21
|
|
22
22
|
def handle_request(args = {})
|
23
23
|
build(args)
|
24
|
+
@permissions.can?(:delete, @collection)
|
24
25
|
|
25
26
|
parent_id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
|
26
27
|
is_delete_mode = !args.dig(:params, :delete).nil?
|
@@ -84,6 +85,7 @@ module ForestAdminAgent
|
|
84
85
|
Filter.new(
|
85
86
|
condition_tree: ConditionTree::ConditionTreeFactory.intersect(
|
86
87
|
[
|
88
|
+
@permissions.get_scope(@child_collection),
|
87
89
|
Utils::QueryStringParser.parse_condition_tree(@child_collection, args),
|
88
90
|
selected_ids
|
89
91
|
]
|
@@ -7,6 +7,8 @@ module ForestAdminAgent
|
|
7
7
|
class ListRelated < AbstractRelatedRoute
|
8
8
|
include ForestAdminAgent::Builder
|
9
9
|
include ForestAdminDatasourceToolkit::Utils
|
10
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
|
11
|
+
|
10
12
|
def setup_routes
|
11
13
|
add_route(
|
12
14
|
'forest_related_list',
|
@@ -20,10 +22,16 @@ module ForestAdminAgent
|
|
20
22
|
|
21
23
|
def handle_request(args = {})
|
22
24
|
build(args)
|
25
|
+
@permissions.can?(:browse, @collection)
|
23
26
|
# TODO: add csv behaviour
|
24
27
|
|
25
28
|
filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
|
26
|
-
condition_tree:
|
29
|
+
condition_tree: ConditionTreeFactory.intersect(
|
30
|
+
[
|
31
|
+
@permissions.get_scope(@collection),
|
32
|
+
ForestAdminAgent::Utils::QueryStringParser.parse_condition_tree(@child_collection, args)
|
33
|
+
]
|
34
|
+
),
|
27
35
|
page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args)
|
28
36
|
)
|
29
37
|
projection = ForestAdminAgent::Utils::QueryStringParser.parse_projection_with_pks(@child_collection, args)
|
@@ -21,6 +21,7 @@ module ForestAdminAgent
|
|
21
21
|
|
22
22
|
def handle_request(args = {})
|
23
23
|
build(args)
|
24
|
+
@permissions.can?(:edit, @collection)
|
24
25
|
|
25
26
|
relation = @collection.fields[args[:params]['relation_name']]
|
26
27
|
parent_id = Utils::Id.unpack_id(@collection, args[:params]['id'])
|
@@ -61,6 +62,7 @@ module ForestAdminAgent
|
|
61
62
|
old_fk_owner_filter = Filter.new(
|
62
63
|
condition_tree: ConditionTree::ConditionTreeFactory.intersect(
|
63
64
|
[
|
65
|
+
@permissions.get_scope(@collection),
|
64
66
|
ConditionTree::Nodes::ConditionTreeLeaf.new(
|
65
67
|
relation.origin_key,
|
66
68
|
ConditionTree::Operators::EQUAL,
|
@@ -91,7 +93,12 @@ module ForestAdminAgent
|
|
91
93
|
|
92
94
|
@child_collection.update(
|
93
95
|
@caller,
|
94
|
-
Filter.new(condition_tree:
|
96
|
+
Filter.new(condition_tree: ConditionTree::ConditionTreeFactory.intersect(
|
97
|
+
[
|
98
|
+
@permissions.get_scope(@collection),
|
99
|
+
new_fk_owner
|
100
|
+
]
|
101
|
+
)),
|
95
102
|
{ relation.origin_key => origin_value }
|
96
103
|
)
|
97
104
|
end
|
@@ -15,16 +15,18 @@ module ForestAdminAgent
|
|
15
15
|
|
16
16
|
def handle_request(args = {})
|
17
17
|
build(args)
|
18
|
+
@permissions.can?(:read, @collection)
|
19
|
+
scope = @permissions.get_scope(@collection)
|
18
20
|
id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
|
19
|
-
caller = ForestAdminAgent::Utils::QueryStringParser.parse_caller(args)
|
20
21
|
condition_tree = ConditionTree::ConditionTreeFactory.match_records(@collection, [id])
|
21
22
|
filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
|
22
|
-
condition_tree: condition_tree,
|
23
|
+
condition_tree: ConditionTree::ConditionTreeFactory.intersect([condition_tree, scope]),
|
23
24
|
page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args)
|
24
25
|
)
|
26
|
+
|
25
27
|
projection = ProjectionFactory.all(@collection)
|
26
28
|
|
27
|
-
records = @collection.list(caller, filter, projection)
|
29
|
+
records = @collection.list(@caller, filter, projection)
|
28
30
|
|
29
31
|
raise Http::Exceptions::NotFoundError, 'Record does not exists' unless records.size.positive?
|
30
32
|
|
@@ -16,16 +16,17 @@ module ForestAdminAgent
|
|
16
16
|
|
17
17
|
def handle_request(args = {})
|
18
18
|
build(args)
|
19
|
+
@permissions.can?(:edit, @collection)
|
20
|
+
scope = @permissions.get_scope(@collection)
|
19
21
|
id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
|
20
|
-
caller = ForestAdminAgent::Utils::QueryStringParser.parse_caller(args)
|
21
22
|
condition_tree = ConditionTree::ConditionTreeFactory.match_records(@collection, [id])
|
22
23
|
filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
|
23
|
-
condition_tree: condition_tree,
|
24
|
+
condition_tree: ConditionTree::ConditionTreeFactory.intersect([condition_tree, scope]),
|
24
25
|
page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args)
|
25
26
|
)
|
26
27
|
data = format_attributes(args)
|
27
28
|
@collection.update(@caller, filter, data)
|
28
|
-
records = @collection.list(caller, filter, ProjectionFactory.all(@collection))
|
29
|
+
records = @collection.list(@caller, filter, ProjectionFactory.all(@collection))
|
29
30
|
|
30
31
|
{
|
31
32
|
name: args[:params]['collection_name'],
|
@@ -0,0 +1,264 @@
|
|
1
|
+
require 'filecache'
|
2
|
+
|
3
|
+
module ForestAdminAgent
|
4
|
+
module Services
|
5
|
+
class Permissions
|
6
|
+
include ForestAdminAgent::Http::Exceptions
|
7
|
+
include ForestAdminAgent::Utils
|
8
|
+
include ForestAdminDatasourceToolkit::Exceptions
|
9
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
|
10
|
+
|
11
|
+
attr_reader :caller, :forest_api, :cache
|
12
|
+
|
13
|
+
def initialize(caller)
|
14
|
+
@caller = caller
|
15
|
+
@forest_api = ForestAdminAgent::Http::ForestAdminApiRequester.new
|
16
|
+
@cache = FileCache.new(
|
17
|
+
'permissions',
|
18
|
+
Facades::Container.config_from_cache[:cache_dir].to_s,
|
19
|
+
Facades::Container.config_from_cache[:permission_expiration]
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.invalidate_cache(id_cache = nil)
|
24
|
+
cache = FileCache.new(
|
25
|
+
'permissions',
|
26
|
+
Facades::Container.config_from_cache[:cache_dir].to_s,
|
27
|
+
Facades::Container.config_from_cache[:permission_expiration]
|
28
|
+
)
|
29
|
+
|
30
|
+
cache.clear if id_cache.nil?
|
31
|
+
|
32
|
+
cache.delete(id_cache) unless cache.get(id_cache).nil?
|
33
|
+
|
34
|
+
# TODO: HANDLE LOGGER
|
35
|
+
# logger.debug("Invalidating #{id_cache} cache..")
|
36
|
+
end
|
37
|
+
|
38
|
+
def can?(action, collection, allow_fetch: false)
|
39
|
+
return true unless permission_system?
|
40
|
+
|
41
|
+
user_data = get_user_data(caller.id)
|
42
|
+
collections_data = get_collections_permissions_data(force_fetch: allow_fetch)
|
43
|
+
|
44
|
+
is_allowed = collections_data.key?(collection.name.to_sym) && collections_data[collection.name.to_sym][action].include?(user_data[:roleId])
|
45
|
+
|
46
|
+
# Refetch
|
47
|
+
unless is_allowed
|
48
|
+
collections_data = get_collections_permissions_data(force_fetch: true)
|
49
|
+
is_allowed = collections_data[collection.name.to_sym][action].include?(user_data[:roleId])
|
50
|
+
end
|
51
|
+
|
52
|
+
# still not allowed - throw forbidden message
|
53
|
+
raise ForbiddenError, "You don't have permission to #{action} this collection." unless is_allowed
|
54
|
+
|
55
|
+
is_allowed
|
56
|
+
end
|
57
|
+
|
58
|
+
def can_chart?(parameters)
|
59
|
+
attributes = sanitize_chart_parameters(parameters)
|
60
|
+
hash_request = "#{attributes[:type]}:#{array_hash(attributes)}"
|
61
|
+
is_allowed = get_chart_data(caller.rendering_id).include?(hash_request)
|
62
|
+
|
63
|
+
# Refetch
|
64
|
+
is_allowed ||= get_chart_data(caller.rendering_id, force_fetch: true).include?(hash_request)
|
65
|
+
|
66
|
+
# still not allowed - throw forbidden message
|
67
|
+
unless is_allowed
|
68
|
+
# TODO: HANDLE LOGGER
|
69
|
+
# logger.debug("User #{caller.id} cannot retrieve chart on rendering #{caller.rendering_id}")
|
70
|
+
raise ForbiddenError, "You don't have permission to access this collection."
|
71
|
+
end
|
72
|
+
|
73
|
+
# TODO: HANDLE LOGGER
|
74
|
+
# logger.debug("User #{caller.id} can retrieve chart on rendering #{caller.rendering_id}")
|
75
|
+
|
76
|
+
is_allowed
|
77
|
+
end
|
78
|
+
|
79
|
+
def can_smart_action?(request, collection, filter, allow_fetch: true)
|
80
|
+
return true unless permission_system?
|
81
|
+
|
82
|
+
user_data = get_user_data(caller.id)
|
83
|
+
collections_data = get_collections_permissions_data(force_fetch: allow_fetch)
|
84
|
+
action = find_action_from_endpoint(collection.name, request[:headers]['REQUEST_PATH'], request[:headers]['REQUEST_METHOD'])
|
85
|
+
smart_action_approval = SmartActionChecker.new(
|
86
|
+
request[:params],
|
87
|
+
collection,
|
88
|
+
collections_data[collection.name.to_sym][:actions][action[:name]],
|
89
|
+
caller,
|
90
|
+
user_data[:roleId],
|
91
|
+
filter
|
92
|
+
)
|
93
|
+
|
94
|
+
smart_action_approval.can_execute?
|
95
|
+
# TODO: HANDLE LOGGER
|
96
|
+
# logger.debug("User #{user_data[:roleId]} is #{is_allowed ? '' : 'not'} allowed to perform #{action['name']}")
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_scope(collection)
|
100
|
+
permissions = get_scope_and_team_data(caller.rendering_id)
|
101
|
+
scope = permissions[:scopes][collection.name.to_sym]
|
102
|
+
team = permissions[:team]
|
103
|
+
user = get_user_data(caller.id)
|
104
|
+
|
105
|
+
return nil if scope.nil?
|
106
|
+
|
107
|
+
context_variables = ContextVariables.new(team, user)
|
108
|
+
|
109
|
+
ContextVariablesInjector.inject_context_in_filter(scope, context_variables)
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_user_data(user_id)
|
113
|
+
cache.get_or_set('forest.users') do
|
114
|
+
response = fetch('/liana/v4/permissions/users')
|
115
|
+
users = {}
|
116
|
+
|
117
|
+
response.each do |user|
|
118
|
+
users[user[:id].to_s] = user
|
119
|
+
end
|
120
|
+
|
121
|
+
# TODO: HANDLE LOGGER
|
122
|
+
# logger.debug('Refreshing user permissions cache')
|
123
|
+
|
124
|
+
users
|
125
|
+
end[user_id.to_s]
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_team(rendering_id)
|
129
|
+
permissions = get_scope_and_team_data(rendering_id)
|
130
|
+
|
131
|
+
permissions[:team]
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def get_collections_permissions_data(force_fetch: false)
|
137
|
+
self.class.invalidate_cache('forest.collections') if force_fetch == true
|
138
|
+
|
139
|
+
cache.get_or_set('forest.collections') do
|
140
|
+
response = fetch('/liana/v4/permissions/environment')
|
141
|
+
collections = {}
|
142
|
+
|
143
|
+
response[:collections].each do |name, collection|
|
144
|
+
collections[name] = decode_crud_permissions(collection).merge(decode_action_permissions(collection))
|
145
|
+
end
|
146
|
+
|
147
|
+
# TODO: HANDLE LOGGER
|
148
|
+
# logger.debug('Fetching environment permissions')
|
149
|
+
|
150
|
+
collections
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_chart_data(rendering_id, force_fetch: false)
|
155
|
+
self.class.invalidate_cache('forest.stats') if force_fetch == true
|
156
|
+
|
157
|
+
cache.get_or_set('forest.stats') do
|
158
|
+
response = fetch("/liana/v4/permissions/renderings/#{rendering_id}")
|
159
|
+
stat_hash = []
|
160
|
+
response[:stats].each do |stat|
|
161
|
+
stat = stat.select { |_, value| !value.nil? && value != '' }
|
162
|
+
stat_hash << "#{stat[:type]}:#{array_hash(stat)}"
|
163
|
+
end
|
164
|
+
|
165
|
+
# TODO: HANDLE LOGGER
|
166
|
+
# logger.debug("Loading rendering permissions for rendering #{rendering_id}")
|
167
|
+
|
168
|
+
stat_hash
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def sanitize_chart_parameters(parameters)
|
173
|
+
# parameters = parameters.to_h
|
174
|
+
parameters.delete(:timezone)
|
175
|
+
parameters.delete(:collection)
|
176
|
+
parameters.delete(:contextVariables)
|
177
|
+
|
178
|
+
parameters.select { |_, value| !value.nil? && value != '' }
|
179
|
+
end
|
180
|
+
|
181
|
+
def array_hash(data)
|
182
|
+
Digest::SHA1.hexdigest(data.sort.to_h.to_s)
|
183
|
+
end
|
184
|
+
|
185
|
+
def get_scope_and_team_data(rendering_id)
|
186
|
+
cache.get_or_set('forest.scopes') do
|
187
|
+
data = {}
|
188
|
+
response = fetch("/liana/v4/permissions/renderings/#{rendering_id}")
|
189
|
+
|
190
|
+
data[:scopes] = decode_scope_permissions(response[:collections])
|
191
|
+
data[:team] = response[:team]
|
192
|
+
|
193
|
+
data
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def permission_system?
|
198
|
+
cache.get_or_set('forest.has_permission') do
|
199
|
+
response = fetch('/liana/v4/permissions/environment')
|
200
|
+
{ enable: !response.nil? }
|
201
|
+
end[:enable]
|
202
|
+
end
|
203
|
+
|
204
|
+
def find_action_from_endpoint(collection_name, endpoint, http_method)
|
205
|
+
schema_file = JSON.parse(File.read(Facades::Container.config_from_cache[:schema_path]))
|
206
|
+
actions = schema_file['collections']&.select { |collection| collection['name'] == collection_name }&.first&.dig('actions')
|
207
|
+
|
208
|
+
return nil if actions.nil? || actions.empty?
|
209
|
+
|
210
|
+
action = actions.find { |a| a['endpoint'] == endpoint && a['http_method'].casecmp(http_method).zero? }
|
211
|
+
|
212
|
+
raise ForestException, "The collection #{collection_name} does not have this smart action" if action.nil?
|
213
|
+
|
214
|
+
action
|
215
|
+
end
|
216
|
+
|
217
|
+
def decode_crud_permissions(collection)
|
218
|
+
{
|
219
|
+
browse: collection[:collection][:browseEnabled][:roles],
|
220
|
+
read: collection[:collection][:readEnabled][:roles],
|
221
|
+
edit: collection[:collection][:editEnabled][:roles],
|
222
|
+
add: collection[:collection][:addEnabled][:roles],
|
223
|
+
delete: collection[:collection][:deleteEnabled][:roles],
|
224
|
+
export: collection[:collection][:exportEnabled][:roles]
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
228
|
+
def decode_action_permissions(collection)
|
229
|
+
actions = {}
|
230
|
+
actions[:actions] = {}
|
231
|
+
collection[:actions].each do |id, action|
|
232
|
+
actions[:actions][id] = {
|
233
|
+
triggerEnabled: action[:triggerEnabled][:roles],
|
234
|
+
triggerConditions: action[:triggerConditions],
|
235
|
+
approvalRequired: action[:approvalRequired][:roles],
|
236
|
+
approvalRequiredConditions: action[:approvalRequiredConditions],
|
237
|
+
userApprovalEnabled: action[:userApprovalEnabled][:roles],
|
238
|
+
userApprovalConditions: action[:userApprovalConditions],
|
239
|
+
selfApprovalEnabled: action[:selfApprovalEnabled][:roles]
|
240
|
+
}
|
241
|
+
end
|
242
|
+
|
243
|
+
actions
|
244
|
+
end
|
245
|
+
|
246
|
+
def decode_scope_permissions(raw_permissions)
|
247
|
+
scopes = {}
|
248
|
+
raw_permissions.each do |collection_name, value|
|
249
|
+
scopes[collection_name] = ConditionTreeFactory.from_plain_object(value[:scope]) unless value[:scope].nil?
|
250
|
+
end
|
251
|
+
|
252
|
+
scopes
|
253
|
+
end
|
254
|
+
|
255
|
+
def fetch(url)
|
256
|
+
response = forest_api.get(url)
|
257
|
+
|
258
|
+
JSON.parse(response.body, symbolize_names: true)
|
259
|
+
rescue StandardError => e
|
260
|
+
forest_api.handle_response_error(e)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Services
|
3
|
+
class SmartActionChecker
|
4
|
+
include ForestAdminAgent::Http::Exceptions
|
5
|
+
include ForestAdminAgent::Utils
|
6
|
+
include ForestAdminDatasourceToolkit::Utils
|
7
|
+
include ForestAdminDatasourceToolkit::Components::Query
|
8
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
|
9
|
+
|
10
|
+
attr_reader :parameters, :collection, :smart_action, :caller, :role_id, :filter, :attributes
|
11
|
+
|
12
|
+
TRIGGER_FORBIDDEN_ERROR = 'CustomActionTriggerForbiddenError'.freeze
|
13
|
+
|
14
|
+
REQUIRE_APPROVAL_ERROR = 'CustomActionRequiresApprovalError'.freeze
|
15
|
+
|
16
|
+
INVALID_ACTION_CONDITION_ERROR = 'InvalidActionConditionError'.freeze
|
17
|
+
|
18
|
+
def initialize(parameters, collection, smart_action, caller, role_id, filter)
|
19
|
+
@parameters = parameters
|
20
|
+
@collection = collection
|
21
|
+
@smart_action = smart_action
|
22
|
+
@caller = caller
|
23
|
+
@role_id = role_id
|
24
|
+
@filter = filter
|
25
|
+
@attributes = parameters[:data][:attributes]
|
26
|
+
end
|
27
|
+
|
28
|
+
def can_execute?
|
29
|
+
if attributes[:signed_approval_request].nil?
|
30
|
+
can_trigger?
|
31
|
+
else
|
32
|
+
can_approve?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def can_approve?
|
39
|
+
if smart_action[:userApprovalEnabled].include?(role_id) &&
|
40
|
+
(smart_action[:userApprovalConditions].empty? || match_conditions(:userApprovalConditions)) &&
|
41
|
+
(attributes[:requester_id] != caller.id || smart_action[:selfApprovalEnabled].include?(role_id))
|
42
|
+
return true
|
43
|
+
end
|
44
|
+
|
45
|
+
raise ForbiddenError.new('You don\'t have the permission to trigger this action.', TRIGGER_FORBIDDEN_ERROR)
|
46
|
+
end
|
47
|
+
|
48
|
+
def can_trigger?
|
49
|
+
if smart_action[:triggerEnabled].include?(role_id) && !smart_action[:approvalRequired].include?(role_id)
|
50
|
+
return true if smart_action[:triggerConditions].empty? || match_conditions(:triggerConditions)
|
51
|
+
elsif smart_action[:approvalRequired].include?(role_id) && smart_action[:triggerEnabled].include?(role_id)
|
52
|
+
if smart_action[:approvalRequiredConditions].empty? || match_conditions(:approvalRequiredConditions)
|
53
|
+
raise RequireApproval.new(
|
54
|
+
'This action requires to be approved.',
|
55
|
+
REQUIRE_APPROVAL_ERROR,
|
56
|
+
smart_action[:userApprovalEnabled]
|
57
|
+
)
|
58
|
+
elsif smart_action[:triggerConditions].empty? || match_conditions(:triggerConditions)
|
59
|
+
return true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
raise ForbiddenError.new('You don\'t have the permission to trigger this action.', TRIGGER_FORBIDDEN_ERROR)
|
64
|
+
end
|
65
|
+
|
66
|
+
def match_conditions(condition_name)
|
67
|
+
pk = Schema.primary_keys(collection)[0]
|
68
|
+
condition_filter = if attributes[:all_records]
|
69
|
+
Nodes::ConditionTreeLeaf.new(pk, 'NOT_EQUAL', attributes[:all_records_ids_excluded])
|
70
|
+
else
|
71
|
+
Nodes::ConditionTreeLeaf.new(pk, 'IN', attributes[:ids])
|
72
|
+
end
|
73
|
+
|
74
|
+
condition = smart_action[condition_name][0]['filter']
|
75
|
+
conditional_filter = filter.override(
|
76
|
+
condition_tree: ConditionTreeFactory.intersect(
|
77
|
+
[
|
78
|
+
ConditionTreeParser.from_plain_object(collection, condition),
|
79
|
+
filter.condition_tree,
|
80
|
+
condition_filter
|
81
|
+
]
|
82
|
+
)
|
83
|
+
)
|
84
|
+
rows = collection.aggregate(caller, conditional_filter, Aggregation.new(operation: 'Count'))
|
85
|
+
|
86
|
+
(rows[0]['value'] || 0) == attributes[:ids].count
|
87
|
+
rescue StandardError
|
88
|
+
raise ConflictError.new(
|
89
|
+
'The conditions to trigger this action cannot be verified. Please contact an administrator.',
|
90
|
+
INVALID_ACTION_CONDITION_ERROR
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'ld-eventsource'
|
2
|
+
|
3
|
+
module ForestAdminAgent
|
4
|
+
module Services
|
5
|
+
class SSECacheInvalidation
|
6
|
+
include ForestAdminDatasourceToolkit::Exceptions
|
7
|
+
|
8
|
+
MESSAGE_CACHE_KEYS = {
|
9
|
+
'refresh-users': %w[forest.users],
|
10
|
+
'refresh-roles': %w[forest.collections],
|
11
|
+
'refresh-renderings': %w[forest.collections forest.stats forest.scopes]
|
12
|
+
# TODO: add one for ip whitelist when server implement it
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def self.run
|
16
|
+
uri = "#{Facades::Container.config_from_cache[:forest_server_url]}/liana/v4/subscribe-to-events"
|
17
|
+
headers = {
|
18
|
+
'forest-secret-key' => Facades::Container.config_from_cache[:env_secret],
|
19
|
+
'Accept' => 'text/event-stream'
|
20
|
+
}
|
21
|
+
|
22
|
+
begin
|
23
|
+
SSE::Client.new(uri, headers: headers) do |client|
|
24
|
+
client.on_event do |event|
|
25
|
+
next if event.type == :heartbeat
|
26
|
+
|
27
|
+
MESSAGE_CACHE_KEYS[event.type]&.each do |cache_key|
|
28
|
+
Permissions.invalidate_cache(cache_key)
|
29
|
+
# TODO: HANDLE LOGGER
|
30
|
+
# "info","invalidate cache {MESSAGE_CACHE_KEYS[event.type]} for event {event.type}"
|
31
|
+
end
|
32
|
+
# TODO: HANDLE LOGGER add else
|
33
|
+
# "info", "SSECacheInvalidation: unhandled message from server: {event.type}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
rescue StandardError
|
37
|
+
raise ForestException, 'Failed to reach SSE data from ForestAdmin server.'
|
38
|
+
# TODO: HANDLE LOGGER
|
39
|
+
# "debug", "SSE connection to forestadmin server due to ..."
|
40
|
+
# "warning", "SSE connection to forestadmin server closed unexpectedly, retrying."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Utils
|
3
|
+
class ContextVariables
|
4
|
+
attr_reader :team, :user, :request_context_variables
|
5
|
+
|
6
|
+
USER_VALUE_PREFIX = 'currentUser.'.freeze
|
7
|
+
|
8
|
+
USER_VALUE_TAG_PREFIX = 'currentUser.tags.'.freeze
|
9
|
+
|
10
|
+
USER_VALUE_TEAM_PREFIX = 'currentUser.team.'.freeze
|
11
|
+
|
12
|
+
def initialize(team, user, request_context_variables = nil)
|
13
|
+
@team = team.transform_keys(&:to_sym)
|
14
|
+
@user = user.transform_keys(&:to_sym)
|
15
|
+
@request_context_variables = request_context_variables
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_value(context_variable_key)
|
19
|
+
return get_current_user_data(context_variable_key) if context_variable_key.start_with?(USER_VALUE_PREFIX)
|
20
|
+
|
21
|
+
request_context_variables[context_variable_key]
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def get_current_user_data(context_variable_key)
|
27
|
+
if context_variable_key.start_with?(USER_VALUE_TEAM_PREFIX)
|
28
|
+
return team[context_variable_key[USER_VALUE_TEAM_PREFIX.length..].to_sym]
|
29
|
+
end
|
30
|
+
|
31
|
+
if context_variable_key.start_with?(USER_VALUE_TAG_PREFIX)
|
32
|
+
return user[:tags][context_variable_key[USER_VALUE_TAG_PREFIX.length..]]
|
33
|
+
end
|
34
|
+
|
35
|
+
user[context_variable_key[USER_VALUE_PREFIX.length..].to_sym]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Utils
|
3
|
+
class ContextVariablesInjector
|
4
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree::Nodes
|
5
|
+
|
6
|
+
def self.inject_context_in_value(value, context_variables)
|
7
|
+
inject_context_in_value_custom(value) do |context_variable_key|
|
8
|
+
context_variables.get_value(context_variable_key).to_s
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.inject_context_in_value_custom(value)
|
13
|
+
return value unless value.is_a?(String)
|
14
|
+
|
15
|
+
value_with_context_variables_injected = value
|
16
|
+
regex = /{{([^}]+)}}/
|
17
|
+
encountered_variables = []
|
18
|
+
|
19
|
+
while (match = regex.match(value_with_context_variables_injected))
|
20
|
+
context_variable_key = match[1]
|
21
|
+
|
22
|
+
unless encountered_variables.include?(context_variable_key)
|
23
|
+
value_with_context_variables_injected.gsub!(
|
24
|
+
/{{#{context_variable_key}}}/,
|
25
|
+
yield(context_variable_key)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
encountered_variables.push(context_variable_key)
|
30
|
+
end
|
31
|
+
|
32
|
+
value_with_context_variables_injected
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.inject_context_in_filter(filter, context_variables)
|
36
|
+
return nil unless filter
|
37
|
+
|
38
|
+
if filter.is_a?(ConditionTreeBranch)
|
39
|
+
return ConditionTreeBranch.new(
|
40
|
+
filter.aggregator,
|
41
|
+
filter.conditions.map { |condition| inject_context_in_filter(condition, context_variables) }
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
ConditionTreeLeaf.new(
|
46
|
+
filter.field,
|
47
|
+
filter.operator,
|
48
|
+
inject_context_in_value(filter.value, context_variables)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/forest_admin_agent.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forest_admin_agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.pre.beta.
|
4
|
+
version: 1.0.0.pre.beta.22
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthieu
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-12-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -53,6 +53,20 @@ dependencies:
|
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '2.7'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: filecache
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '1.0'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.0'
|
56
70
|
- !ruby/object:Gem::Dependency
|
57
71
|
name: ipaddress
|
58
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -96,19 +110,19 @@ dependencies:
|
|
96
110
|
- !ruby/object:Gem::Version
|
97
111
|
version: '2.7'
|
98
112
|
- !ruby/object:Gem::Dependency
|
99
|
-
name:
|
113
|
+
name: ld-eventsource
|
100
114
|
requirement: !ruby/object:Gem::Requirement
|
101
115
|
requirements:
|
102
116
|
- - "~>"
|
103
117
|
- !ruby/object:Gem::Version
|
104
|
-
version:
|
118
|
+
version: '2.2'
|
105
119
|
type: :runtime
|
106
120
|
prerelease: false
|
107
121
|
version_requirements: !ruby/object:Gem::Requirement
|
108
122
|
requirements:
|
109
123
|
- - "~>"
|
110
124
|
- !ruby/object:Gem::Version
|
111
|
-
version:
|
125
|
+
version: '2.2'
|
112
126
|
- !ruby/object:Gem::Dependency
|
113
127
|
name: mono_logger
|
114
128
|
requirement: !ruby/object:Gem::Requirement
|
@@ -205,8 +219,11 @@ files:
|
|
205
219
|
- lib/forest_admin_agent/facades/container.rb
|
206
220
|
- lib/forest_admin_agent/facades/whitelist.rb
|
207
221
|
- lib/forest_admin_agent/http/Exceptions/authentication_open_id_client.rb
|
222
|
+
- lib/forest_admin_agent/http/Exceptions/conflict_error.rb
|
223
|
+
- lib/forest_admin_agent/http/Exceptions/forbidden_error.rb
|
208
224
|
- lib/forest_admin_agent/http/Exceptions/http_exception.rb
|
209
225
|
- lib/forest_admin_agent/http/Exceptions/not_found_error.rb
|
226
|
+
- lib/forest_admin_agent/http/Exceptions/require_approval.rb
|
210
227
|
- lib/forest_admin_agent/http/forest_admin_api_requester.rb
|
211
228
|
- lib/forest_admin_agent/http/router.rb
|
212
229
|
- lib/forest_admin_agent/routes/abstract_authenticated_route.rb
|
@@ -229,7 +246,12 @@ files:
|
|
229
246
|
- lib/forest_admin_agent/serializer/forest_serializer_override.rb
|
230
247
|
- lib/forest_admin_agent/services/ip_whitelist.rb
|
231
248
|
- lib/forest_admin_agent/services/logger_service.rb
|
249
|
+
- lib/forest_admin_agent/services/permissions.rb
|
250
|
+
- lib/forest_admin_agent/services/smart_action_checker.rb
|
251
|
+
- lib/forest_admin_agent/services/sse_cache_invalidation.rb
|
232
252
|
- lib/forest_admin_agent/utils/condition_tree_parser.rb
|
253
|
+
- lib/forest_admin_agent/utils/context_variables.rb
|
254
|
+
- lib/forest_admin_agent/utils/context_variables_injector.rb
|
233
255
|
- lib/forest_admin_agent/utils/error_messages.rb
|
234
256
|
- lib/forest_admin_agent/utils/id.rb
|
235
257
|
- lib/forest_admin_agent/utils/query_string_parser.rb
|