forest_admin_agent 1.0.0.pre.beta.21 → 1.0.0.pre.beta.22
Sign up to get free protection for your applications and to get access to all the features.
- 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
|