forest_admin_agent 1.0.0.pre.beta.21
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +5 -0
- data/README.md +35 -0
- data/Rakefile +10 -0
- data/forest_admin +1 -0
- data/forest_admin_agent.gemspec +48 -0
- data/lib/forest_admin_agent/auth/auth_manager.rb +50 -0
- data/lib/forest_admin_agent/auth/oauth2/forest_provider.rb +62 -0
- data/lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb +42 -0
- data/lib/forest_admin_agent/auth/oauth2/oidc_config.rb +29 -0
- data/lib/forest_admin_agent/auth/oidc_client_manager.rb +71 -0
- data/lib/forest_admin_agent/builder/agent_factory.rb +77 -0
- data/lib/forest_admin_agent/facades/container.rb +23 -0
- data/lib/forest_admin_agent/facades/whitelist.rb +13 -0
- data/lib/forest_admin_agent/http/Exceptions/authentication_open_id_client.rb +16 -0
- data/lib/forest_admin_agent/http/Exceptions/http_exception.rb +17 -0
- data/lib/forest_admin_agent/http/Exceptions/not_found_error.rb +15 -0
- data/lib/forest_admin_agent/http/forest_admin_api_requester.rb +28 -0
- data/lib/forest_admin_agent/http/router.rb +52 -0
- data/lib/forest_admin_agent/routes/abstract_authenticated_route.rb +25 -0
- data/lib/forest_admin_agent/routes/abstract_related_route.rb +12 -0
- data/lib/forest_admin_agent/routes/abstract_route.rb +27 -0
- data/lib/forest_admin_agent/routes/resources/count.rb +41 -0
- data/lib/forest_admin_agent/routes/resources/delete.rb +51 -0
- data/lib/forest_admin_agent/routes/resources/list.rb +38 -0
- data/lib/forest_admin_agent/routes/resources/related/associate_related.rb +63 -0
- data/lib/forest_admin_agent/routes/resources/related/count_related.rb +56 -0
- data/lib/forest_admin_agent/routes/resources/related/dissociate_related.rb +97 -0
- data/lib/forest_admin_agent/routes/resources/related/list_related.rb +54 -0
- data/lib/forest_admin_agent/routes/resources/related/update_related.rb +102 -0
- data/lib/forest_admin_agent/routes/resources/show.rb +44 -0
- data/lib/forest_admin_agent/routes/resources/store.rb +51 -0
- data/lib/forest_admin_agent/routes/resources/update.rb +42 -0
- data/lib/forest_admin_agent/routes/security/authentication.rb +95 -0
- data/lib/forest_admin_agent/routes/system/health_check.rb +22 -0
- data/lib/forest_admin_agent/serializer/forest_serializer.rb +176 -0
- data/lib/forest_admin_agent/serializer/forest_serializer_override.rb +103 -0
- data/lib/forest_admin_agent/services/ip_whitelist.rb +100 -0
- data/lib/forest_admin_agent/services/logger_service.rb +20 -0
- data/lib/forest_admin_agent/utils/condition_tree_parser.rb +57 -0
- data/lib/forest_admin_agent/utils/error_messages.rb +38 -0
- data/lib/forest_admin_agent/utils/id.rb +48 -0
- data/lib/forest_admin_agent/utils/query_string_parser.rb +89 -0
- data/lib/forest_admin_agent/utils/schema/frontend_filterable.rb +73 -0
- data/lib/forest_admin_agent/utils/schema/generator_collection.rb +35 -0
- data/lib/forest_admin_agent/utils/schema/generator_field.rb +183 -0
- data/lib/forest_admin_agent/utils/schema/schema_emitter.rb +103 -0
- data/lib/forest_admin_agent/version.rb +3 -0
- data/lib/forest_admin_agent.rb +11 -0
- data/sig/forest_admin_agent/auth/auth_manager.rbs +14 -0
- data/sig/forest_admin_agent/auth/oauth2/forest_provider.rbs +16 -0
- data/sig/forest_admin_agent/auth/oauth2/forest_resource_owner.rbs +15 -0
- data/sig/forest_admin_agent/auth/oidc_client_manager.rbs +15 -0
- data/sig/forest_admin_agent/builder/agent_factory.rbs +21 -0
- data/sig/forest_admin_agent/facades/container.rbs +9 -0
- data/sig/forest_admin_agent/http/router.rbs +9 -0
- data/sig/forest_admin_agent/routes/abstract_route.rbs +12 -0
- data/sig/forest_admin_agent/routes/security/authentication.rbs +14 -0
- data/sig/forest_admin_agent/routes/system/health_check.rbs +10 -0
- data/sig/forest_admin_agent.rbs +4 -0
- metadata +279 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a86f151f54daa3a9a8f46251c380448eed7c0fdf90ff82818a37fa213a868557
|
4
|
+
data.tar.gz: 2af48bff7f6a6691f2fe495509dba2669ec64d9197886167a1de316c24846263
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 006cdc9ffa0c0afcb5ae7efb9a401b5964d379834bbf416d9f5cd7400de8425c83867ae2e0a83cd5a6f7d9de4043c018c3f2a650cab00383a576baf364ebaf22
|
7
|
+
data.tar.gz: 3d04663fff62e53935d54c3a9312d5c575ff5b78ce7bd5fc2be1e73021f7dbda7ac43fe5f30dc141f07f6d9fb98cb23f54c093be65b2c8cb866daabfd8f97fcf
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Agent
|
2
|
+
|
3
|
+
TODO: Delete this and the text below, and describe your gem
|
4
|
+
|
5
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/agent_ruby`. To experiment with that code, run `bin/console` for an interactive prompt.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
10
|
+
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
12
|
+
|
13
|
+
$ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
|
14
|
+
|
15
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
16
|
+
|
17
|
+
$ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Development
|
24
|
+
|
25
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
26
|
+
|
27
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
28
|
+
|
29
|
+
## Contributing
|
30
|
+
|
31
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/agent_ruby.
|
32
|
+
|
33
|
+
## License
|
34
|
+
|
35
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/forest_admin
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Logfile created on 2023-09-27 15:44:52 +0200 by logger.rb/v1.5.3
|
@@ -0,0 +1,48 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require_relative "lib/forest_admin_agent/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "forest_admin_agent"
|
8
|
+
spec.version = ForestAdminAgent::VERSION
|
9
|
+
spec.authors = ["Matthieu", "Nicolas"]
|
10
|
+
spec.email = ["matthv@gmail.com", "nicolasalexandre9@gmail.com"]
|
11
|
+
spec.homepage = "https://www.forestadmin.com"
|
12
|
+
spec.summary = "Ruby agent for Forest Admin."
|
13
|
+
spec.description = "Forest is a modern admin interface that works on all major web frameworks. This gem makes Forest
|
14
|
+
admin work on any Ruby application."
|
15
|
+
spec.license = "MIT"
|
16
|
+
spec.required_ruby_version = ">= 3.0.0"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/ForestAdmin/agent-ruby"
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/ForestAdmin/agent-ruby/CHANGELOG.md"
|
21
|
+
spec.metadata["rubygems_mfa_required"] = "false"
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(File.expand_path(f) == __FILE__) ||
|
28
|
+
f.start_with?(*%w[bin/ test/ spec/ .git Gemfile LICENSE])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
spec.bindir = "exe"
|
33
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
34
|
+
spec.require_paths = ["lib"]
|
35
|
+
|
36
|
+
spec.add_dependency "activesupport", ">= 6.1"
|
37
|
+
spec.add_dependency "dry-container", "~> 0.11"
|
38
|
+
spec.add_dependency "faraday", "~> 2.7"
|
39
|
+
spec.add_dependency "ipaddress", "~> 0.8.3"
|
40
|
+
spec.add_dependency "jsonapi-serializers", "~> 1.0"
|
41
|
+
spec.add_dependency "jwt", "~> 2.7"
|
42
|
+
spec.add_dependency "lightly", "~> 0.4.0"
|
43
|
+
spec.add_dependency "mono_logger", "~> 1.1"
|
44
|
+
spec.add_dependency "openid_connect", "~> 2.2"
|
45
|
+
spec.add_dependency "rake", "~> 13.0"
|
46
|
+
spec.add_dependency "rack-cors", "~> 2.0"
|
47
|
+
spec.add_dependency "zeitwerk", "~> 2.3"
|
48
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module ForestAdminAgent
|
4
|
+
module Auth
|
5
|
+
class AuthManager
|
6
|
+
def initialize
|
7
|
+
@oidc = ForestAdminAgent::Auth::OidcClientManager.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def start(rendering_id)
|
11
|
+
client = @oidc.make_forest_provider rendering_id
|
12
|
+
client.authorization_uri({ state: JSON.generate({ renderingId: rendering_id }) })
|
13
|
+
end
|
14
|
+
|
15
|
+
def verify_code_and_generate_token(params)
|
16
|
+
raise Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_MISSING unless params['state']
|
17
|
+
|
18
|
+
if Facades::Container.cache(:debug)
|
19
|
+
OpenIDConnect.http_config do |options|
|
20
|
+
options.ssl.verify = false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
rendering_id = get_rendering_id_from_state(params['state'])
|
25
|
+
|
26
|
+
forest_provider = @oidc.make_forest_provider rendering_id
|
27
|
+
forest_provider.authorization_code = params['code']
|
28
|
+
access_token = forest_provider.access_token! 'none'
|
29
|
+
resource_owner = forest_provider.get_resource_owner access_token
|
30
|
+
|
31
|
+
resource_owner.make_jwt
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def get_rendering_id_from_state(state)
|
37
|
+
state = JSON.parse(state.tr("'", '"').gsub('=>', ':'))
|
38
|
+
raise Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_RENDERING_ID unless state.key? 'renderingId'
|
39
|
+
|
40
|
+
begin
|
41
|
+
Integer(state['renderingId'])
|
42
|
+
rescue ArgumentError
|
43
|
+
raise Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID
|
44
|
+
end
|
45
|
+
|
46
|
+
state['renderingId'].to_i
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'openid_connect'
|
2
|
+
require_relative 'forest_resource_owner'
|
3
|
+
|
4
|
+
module ForestAdminAgent
|
5
|
+
module Auth
|
6
|
+
module OAuth2
|
7
|
+
class ForestProvider < OpenIDConnect::Client
|
8
|
+
attr_reader :rendering_id
|
9
|
+
|
10
|
+
def initialize(rendering_id, attributes = {})
|
11
|
+
super attributes
|
12
|
+
@rendering_id = rendering_id
|
13
|
+
@authorization_endpoint = '/oidc/auth'
|
14
|
+
@token_endpoint = '/oidc/token'
|
15
|
+
self.userinfo_endpoint = "/liana/v2/renderings/#{rendering_id}/authorization"
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_resource_owner(access_token)
|
19
|
+
headers = { 'forest-token': access_token.access_token, 'forest-secret-key': secret }
|
20
|
+
hash = check_response do
|
21
|
+
OpenIDConnect.http_client.get access_token.client.userinfo_uri, {}, headers
|
22
|
+
end
|
23
|
+
|
24
|
+
response = OpenIDConnect::ResponseObject::UserInfo.new hash
|
25
|
+
|
26
|
+
create_resource_owner response.raw_attributes[:data]
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def create_resource_owner(data)
|
32
|
+
ForestResourceOwner.new data, rendering_id
|
33
|
+
end
|
34
|
+
|
35
|
+
def check_response
|
36
|
+
response = yield
|
37
|
+
case response.status
|
38
|
+
when 200
|
39
|
+
server_error = response.body.key?('errors') ? response.body['errors'][0] : nil
|
40
|
+
if server_error &&
|
41
|
+
server_error['name'] == Utils::ErrorMessages::TWO_FACTOR_AUTHENTICATION_REQUIRED
|
42
|
+
raise Error, Utils::ErrorMessages::TWO_FACTOR_AUTHENTICATION_REQUIRED
|
43
|
+
end
|
44
|
+
|
45
|
+
response.body.with_indifferent_access
|
46
|
+
when 400
|
47
|
+
raise OpenIDConnect::BadRequest.new('API Access Failed', response)
|
48
|
+
when 401
|
49
|
+
raise OpenIDConnect::Unauthorized.new(Utils::ErrorMessages::AUTHORIZATION_FAILED, response)
|
50
|
+
when 404
|
51
|
+
raise OpenIDConnect::HttpError.new(response.status, Utils::ErrorMessages::SECRET_NOT_FOUND, response)
|
52
|
+
when 422
|
53
|
+
raise OpenIDConnect::HttpError.new(response.status,
|
54
|
+
Utils::ErrorMessages::SECRET_AND_RENDERINGID_INCONSISTENT, response)
|
55
|
+
else
|
56
|
+
raise OpenIDConnect::HttpError.new(response.status, 'Unknown HttpError', response)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'jwt'
|
3
|
+
|
4
|
+
module ForestAdminAgent
|
5
|
+
module Auth
|
6
|
+
module OAuth2
|
7
|
+
class ForestResourceOwner
|
8
|
+
def initialize(data, rendering_id)
|
9
|
+
@data = data
|
10
|
+
@rendering_id = rendering_id
|
11
|
+
end
|
12
|
+
|
13
|
+
def id
|
14
|
+
@data['id']
|
15
|
+
end
|
16
|
+
|
17
|
+
def expiration_in_seconds
|
18
|
+
(DateTime.now + (1 / 24.0)).to_time.to_i
|
19
|
+
end
|
20
|
+
|
21
|
+
def make_jwt
|
22
|
+
attributes = @data['attributes']
|
23
|
+
user = {
|
24
|
+
id: id,
|
25
|
+
email: attributes['email'],
|
26
|
+
first_name: attributes['first_name'],
|
27
|
+
last_name: attributes['last_name'],
|
28
|
+
team: attributes['teams'][0],
|
29
|
+
tags: attributes['tags'],
|
30
|
+
rendering_id: @rendering_id,
|
31
|
+
exp: expiration_in_seconds,
|
32
|
+
permission_level: attributes['permission_level']
|
33
|
+
}
|
34
|
+
|
35
|
+
JWT.encode user,
|
36
|
+
Facades::Container.cache(:auth_secret),
|
37
|
+
'HS256'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'openid_connect'
|
2
|
+
|
3
|
+
module ForestAdminAgent
|
4
|
+
module Auth
|
5
|
+
module OAuth2
|
6
|
+
class OidcConfig
|
7
|
+
def self.discover!(identifier, cache_options = {})
|
8
|
+
uri = URI.parse(identifier)
|
9
|
+
Resource.new(uri).discover!(cache_options).tap do |response|
|
10
|
+
response.expected_issuer = identifier
|
11
|
+
response.validate!
|
12
|
+
end
|
13
|
+
rescue SWD::Exception, OpenIDConnect::ValidationFailed => e
|
14
|
+
raise OpenIDConnect::Discovery::DiscoveryFailed, e.message
|
15
|
+
end
|
16
|
+
|
17
|
+
class Resource < OpenIDConnect::Discovery::Provider::Config::Resource
|
18
|
+
def initialize(uri)
|
19
|
+
super
|
20
|
+
@host = uri.host
|
21
|
+
@port = uri.port unless [80, 443].include?(uri.port)
|
22
|
+
@path = File.join uri.path, 'oidc/.well-known/openid-configuration'
|
23
|
+
attr_missing!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'openid_connect'
|
2
|
+
require_relative 'oauth2/oidc_config'
|
3
|
+
require_relative 'oauth2/forest_provider'
|
4
|
+
|
5
|
+
module ForestAdminAgent
|
6
|
+
module Auth
|
7
|
+
class OidcClientManager
|
8
|
+
TTL = 60 * 60 * 24
|
9
|
+
|
10
|
+
def make_forest_provider(rendering_id)
|
11
|
+
config_agent = Facades::Container.config_from_cache
|
12
|
+
cache_key = "#{config_agent[:env_secret]}-client-data"
|
13
|
+
cache = setup_cache(cache_key, config_agent)
|
14
|
+
|
15
|
+
render_provider(cache, rendering_id, config_agent[:env_secret])
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def setup_cache(env_secret, config_agent)
|
21
|
+
lightly = Lightly.new(life: TTL, dir: "#{config_agent[:cache_dir]}/issuer")
|
22
|
+
lightly.get env_secret do
|
23
|
+
oidc_config = retrieve_config(config_agent[:forest_server_url])
|
24
|
+
credentials = register(
|
25
|
+
config_agent[:env_secret],
|
26
|
+
oidc_config.raw['registration_endpoint'],
|
27
|
+
{
|
28
|
+
token_endpoint_auth_method: 'none',
|
29
|
+
registration_endpoint: oidc_config.raw['registration_endpoint'],
|
30
|
+
application_type: 'web'
|
31
|
+
}
|
32
|
+
)
|
33
|
+
|
34
|
+
{
|
35
|
+
client_id: credentials['client_id'],
|
36
|
+
issuer: oidc_config.raw['issuer'],
|
37
|
+
redirect_uri: credentials['redirect_uris'].first
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def register(env_secret, registration_endpoint, data)
|
43
|
+
response = OpenIDConnect.http_client.post(
|
44
|
+
registration_endpoint,
|
45
|
+
data,
|
46
|
+
{ 'Authorization' => "Bearer #{env_secret}" }
|
47
|
+
)
|
48
|
+
|
49
|
+
response.body
|
50
|
+
end
|
51
|
+
|
52
|
+
def render_provider(cache, rendering_id, secret)
|
53
|
+
OAuth2::ForestProvider.new(
|
54
|
+
rendering_id,
|
55
|
+
{
|
56
|
+
identifier: cache[:client_id],
|
57
|
+
redirect_uri: cache[:redirect_uri],
|
58
|
+
host: cache[:issuer].to_s.sub(%r{^https?://(www.)?}, ''),
|
59
|
+
secret: secret
|
60
|
+
}
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def retrieve_config(uri)
|
65
|
+
OAuth2::OidcConfig.discover! uri
|
66
|
+
rescue OpenIDConnect::Discovery::DiscoveryFailed
|
67
|
+
raise Error, ForestAdminAgent::Utils::ErrorMessages::SERVER_DOWN
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'dry-container'
|
2
|
+
require 'lightly'
|
3
|
+
|
4
|
+
module ForestAdminAgent
|
5
|
+
module Builder
|
6
|
+
class AgentFactory
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
TTL_CONFIG = 3600
|
10
|
+
TTL_SCHEMA = 7200
|
11
|
+
|
12
|
+
attr_reader :customizer, :container, :has_env_secret
|
13
|
+
|
14
|
+
def setup(options)
|
15
|
+
@options = options
|
16
|
+
@has_env_secret = options.to_h.key?(:env_secret)
|
17
|
+
@customizer = ForestAdminDatasourceToolkit::Datasource.new
|
18
|
+
build_container
|
19
|
+
build_cache
|
20
|
+
build_logger
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_datasource(datasource)
|
24
|
+
datasource.collections.each { |_name, collection| @customizer.add_collection(collection) }
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def build
|
29
|
+
@container.register(:datasource, @customizer)
|
30
|
+
send_schema
|
31
|
+
end
|
32
|
+
|
33
|
+
def send_schema(force: false)
|
34
|
+
return unless @has_env_secret
|
35
|
+
|
36
|
+
schema = ForestAdminAgent::Utils::Schema::SchemaEmitter.get_serialized_schema(@customizer)
|
37
|
+
schema_is_know = @container.key?(:schema_file_hash) &&
|
38
|
+
@container.resolve(:schema_file_hash).get('value') == schema[:meta][:schemaFileHash]
|
39
|
+
|
40
|
+
if !schema_is_know || force
|
41
|
+
# Logger::log('Info', 'schema was updated, sending new version');
|
42
|
+
client = ForestAdminAgent::Http::ForestAdminApiRequester.new
|
43
|
+
client.post('/forest/apimaps', schema)
|
44
|
+
schema_file_hash_cache = Lightly.new(life: TTL_SCHEMA, dir: @options[:cache_dir].to_s)
|
45
|
+
schema_file_hash_cache.get 'value' do
|
46
|
+
schema[:meta][:schemaFileHash]
|
47
|
+
end
|
48
|
+
@container.register(:schema_file_hash, schema_file_hash_cache)
|
49
|
+
else
|
50
|
+
@container.resolve(:logger)
|
51
|
+
# TODO: Logger::log('Info', 'Schema was not updated since last run');
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def build_container
|
58
|
+
@container = Dry::Container.new
|
59
|
+
end
|
60
|
+
|
61
|
+
def build_cache
|
62
|
+
@container.register(:cache, Lightly.new(life: TTL_CONFIG, dir: @options[:cache_dir].to_s))
|
63
|
+
return unless @has_env_secret
|
64
|
+
|
65
|
+
cache = @container.resolve(:cache)
|
66
|
+
cache.get 'config' do
|
67
|
+
@options.to_h
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def build_logger
|
72
|
+
logger = Services::LoggerService.new(@options[:loggerLevel], @options[:logger])
|
73
|
+
@container.register(:logger, logger)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Facades
|
3
|
+
class Container
|
4
|
+
def self.instance
|
5
|
+
ForestAdminAgent::Builder::AgentFactory.instance.container
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.datasource
|
9
|
+
instance.resolve(:datasource)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.config_from_cache
|
13
|
+
instance.resolve(:cache).get('config')
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.cache(key)
|
17
|
+
raise "Key #{key} not found in container" unless config_from_cache.key?(key)
|
18
|
+
|
19
|
+
config_from_cache[key]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Facades
|
3
|
+
class Whitelist
|
4
|
+
def self.check_ip(request_ip)
|
5
|
+
ip_whitelist = ForestAdminAgent::Services::IpWhitelist.new
|
6
|
+
return unless ip_whitelist.enabled?
|
7
|
+
return if ip_whitelist.ip_matches_any_rule?(request_ip)
|
8
|
+
|
9
|
+
raise Net::HTTPExceptions, "IP address rejected (#{request_ip})"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Http
|
3
|
+
module Exceptions
|
4
|
+
class AuthenticationOpenIdClient < HttpException
|
5
|
+
attr_reader :error, :error_description, :state
|
6
|
+
|
7
|
+
def initialize(error, error_description, state)
|
8
|
+
super error, 401, error_description
|
9
|
+
@error = error
|
10
|
+
@error_description = error_description
|
11
|
+
@state = state
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Http
|
3
|
+
module Exceptions
|
4
|
+
class HttpException < StandardError
|
5
|
+
attr_reader :code, :status, :message, :name
|
6
|
+
|
7
|
+
def initialize(code, status, message, name = nil)
|
8
|
+
super(message)
|
9
|
+
@code = code
|
10
|
+
@status = status
|
11
|
+
@message = message
|
12
|
+
@name = name
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Http
|
3
|
+
module Exceptions
|
4
|
+
class NotFoundError < StandardError
|
5
|
+
attr_reader :name, :status
|
6
|
+
|
7
|
+
def initialize(msg, name = 'NotFoundError')
|
8
|
+
super msg
|
9
|
+
@name = name
|
10
|
+
@status = 404
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module ForestAdminAgent
|
4
|
+
module Http
|
5
|
+
class ForestAdminApiRequester
|
6
|
+
def initialize
|
7
|
+
@headers = {
|
8
|
+
'Content-Type' => 'application/json',
|
9
|
+
'forest-secret-key' => Facades::Container.cache(:env_secret)
|
10
|
+
}
|
11
|
+
@client = Faraday.new(
|
12
|
+
Facades::Container.cache(:forest_server_url),
|
13
|
+
{
|
14
|
+
headers: @headers
|
15
|
+
}
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(url, params)
|
20
|
+
@client.get(url, params.to_json)
|
21
|
+
end
|
22
|
+
|
23
|
+
def post(url, params)
|
24
|
+
@client.post(url, params.to_json)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Http
|
3
|
+
class Router
|
4
|
+
include ForestAdminAgent::Routes
|
5
|
+
|
6
|
+
def self.routes
|
7
|
+
[
|
8
|
+
# actions_routes,
|
9
|
+
# api_charts_routes,
|
10
|
+
System::HealthCheck.new.routes,
|
11
|
+
Security::Authentication.new.routes,
|
12
|
+
Resources::Count.new.routes,
|
13
|
+
Resources::Delete.new.routes,
|
14
|
+
Resources::List.new.routes,
|
15
|
+
Resources::Show.new.routes,
|
16
|
+
Resources::Store.new.routes,
|
17
|
+
Resources::Update.new.routes,
|
18
|
+
Resources::Related::ListRelated.new.routes,
|
19
|
+
Resources::Related::CountRelated.new.routes,
|
20
|
+
Resources::Related::AssociateRelated.new.routes,
|
21
|
+
Resources::Related::DissociateRelated.new.routes,
|
22
|
+
Resources::Related::UpdateRelated.new.routes
|
23
|
+
].inject(&:merge)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.actions_routes
|
27
|
+
routes = []
|
28
|
+
# TODO
|
29
|
+
# AgentFactory.get('datasource').collections.each do |collection|
|
30
|
+
# collection.get_actions.each do |action_name, action|
|
31
|
+
# routes << Actions.new(collection, action_name).routes
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
routes.flatten
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.api_charts_routes
|
38
|
+
routes = []
|
39
|
+
# TODO
|
40
|
+
# AgentFactory.get('datasource').charts.each do |chart|
|
41
|
+
# routes << ApiChartDatasource.new(chart).routes
|
42
|
+
# end
|
43
|
+
# AgentFactory.get('datasource').collections.each do |collection|
|
44
|
+
# collection.charts.each do |chart|
|
45
|
+
# routes << ApiChartCollection.new(collection, chart).routes
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
routes.flatten
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Routes
|
3
|
+
class AbstractAuthenticatedRoute < AbstractRoute
|
4
|
+
def build(args = {})
|
5
|
+
if args.dig(:headers, 'action_dispatch.remote_ip')
|
6
|
+
Facades::Whitelist.check_ip(args[:headers]['action_dispatch.remote_ip'].to_s)
|
7
|
+
end
|
8
|
+
@caller = Utils::QueryStringParser.parse_caller(args)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_attributes(args)
|
13
|
+
record = args[:params][:data][:attributes]
|
14
|
+
|
15
|
+
args[:params][:data][:relationships]&.map do |field, value|
|
16
|
+
schema = @collection.fields[field]
|
17
|
+
|
18
|
+
record[schema.foreign_key] = value['data'][schema.foreign_key_target] if schema.type == 'ManyToOne'
|
19
|
+
end
|
20
|
+
|
21
|
+
record
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Routes
|
3
|
+
class AbstractRelatedRoute < AbstractAuthenticatedRoute
|
4
|
+
def build(args = {})
|
5
|
+
super
|
6
|
+
|
7
|
+
relation = @collection.fields[args[:params]['relation_name']]
|
8
|
+
@child_collection = @datasource.collection(relation.foreign_collection)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|