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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +5 -0
  4. data/README.md +35 -0
  5. data/Rakefile +10 -0
  6. data/forest_admin +1 -0
  7. data/forest_admin_agent.gemspec +48 -0
  8. data/lib/forest_admin_agent/auth/auth_manager.rb +50 -0
  9. data/lib/forest_admin_agent/auth/oauth2/forest_provider.rb +62 -0
  10. data/lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb +42 -0
  11. data/lib/forest_admin_agent/auth/oauth2/oidc_config.rb +29 -0
  12. data/lib/forest_admin_agent/auth/oidc_client_manager.rb +71 -0
  13. data/lib/forest_admin_agent/builder/agent_factory.rb +77 -0
  14. data/lib/forest_admin_agent/facades/container.rb +23 -0
  15. data/lib/forest_admin_agent/facades/whitelist.rb +13 -0
  16. data/lib/forest_admin_agent/http/Exceptions/authentication_open_id_client.rb +16 -0
  17. data/lib/forest_admin_agent/http/Exceptions/http_exception.rb +17 -0
  18. data/lib/forest_admin_agent/http/Exceptions/not_found_error.rb +15 -0
  19. data/lib/forest_admin_agent/http/forest_admin_api_requester.rb +28 -0
  20. data/lib/forest_admin_agent/http/router.rb +52 -0
  21. data/lib/forest_admin_agent/routes/abstract_authenticated_route.rb +25 -0
  22. data/lib/forest_admin_agent/routes/abstract_related_route.rb +12 -0
  23. data/lib/forest_admin_agent/routes/abstract_route.rb +27 -0
  24. data/lib/forest_admin_agent/routes/resources/count.rb +41 -0
  25. data/lib/forest_admin_agent/routes/resources/delete.rb +51 -0
  26. data/lib/forest_admin_agent/routes/resources/list.rb +38 -0
  27. data/lib/forest_admin_agent/routes/resources/related/associate_related.rb +63 -0
  28. data/lib/forest_admin_agent/routes/resources/related/count_related.rb +56 -0
  29. data/lib/forest_admin_agent/routes/resources/related/dissociate_related.rb +97 -0
  30. data/lib/forest_admin_agent/routes/resources/related/list_related.rb +54 -0
  31. data/lib/forest_admin_agent/routes/resources/related/update_related.rb +102 -0
  32. data/lib/forest_admin_agent/routes/resources/show.rb +44 -0
  33. data/lib/forest_admin_agent/routes/resources/store.rb +51 -0
  34. data/lib/forest_admin_agent/routes/resources/update.rb +42 -0
  35. data/lib/forest_admin_agent/routes/security/authentication.rb +95 -0
  36. data/lib/forest_admin_agent/routes/system/health_check.rb +22 -0
  37. data/lib/forest_admin_agent/serializer/forest_serializer.rb +176 -0
  38. data/lib/forest_admin_agent/serializer/forest_serializer_override.rb +103 -0
  39. data/lib/forest_admin_agent/services/ip_whitelist.rb +100 -0
  40. data/lib/forest_admin_agent/services/logger_service.rb +20 -0
  41. data/lib/forest_admin_agent/utils/condition_tree_parser.rb +57 -0
  42. data/lib/forest_admin_agent/utils/error_messages.rb +38 -0
  43. data/lib/forest_admin_agent/utils/id.rb +48 -0
  44. data/lib/forest_admin_agent/utils/query_string_parser.rb +89 -0
  45. data/lib/forest_admin_agent/utils/schema/frontend_filterable.rb +73 -0
  46. data/lib/forest_admin_agent/utils/schema/generator_collection.rb +35 -0
  47. data/lib/forest_admin_agent/utils/schema/generator_field.rb +183 -0
  48. data/lib/forest_admin_agent/utils/schema/schema_emitter.rb +103 -0
  49. data/lib/forest_admin_agent/version.rb +3 -0
  50. data/lib/forest_admin_agent.rb +11 -0
  51. data/sig/forest_admin_agent/auth/auth_manager.rbs +14 -0
  52. data/sig/forest_admin_agent/auth/oauth2/forest_provider.rbs +16 -0
  53. data/sig/forest_admin_agent/auth/oauth2/forest_resource_owner.rbs +15 -0
  54. data/sig/forest_admin_agent/auth/oidc_client_manager.rbs +15 -0
  55. data/sig/forest_admin_agent/builder/agent_factory.rbs +21 -0
  56. data/sig/forest_admin_agent/facades/container.rbs +9 -0
  57. data/sig/forest_admin_agent/http/router.rbs +9 -0
  58. data/sig/forest_admin_agent/routes/abstract_route.rbs +12 -0
  59. data/sig/forest_admin_agent/routes/security/authentication.rbs +14 -0
  60. data/sig/forest_admin_agent/routes/system/health_check.rbs +10 -0
  61. data/sig/forest_admin_agent.rbs +4 -0
  62. 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
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-08-28
4
+
5
+ - Initial release
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
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ require "rubocop/rake_task"
7
+
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: %i[spec rubocop]
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