From 69f6c908f0b1088a5a2360260b0abbb5125edb43 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 13 Sep 2023 10:54:24 +0200 Subject: [PATCH 01/40] fix: readme agent name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e82dac79..784685372 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Forest Admin agent PHP +# Forest Admin agent Ruby Forest Admin provides an off-the-shelf administration panel based on a highly-extensible API plugged into your application. From 7ee3b574aa20d21e124ca07364db4b6fabbea32d Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 14 Sep 2023 10:20:54 +0200 Subject: [PATCH 02/40] chore: manage route with parameters --- .../lib/forest_admin_agent/http/router.rb | 6 +++++- .../routes/resources/list.rb | 18 ++++++++++++++++++ .../forest_admin_rails/forest_controller.rb | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/list.rb diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/router.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/router.rb index e0e291329..1b190f190 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/router.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/http/router.rb @@ -1,11 +1,15 @@ module ForestAdminAgent module Http class Router + include ForestAdminAgent::Routes + def self.routes [ # actions_routes, # api_charts_routes, - ForestAdminAgent::Routes::System::HealthCheck.new.routes + System::HealthCheck.new.routes, + Security::Authentication.new.routes, + Resources::List.new.routes ].inject(&:merge) end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/list.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/list.rb new file mode 100644 index 000000000..008b66dcb --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/list.rb @@ -0,0 +1,18 @@ +module ForestAdminAgent + module Routes + module Resources + class List < AbstractRoute + include ForestAdminAgent::Builder + def setup_routes + add_route('forest_list', 'get', '/:collection_name', ->(args) { handle_request(args) }) + + self + end + + def handle_request(args = {}) + { name: args['collection_name'], content: args['collection_name'] } + end + end + end + end +end diff --git a/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb b/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb index 57b7e251b..175b65ce9 100644 --- a/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb +++ b/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb @@ -5,7 +5,7 @@ def index if ForestAdminAgent::Http::Router.routes.key? route_alias route = ForestAdminAgent::Http::Router.routes[route_alias] - forest_response route[:closure] + forest_response route[:closure].call(params) else render json: { error: 'Route not found' }, status: 404 end From 3128559c06df5bc94aa309c9ebc21586b261cea7 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 15 Sep 2023 15:58:46 +0200 Subject: [PATCH 03/40] chore: add route_alias on routing --- .../controllers/forest_admin_rails/forest_controller.rb | 7 ++++--- packages/forest_admin_rails/config/routes.rb | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb b/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb index 175b65ce9..de1dec70c 100644 --- a/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb +++ b/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb @@ -1,9 +1,10 @@ module ForestAdminRails class ForestController < ActionController::Base + skip_forgery_protection + def index - route_alias = request.routes.named_routes.helper_names.first.delete_suffix('_path') - if ForestAdminAgent::Http::Router.routes.key? route_alias - route = ForestAdminAgent::Http::Router.routes[route_alias] + if ForestAdminAgent::Http::Router.routes.key? params['route_alias'] + route = ForestAdminAgent::Http::Router.routes[params['route_alias']] forest_response route[:closure].call(params) else diff --git a/packages/forest_admin_rails/config/routes.rb b/packages/forest_admin_rails/config/routes.rb index ecb2139ce..81600ce2c 100644 --- a/packages/forest_admin_rails/config/routes.rb +++ b/packages/forest_admin_rails/config/routes.rb @@ -1,5 +1,5 @@ ForestAdminRails::Engine.routes.draw do ForestAdminAgent::Http::Router.routes.each do |name, agent_route| - match agent_route[:uri], to: 'forest#index', via: agent_route[:method], as: name + match agent_route[:uri], to: 'forest#index', via: agent_route[:method], as: name, route_alias: name end end From b55546f94de7ca5c2e85bbf0ee6f945bf55bc3e2 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 15 Sep 2023 15:59:16 +0200 Subject: [PATCH 04/40] chore: add package openid_connect --- packages/forest_admin_agent/forest_admin_agent.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/forest_admin_agent/forest_admin_agent.gemspec b/packages/forest_admin_agent/forest_admin_agent.gemspec index 259df194c..176a32308 100644 --- a/packages/forest_admin_agent/forest_admin_agent.gemspec +++ b/packages/forest_admin_agent/forest_admin_agent.gemspec @@ -36,6 +36,7 @@ admin work on any Ruby application." spec.add_dependency "dry-container", "~> 0.11" spec.add_dependency "lightly", "~> 0.4.0" spec.add_dependency "mono_logger", "~> 1.1" + spec.add_dependency "openid_connect", "~> 2.2" spec.add_dependency "rake", "~> 13.0" spec.add_dependency "zeitwerk", "~> 2.3" end From 1c5ce0cf5925226a4466137eba1465a9f36b1423 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 15 Sep 2023 16:01:04 +0200 Subject: [PATCH 05/40] chore: route setup request & args on route --- .../lib/forest_admin_agent/routes/abstract_route.rb | 1 - .../lib/forest_admin_agent/routes/system/health_check.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/abstract_route.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/abstract_route.rb index e61ddc759..f6fe2b0cd 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/routes/abstract_route.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/abstract_route.rb @@ -4,7 +4,6 @@ class AbstractRoute attr_reader :request def initialize - @request = ActionDispatch::Request.new({}) @routes = {} setup_routes end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/system/health_check.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/system/health_check.rb index eb833f914..aadbe2d0f 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/routes/system/health_check.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/system/health_check.rb @@ -4,7 +4,7 @@ module System class HealthCheck < AbstractRoute include ForestAdminAgent::Builder def setup_routes - add_route('forest', 'GET', '/', handle_request) + add_route('forest', 'GET', '/', ->(args) { handle_request(args) }) self end From 417f2ba214fe8ecde0af43c03350fd90629409f0 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 15 Sep 2023 16:04:24 +0200 Subject: [PATCH 06/40] chore: add utils error_messages --- .rubocop.yml | 2 ++ .../utils/error_messages.rb | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/utils/error_messages.rb diff --git a/.rubocop.yml b/.rubocop.yml index 283a0b025..bf0a5e4f6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -99,3 +99,5 @@ Style/RedundantConstantBase: Layout/LineLength: Max: 120 + Exclude: + - 'packages/forest_admin_agent/lib/forest_admin_agent/utils/error_messages.rb' diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/error_messages.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/error_messages.rb new file mode 100644 index 000000000..d94f705b2 --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/error_messages.rb @@ -0,0 +1,33 @@ +module ForestAdminAgent + module Utils + class ErrorMessages + AUTH_SECRET_MISSING = 'Your Forest authSecret seems to be missing. Can you check that you properly set a Forest authSecret in the Forest initializer?'.freeze + + SECRET_AND_RENDERINGID_INCONSISTENT = 'Cannot retrieve the project you\'re trying to unlock. The envSecret and renderingId seems to be missing or inconsistent.'.freeze + + SERVER_DOWN = 'Cannot retrieve the data from the Forest server. Forest API seems to be down right now.'.freeze + + SECRET_NOT_FOUND = 'Cannot retrieve the data from the Forest server. Can you check that you properly copied the Forest envSecret in the Liana initializer?'.freeze + + UNEXPECTED = 'Cannot retrieve the data from the Forest server. An error occured in Forest API'.freeze + + INVALID_STATE_MISSING = 'Invalid response from the authentication server: the state parameter is missing'.freeze + + INVALID_STATE_FORMAT = 'Invalid response from the authentication server: the state parameter is not at the right format'.freeze + + INVALID_STATE_RENDERING_ID = 'Invalid response from the authentication server: the state does not contain a renderingId'.freeze + + MISSING_RENDERING_ID = 'Authentication request must contain a renderingId'.freeze + + INVALID_RENDERING_ID = 'The parameter renderingId is not valid'.freeze + + REGISTRATION_FAILED = 'The registration to the authentication API failed, response: '.freeze + + OIDC_CONFIGURATION_RETRIEVAL_FAILED = 'Failed to retrieve the provider\'s configuration.'.freeze + + TWO_FACTOR_AUTHENTICATION_REQUIRED = 'TwoFactorAuthenticationRequiredForbiddenError'.freeze + + AUTHORIZATION_FAILED = 'Error while authorizing the user on Forest Admin'.freeze + end + end +end From 2802dd987f55cd9e62d7ae0588c19b925e7b5162 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 15 Sep 2023 16:05:19 +0200 Subject: [PATCH 07/40] chore: lint --- .rubocop.yml | 2 -- .../forest_admin_agent/utils/error_messages.rb | 15 ++++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index bf0a5e4f6..283a0b025 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -99,5 +99,3 @@ Style/RedundantConstantBase: Layout/LineLength: Max: 120 - Exclude: - - 'packages/forest_admin_agent/lib/forest_admin_agent/utils/error_messages.rb' diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/error_messages.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/error_messages.rb index d94f705b2..cb4a6f680 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/error_messages.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/error_messages.rb @@ -1,21 +1,26 @@ module ForestAdminAgent module Utils class ErrorMessages - AUTH_SECRET_MISSING = 'Your Forest authSecret seems to be missing. Can you check that you properly set a Forest authSecret in the Forest initializer?'.freeze + AUTH_SECRET_MISSING = 'Your Forest authSecret seems to be missing. Can you check that you properly set a Forest +authSecret in the Forest initializer?'.freeze - SECRET_AND_RENDERINGID_INCONSISTENT = 'Cannot retrieve the project you\'re trying to unlock. The envSecret and renderingId seems to be missing or inconsistent.'.freeze + SECRET_AND_RENDERINGID_INCONSISTENT = 'Cannot retrieve the project you\'re trying to unlock. The envSecret and +renderingId seems to be missing or inconsistent.'.freeze SERVER_DOWN = 'Cannot retrieve the data from the Forest server. Forest API seems to be down right now.'.freeze - SECRET_NOT_FOUND = 'Cannot retrieve the data from the Forest server. Can you check that you properly copied the Forest envSecret in the Liana initializer?'.freeze + SECRET_NOT_FOUND = 'Cannot retrieve the data from the Forest server. Can you check that you properly copied the +Forest envSecret in the Liana initializer?'.freeze UNEXPECTED = 'Cannot retrieve the data from the Forest server. An error occured in Forest API'.freeze INVALID_STATE_MISSING = 'Invalid response from the authentication server: the state parameter is missing'.freeze - INVALID_STATE_FORMAT = 'Invalid response from the authentication server: the state parameter is not at the right format'.freeze + INVALID_STATE_FORMAT = 'Invalid response from the authentication server: the state parameter is not at the right +format'.freeze - INVALID_STATE_RENDERING_ID = 'Invalid response from the authentication server: the state does not contain a renderingId'.freeze + INVALID_STATE_RENDERING_ID = 'Invalid response from the authentication server: the state does not contain a +renderingId'.freeze MISSING_RENDERING_ID = 'Authentication request must contain a renderingId'.freeze From 5d588e233f3832b87b5815cb6e10e5839faa705d Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 15 Sep 2023 16:29:53 +0200 Subject: [PATCH 08/40] feat(auth): add logic to auth user before callback --- .rubocop.yml | 3 + .../auth/oauth2/forest_provider.rb | 44 +++++++++++++ .../auth/oauth2/oidc_config.rb | 29 +++++++++ .../auth/oidc_client_manager.rb | 64 +++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/oidc_config.rb create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb diff --git a/.rubocop.yml b/.rubocop.yml index 283a0b025..d5543c48c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -99,3 +99,6 @@ Style/RedundantConstantBase: Layout/LineLength: Max: 120 + +Metrics/MethodLength: + Max: 20 diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb new file mode 100644 index 000000000..d31f96919 --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb @@ -0,0 +1,44 @@ +require 'openid_connect' +require_relative 'forest_resource_owner' + +module ForestAdminAgent + module Auth + module OAuth2 + class ForestProvider < OpenIDConnect::Client + attr_reader :rendering_id + + def initialize(rendering_id, attributes = {}) + super attributes + @rendering_id = rendering_id + @authorization_endpoint = '/oidc/auth' + @token_endpoint = '/oidc/token' + self.userinfo_endpoint = "/liana/v2/renderings/#{rendering_id}/authorization" + end + + # public function getResourceOwner(AccessToken $token) + # { + # $response = $this->fetchResourceOwnerDetails($token); + # + # return $this->createResourceOwner($response, $token); + # } + + # protected function fetchResourceOwnerDetails(AccessToken $token) + # { + # $url = $this->getResourceOwnerDetailsUrl($token); + # + # $request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token); + # + # $response = $this->getParsedResponse($request); + # + # if (false === is_array($response)) { + # throw new UnexpectedValueException( + # 'Invalid response received from Authorization Server. Expected JSON.' + # ); + # } + # + # return $response; + # } + end + end + end +end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/oidc_config.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/oidc_config.rb new file mode 100644 index 000000000..13750abbb --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/oidc_config.rb @@ -0,0 +1,29 @@ +require 'openid_connect' + +module ForestAdminAgent + module Auth + module OAuth2 + class OidcConfig + def self.discover!(identifier, cache_options = {}) + uri = URI.parse(identifier) + Resource.new(uri).discover!(cache_options).tap do |response| + response.expected_issuer = identifier + response.validate! + end + rescue SWD::Exception, OpenIDConnect::ValidationFailed => e + raise OpenIDConnect::DiscoveryFailed, e.message + end + + class Resource < OpenIDConnect::Discovery::Provider::Config::Resource + def initialize(uri) + super + @host = uri.host + @port = uri.port unless [80, 443].include?(uri.port) + @path = File.join uri.path, 'oidc/.well-known/openid-configuration' + attr_missing! + end + end + end + end + end +end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb new file mode 100644 index 000000000..10abb7d76 --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb @@ -0,0 +1,64 @@ +require 'openid_connect' +require_relative 'oauth2/oidc_config' +require_relative 'oauth2/forest_provider' + +module ForestAdminAgent + module Auth + class OidcClientManager + TTL = 60 * 60 * 24 + + def make_forest_provider(rendering_id) + config_agent = ForestAdminAgent::Builder::AgentFactory.instance.container.resolve(:cache).get('config') + cache_key = "#{config_agent[:env_secret]}-client-data" + cache = setup_cache(cache_key, config_agent) + + render_provider(cache, rendering_id) + end + + private + + def setup_cache(env_secret, config_agent) + lightly = Lightly.new(life: TTL, dir: "#{config_agent[:cache_dir]}/issuer") + lightly.get env_secret do + oidc_config = OAuth2::OidcConfig.discover! config_agent[:forest_server_url] + credentials = register( + config_agent[:env_secret], + oidc_config.raw['registration_endpoint'], + { + token_endpoint_auth_method: 'none', + registration_endpoint: oidc_config.raw['registration_endpoint'], + application_type: 'web' + } + ) + + { + client_id: credentials['client_id'], + issuer: oidc_config.raw['issuer'], + redirect_uri: credentials['redirect_uris'].first + } + end + end + + def register(env_secret, registration_endpoint, data) + response = OpenIDConnect.http_client.post( + registration_endpoint, + data, + { 'Authorization' => "Bearer #{env_secret}" } + ) + + response.body + end + + def render_provider(cache, rendering_id) + OAuth2::ForestProvider.new( + rendering_id, + { + identifier: cache[:client_id], + redirect_uri: cache[:redirect_uri], + host: cache[:issuer].to_s.sub(%r{^https?://(www.)?}, '') + } + ) + end + end + end +end From eeba9af8ba5ca1e9a5f4c41fb4bfc370bd81dc54 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 18 Sep 2023 12:06:16 +0200 Subject: [PATCH 09/40] feat(cors): add cors support on the engine --- .../forest_admin_agent.gemspec | 2 ++ .../lib/forest_admin_rails/engine.rb | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/packages/forest_admin_agent/forest_admin_agent.gemspec b/packages/forest_admin_agent/forest_admin_agent.gemspec index 176a32308..c5ad5b4b2 100644 --- a/packages/forest_admin_agent/forest_admin_agent.gemspec +++ b/packages/forest_admin_agent/forest_admin_agent.gemspec @@ -35,8 +35,10 @@ admin work on any Ruby application." spec.add_dependency "dry-container", "~> 0.11" spec.add_dependency "lightly", "~> 0.4.0" + spec.add_dependency "jwt", "~> 2.7" spec.add_dependency "mono_logger", "~> 1.1" spec.add_dependency "openid_connect", "~> 2.2" spec.add_dependency "rake", "~> 13.0" + spec.add_dependency "rack-cors", "~> 2.0" spec.add_dependency "zeitwerk", "~> 2.3" end diff --git a/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb b/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb index c4157d7e7..3ae8d30a5 100644 --- a/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb +++ b/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb @@ -1,4 +1,22 @@ require 'forest_admin_agent' +require 'rack/cors' + +module Rack + class Cors + class Resource + def to_preflight_headers(env) + h = to_headers(env) + if env['HTTP_ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK'] == 'true' + h['Access-Control-Allow-Private-Network'] = 'true' + end + if env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS] + h['Access-Control-Allow-Headers'] = env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS] + end + h + end + end + end +end module ForestAdminRails class Engine < ::Rails::Engine @@ -8,6 +26,7 @@ class Engine < ::Rails::Engine agent_factory = ForestAdminAgent::Builder::AgentFactory.instance agent_factory.setup(ForestAdminRails.config) load_configuration + load_cors end def load_configuration @@ -17,5 +36,18 @@ def load_configuration ForestAdminAgent::Builder::AgentFactory.instance.build end + + def load_cors + config.middleware.insert_before 0, Rack::Cors do + allow do + hostnames = [/\A.*\.forestadmin\.com\z/] + hostnames += ENV['CORS_ORIGINS'].split(',') if ENV['CORS_ORIGINS'] + forest_prefix = ForestAdminRails.config[:prefix] + + origins hostnames + resource "/#{forest_prefix}/*", headers: :any, methods: :any, credentials: true, max_age: 86_400 + end + end + end end end From 35dc260d567aa48f4b103610b5f00a20a8b7bc00 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 18 Sep 2023 14:08:05 +0200 Subject: [PATCH 10/40] fix: cors prefix --- packages/forest_admin_rails/lib/forest_admin_rails/engine.rb | 3 +-- .../lib/generators/forest_admin_rails/install_generator.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb b/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb index 3ae8d30a5..0372588e6 100644 --- a/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb +++ b/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb @@ -42,10 +42,9 @@ def load_cors allow do hostnames = [/\A.*\.forestadmin\.com\z/] hostnames += ENV['CORS_ORIGINS'].split(',') if ENV['CORS_ORIGINS'] - forest_prefix = ForestAdminRails.config[:prefix] origins hostnames - resource "/#{forest_prefix}/*", headers: :any, methods: :any, credentials: true, max_age: 86_400 + resource '*', headers: :any, methods: :any, credentials: true, max_age: 86_400 end end end diff --git a/packages/forest_admin_rails/lib/generators/forest_admin_rails/install_generator.rb b/packages/forest_admin_rails/lib/generators/forest_admin_rails/install_generator.rb index 6b4d2397a..9db3f71ed 100644 --- a/packages/forest_admin_rails/lib/generators/forest_admin_rails/install_generator.rb +++ b/packages/forest_admin_rails/lib/generators/forest_admin_rails/install_generator.rb @@ -9,7 +9,7 @@ def install @env_secret = env_secret template 'initializers/config.rb', 'config/initializers/forest_admin_rails.rb' template 'forest_admin.rb', 'config/forest_admin.rb' - route "mount ForestAdminRails::Engine => '/forest'" + route "mount ForestAdminRails::Engine => '/#{ForestAdminRails.config[:prefix]}'" end end end From 3a4414bc391b0ae34714c9ea3d32c0e5394bdb57 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 19 Sep 2023 12:06:01 +0200 Subject: [PATCH 11/40] chore: add new facade to container --- .../forest_admin_agent/facades/container.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/facades/container.rb diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/facades/container.rb b/packages/forest_admin_agent/lib/forest_admin_agent/facades/container.rb new file mode 100644 index 000000000..5d4e5ec3e --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/facades/container.rb @@ -0,0 +1,19 @@ +module ForestAdminAgent + module Facades + class Container + def self.instance + ForestAdminAgent::Builder::AgentFactory.instance.container + end + + def self.config_from_cache + instance.resolve(:cache).get('config') + end + + def self.get(key) + raise "Key #{key} not found in container" unless config_from_cache.key?(key) + + cache[key] + end + end + end +end From f2bbd15023f7e6014cff2c370bcbd08738e6fce2 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 19 Sep 2023 12:07:01 +0200 Subject: [PATCH 12/40] feat(auth): set provider with resource owner --- .rubocop.yml | 17 ++++- .../auth/oauth2/forest_provider.rb | 64 ++++++++++++------- .../auth/oauth2/forest_resource_owner.rb | 42 ++++++++++++ 3 files changed, 97 insertions(+), 26 deletions(-) create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb diff --git a/.rubocop.yml b/.rubocop.yml index d5543c48c..fffa27bf8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -24,6 +24,20 @@ Lint/EmptyFile: Exclude: - 'packages/forest_admin_rails/app/models/forest_admin_rails/application_record.rb' +Metrics/AbcSize: + Exclude: + - 'packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb' + - 'packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb' + +Metrics/CyclomaticComplexity: + Exclude: + - 'packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb' + +Metrics/MethodLength: + Max: 20 + Exclude: + - 'packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb' + Style/BlockComments: Exclude: - 'packages/forest_admin_agent/spec/spec_helper.rb' @@ -99,6 +113,3 @@ Style/RedundantConstantBase: Layout/LineLength: Max: 120 - -Metrics/MethodLength: - Max: 20 diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb index d31f96919..feeb33460 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb @@ -15,29 +15,47 @@ def initialize(rendering_id, attributes = {}) self.userinfo_endpoint = "/liana/v2/renderings/#{rendering_id}/authorization" end - # public function getResourceOwner(AccessToken $token) - # { - # $response = $this->fetchResourceOwnerDetails($token); - # - # return $this->createResourceOwner($response, $token); - # } - - # protected function fetchResourceOwnerDetails(AccessToken $token) - # { - # $url = $this->getResourceOwnerDetailsUrl($token); - # - # $request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token); - # - # $response = $this->getParsedResponse($request); - # - # if (false === is_array($response)) { - # throw new UnexpectedValueException( - # 'Invalid response received from Authorization Server. Expected JSON.' - # ); - # } - # - # return $response; - # } + def get_resource_owner(access_token) + headers = { 'forest-token': access_token.access_token, 'forest-secret-key': secret } + hash = check_response do + OpenIDConnect.http_client.get access_token.client.userinfo_uri, {}, headers + end + + response = OpenIDConnect::ResponseObject::UserInfo.new hash + + create_resource_owner response.raw_attributes[:data] + end + + private + + def create_resource_owner(data) + ForestResourceOwner.new data, rendering_id + end + + def check_response + response = yield + case response.status + when 200 + server_error = response.body.key?('errors') ? response.body['errors'][0] : nil + if server_error && + server_error['name'] == ForestAdminAgent::Utils::ErrorMessages::TWO_FACTOR_AUTHENTICATION_REQUIRED + raise Error, ForestAdminAgent::Utils::ErrorMessages::TWO_FACTOR_AUTHENTICATION_REQUIRED + end + + response.body.with_indifferent_access + when 400 + raise BadRequest.new('API Access Failed', response) + when 401 + raise Unauthorized.new(ForestAdminAgent::Utils::ErrorMessages::AUTHORIZATION_FAILED, response) + when 404 + raise HttpError.new(res.status, ForestAdminAgent::Utils::ErrorMessages::SECRET_NOT_FOUND, response) + when 422 + raise HttpError.new(res.status, + ForestAdminAgent::Utils::ErrorMessages::SECRET_AND_RENDERINGID_INCONSISTENT, response) + else + raise HttpError.new(res.status, 'Unknown HttpError', response) + end + end end end end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb new file mode 100644 index 000000000..8fc2b10ce --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb @@ -0,0 +1,42 @@ +require 'date' +require 'jwt' + +module ForestAdminAgent + module Auth + module OAuth2 + class ForestResourceOwner + def initialize(data, rendering_id) + @data = data + @rendering_id = rendering_id + end + + def id + @data['id'] + end + + def expiration_in_seconds + (DateTime.now + (1 / 24.0)).to_time.to_i + end + + def make_jwt + attributes = @data['attributes'] + user = { + id: id, + email: attributes['email'], + first_name: attributes['first_name'], + last_name: attributes['last_name'], + team: attributes['teams'][0], + tags: attributes['tags'], + rendering_id: @rendering_id, + exp: expiration_in_seconds, + permission_level: attributes['permission_level'] + } + + JWT.encode user, + Facades::Container.get(:auth_secret), + 'HS256' + end + end + end + end +end From 223e2dcb592353cd889029cfdd22ff46da5c6e84 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 19 Sep 2023 12:08:35 +0200 Subject: [PATCH 13/40] feat(auth): add secret key on provider --- .../lib/forest_admin_agent/auth/oidc_client_manager.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb index 10abb7d76..530e70e74 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb @@ -8,11 +8,11 @@ class OidcClientManager TTL = 60 * 60 * 24 def make_forest_provider(rendering_id) - config_agent = ForestAdminAgent::Builder::AgentFactory.instance.container.resolve(:cache).get('config') + config_agent = Facades::Container.config_from_cache cache_key = "#{config_agent[:env_secret]}-client-data" cache = setup_cache(cache_key, config_agent) - render_provider(cache, rendering_id) + render_provider(cache, rendering_id, config_agent[:env_secret]) end private @@ -49,13 +49,14 @@ def register(env_secret, registration_endpoint, data) response.body end - def render_provider(cache, rendering_id) + def render_provider(cache, rendering_id, secret) OAuth2::ForestProvider.new( rendering_id, { identifier: cache[:client_id], redirect_uri: cache[:redirect_uri], - host: cache[:issuer].to_s.sub(%r{^https?://(www.)?}, '') + host: cache[:issuer].to_s.sub(%r{^https?://(www.)?}, ''), + secret: secret } ) end From 4c4362ad566af3e49c12d8b4659e42c65eaf79cd Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 19 Sep 2023 12:09:16 +0200 Subject: [PATCH 14/40] feat(auth): add response --- .../forest_admin_agent/auth/auth_manager.rb | 50 ++++++++++++ .../routes/security/authentication.rb | 81 +++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb new file mode 100644 index 000000000..52b391616 --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb @@ -0,0 +1,50 @@ +require 'json' + +module ForestAdminAgent + module Auth + class AuthManager + def initialize + @oidc = ForestAdminAgent::Auth::OidcClientManager.new + end + + def start(rendering_id) + client = @oidc.make_forest_provider rendering_id + client.authorization_uri({ state: JSON.generate({ renderingId: rendering_id }) }) + end + + def verify_code_and_generate_token(params) + raise Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_MISSING unless params['state'] + + if Rails.env.development? || Rails.env.test? + OpenIDConnect.http_config do |options| + options.ssl.verify = false + end + end + + rendering_id = get_rendering_id_from_state(params['state']) + + forest_provider = @oidc.make_forest_provider rendering_id + forest_provider.authorization_code = params['code'] + access_token = forest_provider.access_token! 'none' + resource_owner = forest_provider.get_resource_owner access_token + + resource_owner.make_jwt + end + + private + + def get_rendering_id_from_state(state) + state = JSON.parse(state.tr("'", '"').gsub('=>', ':')) + raise Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_RENDERING_ID unless state.key? 'renderingId' + + begin + Integer(state['renderingId']) + rescue ArgumentError + raise Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID + end + + state['renderingId'].to_i + end + end + end +end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb new file mode 100644 index 000000000..a6d7d9a77 --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb @@ -0,0 +1,81 @@ +require 'jwt' + +module ForestAdminAgent + module Routes + module Security + class Authentication < AbstractRoute + include ForestAdminAgent::Builder + def setup_routes + add_route( + 'forest_authentication', + 'POST', + '/authentication', ->(args) { handle_authentication(args) } + ) + add_route( + 'forest_authentication-callback', + 'GET', + '/authentication/callback', ->(args) { handle_authentication_callback(args) } + ) + add_route( + 'forest_logout', + 'POST', + '/authentication/logout', ->(args) { handle_authentication_logout(args) } + ) + + self + end + + def handle_authentication(args = {}) + rendering_id = get_and_check_rendering_id args + + { + content: { + authorizationUrl: auth.start(rendering_id) + } + } + end + + def handle_authentication_callback(args = {}) + token = auth.verify_code_and_generate_token(args) + token_data = JWT.decode( + token, + Facades::Container.get(:auth_secret), + true, + { algorithm: 'HS256' } + )[0] + { + content: { + token: token, + tokenData: token_data + } + } + end + + def handle_authentication_logout(_args = {}) + { + content: nil, + status: 204 + } + end + + def auth + ForestAdminAgent::Auth::AuthManager.new + end + + protected + + def get_and_check_rendering_id(params) + raise Error, ForestAdminAgent::Utils::ErrorMessages::MISSING_RENDERING_ID unless params['renderingId'] + + begin + Integer(params['renderingId']) + rescue ArgumentError + raise Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID + end + + params['renderingId'] + end + end + end + end +end From ebdf6491aae81d7c882dfea03ba08b04da53863c Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 19 Sep 2023 14:48:23 +0200 Subject: [PATCH 15/40] fix(facade): typo --- .../lib/forest_admin_agent/facades/container.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/facades/container.rb b/packages/forest_admin_agent/lib/forest_admin_agent/facades/container.rb index 5d4e5ec3e..f97c77900 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/facades/container.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/facades/container.rb @@ -12,7 +12,7 @@ def self.config_from_cache def self.get(key) raise "Key #{key} not found in container" unless config_from_cache.key?(key) - cache[key] + config_from_cache[key] end end end From d9925e11b2c096896588dadab784bbc004cc447f Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 19 Sep 2023 17:16:42 +0200 Subject: [PATCH 16/40] chore: typing added on auth classes --- .../routes/abstract_route.rb | 2 -- .../routes/security/authentication.rb | 2 +- .../forest_admin_agent/auth/auth_manager.rbs | 14 +++++++++++++ .../auth/oauth2/forest_provider.rbs | 14 +++++++++++++ .../auth/oauth2/forest_resource_owner.rbs | 15 +++++++++++++ .../auth/oidc_client_manager.rbs | 15 +++++++++++++ .../builder/agent_factory.rbs | 21 +++++++++++++++++++ .../forest_admin_agent/facades/container.rbs | 9 ++++++++ .../sig/forest_admin_agent/http/router.rbs | 9 ++++++++ .../routes/abstract_route.rbs | 12 +++++++++++ .../routes/security/authentication.rbs | 14 +++++++++++++ .../routes/system/health_check.rbs | 10 +++++++++ 12 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 packages/forest_admin_agent/sig/forest_admin_agent/auth/auth_manager.rbs create mode 100644 packages/forest_admin_agent/sig/forest_admin_agent/auth/oauth2/forest_provider.rbs create mode 100644 packages/forest_admin_agent/sig/forest_admin_agent/auth/oauth2/forest_resource_owner.rbs create mode 100644 packages/forest_admin_agent/sig/forest_admin_agent/auth/oidc_client_manager.rbs create mode 100644 packages/forest_admin_agent/sig/forest_admin_agent/builder/agent_factory.rbs create mode 100644 packages/forest_admin_agent/sig/forest_admin_agent/facades/container.rbs create mode 100644 packages/forest_admin_agent/sig/forest_admin_agent/http/router.rbs create mode 100644 packages/forest_admin_agent/sig/forest_admin_agent/routes/abstract_route.rbs create mode 100644 packages/forest_admin_agent/sig/forest_admin_agent/routes/security/authentication.rbs create mode 100644 packages/forest_admin_agent/sig/forest_admin_agent/routes/system/health_check.rbs diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/abstract_route.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/abstract_route.rb index f6fe2b0cd..981c0f4fe 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/routes/abstract_route.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/abstract_route.rb @@ -1,8 +1,6 @@ module ForestAdminAgent module Routes class AbstractRoute - attr_reader :request - def initialize @routes = {} setup_routes diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb index a6d7d9a77..d087732cc 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb @@ -73,7 +73,7 @@ def get_and_check_rendering_id(params) raise Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID end - params['renderingId'] + params['renderingId'].to_i end end end diff --git a/packages/forest_admin_agent/sig/forest_admin_agent/auth/auth_manager.rbs b/packages/forest_admin_agent/sig/forest_admin_agent/auth/auth_manager.rbs new file mode 100644 index 000000000..9e3877592 --- /dev/null +++ b/packages/forest_admin_agent/sig/forest_admin_agent/auth/auth_manager.rbs @@ -0,0 +1,14 @@ +module ForestAdminAgent + module Auth + class AuthManager + @oidc: ForestAdminAgent::Auth::OidcClientManager + + def initialize: -> void + def start: (untyped rendering_id) -> untyped + def verify_code_and_generate_token: (untyped params) -> untyped + + private + def get_rendering_id_from_state: (untyped state) -> untyped + end + end +end diff --git a/packages/forest_admin_agent/sig/forest_admin_agent/auth/oauth2/forest_provider.rbs b/packages/forest_admin_agent/sig/forest_admin_agent/auth/oauth2/forest_provider.rbs new file mode 100644 index 000000000..31df7df41 --- /dev/null +++ b/packages/forest_admin_agent/sig/forest_admin_agent/auth/oauth2/forest_provider.rbs @@ -0,0 +1,14 @@ +module ForestAdminAgent + module Auth + module OAuth2 + class ForestProvider + def initialize: (Integer , Array[String]) -> void + def get_resource_owner: -> ForestResourceOwner + + private + def create_resource_owner: -> ForestResourceOwner + def check_response: -> Array[String] + end + end + end +end diff --git a/packages/forest_admin_agent/sig/forest_admin_agent/auth/oauth2/forest_resource_owner.rbs b/packages/forest_admin_agent/sig/forest_admin_agent/auth/oauth2/forest_resource_owner.rbs new file mode 100644 index 000000000..5130a39b8 --- /dev/null +++ b/packages/forest_admin_agent/sig/forest_admin_agent/auth/oauth2/forest_resource_owner.rbs @@ -0,0 +1,15 @@ +module ForestAdminAgent + module Auth + module OAuth2 + class ForestResourceOwner + @data: Array[String] + @rendering_id: Integer + + def initialize: (Array[String] data, Integer rendering_id) -> void + def id: -> Integer + def expiration_in_seconds: -> Integer + def make_jwt: -> String + end + end + end +end diff --git a/packages/forest_admin_agent/sig/forest_admin_agent/auth/oidc_client_manager.rbs b/packages/forest_admin_agent/sig/forest_admin_agent/auth/oidc_client_manager.rbs new file mode 100644 index 000000000..d0bc260f0 --- /dev/null +++ b/packages/forest_admin_agent/sig/forest_admin_agent/auth/oidc_client_manager.rbs @@ -0,0 +1,15 @@ +module ForestAdminAgent + module Auth + class OidcClientManager + TTL_CONFIG: Integer + + def make_forest_provider: -> untyped + + private + + def setup_cache: -> { client_id: String, issuer: String, redirect_uri: String } + def register: -> Array[String] + def render_provider: -> untyped + end + end +end diff --git a/packages/forest_admin_agent/sig/forest_admin_agent/builder/agent_factory.rbs b/packages/forest_admin_agent/sig/forest_admin_agent/builder/agent_factory.rbs new file mode 100644 index 000000000..400d5cd9d --- /dev/null +++ b/packages/forest_admin_agent/sig/forest_admin_agent/builder/agent_factory.rbs @@ -0,0 +1,21 @@ +module ForestAdminAgent + module Builder + class AgentFactory + TTL_CONFIG: Integer + TTL_SCHEMA: Integer + @options: untyped + + attr_reader customizer: untyped + attr_reader container: untyped + attr_reader has_env_secret: untyped + def setup: (Array[string] options) -> untyped + def build: -> nil + + private + def send_schema: (?force: false) -> nil + def build_container: -> untyped + def build_cache: -> nil + def build_logger: -> untyped + end + end +end diff --git a/packages/forest_admin_agent/sig/forest_admin_agent/facades/container.rbs b/packages/forest_admin_agent/sig/forest_admin_agent/facades/container.rbs new file mode 100644 index 000000000..26c9fc4e8 --- /dev/null +++ b/packages/forest_admin_agent/sig/forest_admin_agent/facades/container.rbs @@ -0,0 +1,9 @@ +module ForestAdminAgent + module Facades + class Container + def self.instance: -> untyped + def self.config_from_cache: -> untyped + def self.get: (untyped key) -> untyped + end + end +end diff --git a/packages/forest_admin_agent/sig/forest_admin_agent/http/router.rbs b/packages/forest_admin_agent/sig/forest_admin_agent/http/router.rbs new file mode 100644 index 000000000..31d82c6f9 --- /dev/null +++ b/packages/forest_admin_agent/sig/forest_admin_agent/http/router.rbs @@ -0,0 +1,9 @@ +module ForestAdminAgent + module Http + class Router + def self.routes: -> Array[String] + def self.actions_routes: -> Array[String] + def self.api_charts_routes: -> Array[String] + end + end +end diff --git a/packages/forest_admin_agent/sig/forest_admin_agent/routes/abstract_route.rbs b/packages/forest_admin_agent/sig/forest_admin_agent/routes/abstract_route.rbs new file mode 100644 index 000000000..114e949e6 --- /dev/null +++ b/packages/forest_admin_agent/sig/forest_admin_agent/routes/abstract_route.rbs @@ -0,0 +1,12 @@ +module ForestAdminAgent + module Routes + class AbstractRoute + def initialize : -> void + def routes: -> {} + def add_route: (String, String,String, String) -> void + def setup: -> AbstractRoute + + def setup_routes: -> void + end + end +end diff --git a/packages/forest_admin_agent/sig/forest_admin_agent/routes/security/authentication.rbs b/packages/forest_admin_agent/sig/forest_admin_agent/routes/security/authentication.rbs new file mode 100644 index 000000000..356c0b739 --- /dev/null +++ b/packages/forest_admin_agent/sig/forest_admin_agent/routes/security/authentication.rbs @@ -0,0 +1,14 @@ +module ForestAdminAgent + module Routes + module Security + class Authentication + def setup_routes: -> Authentication + def handle_authentication: (?Hash[untyped, untyped] args) -> {content: {authorizationUrl: String}} + def handle_authentication_callback: (?Hash[untyped, untyped] args) -> {content: {token: String, tokenData: String}} + def handle_authentication_logout: (?Hash[untyped, untyped] _args) -> {content: nil, status: Integer} + def auth: -> ForestAdminAgent::Auth::AuthManager + def get_and_check_rendering_id: (Hash[untyped, untyped] params) -> Integer + end + end + end +end diff --git a/packages/forest_admin_agent/sig/forest_admin_agent/routes/system/health_check.rbs b/packages/forest_admin_agent/sig/forest_admin_agent/routes/system/health_check.rbs new file mode 100644 index 000000000..1cfd45286 --- /dev/null +++ b/packages/forest_admin_agent/sig/forest_admin_agent/routes/system/health_check.rbs @@ -0,0 +1,10 @@ +module ForestAdminAgent + module Routes + module System + class HealthCheck + def setup_routes: -> HealthCheck + def handle_request: (?Hash[untyped, untyped] _args) -> {content: nil, status: Integer} + end + end + end +end From 66d28568f3cf9486de80baa9f254ac9397e6e9d6 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 19 Sep 2023 17:23:24 +0200 Subject: [PATCH 17/40] chore: update script run_rspec errors to English --- bin/run_rspec | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/run_rspec b/bin/run_rspec index 8ab6eaad7..5eeb66f4e 100755 --- a/bin/run_rspec +++ b/bin/run_rspec @@ -4,19 +4,19 @@ require 'rspec' require 'rspec/core/rake_task' # Liste des dossiers à parcourir pour les tests -folders_to_test = %w[./packages/forestadmin_agent ./packages/forestadmin_rails] +folders_to_test = %w[./packages/forest_admin_agent ./packages/forest_admin_rails] # Boucle à travers les dossiers et exécute les tests RSpec avec la configuration spécifique folders_to_test.each do |folder| if File.directory?(folder) if File.exist?(folder) - puts "Exécution des tests RSpec dans le dossier : #{folder}" + puts "Running RSpec tests in the folder : #{folder}" RSpec::Core::Runner.run(%W[--require #{folder}/spec/spec_helper.rb #{folder}]) else - puts "Fichier de configuration RSpec non trouvé dans le dossier : #{folder}/spec" + puts "RSpec configuration file not found in folder : #{folder}/spec" end else - puts "Dossier non trouvé : #{folder}" + puts "Folder not found : #{folder}" end end From 1a99463e98b40cf8777fb100535308cea73c72f4 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 21 Sep 2023 11:39:42 +0200 Subject: [PATCH 18/40] feat(auth): add test on healthcheck --- .rubocop.yml | 3 ++ .../routes/system/healthcheck_spec.rb | 36 +++++++++++++++++++ .../forest_admin_agent/spec/spec_helper.rb | 3 ++ 3 files changed, 42 insertions(+) create mode 100644 packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/system/healthcheck_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index fffa27bf8..20176f24e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -113,3 +113,6 @@ Style/RedundantConstantBase: Layout/LineLength: Max: 120 + +RSpec/MultipleExpectations: + Max: 5 diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/system/healthcheck_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/system/healthcheck_spec.rb new file mode 100644 index 000000000..4eeb0b151 --- /dev/null +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/system/healthcheck_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' +require 'singleton' + +module ForestAdminAgent + module Routes + module System + describe HealthCheck do + before do + agent_factory = ForestAdminAgent::Builder::AgentFactory.instance + agent_factory.setup( + { + auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', + env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb', + is_production: false, + cache_dir: 'tmp/cache/forest_admin' + } + ) + end + + context 'when testing the HealthCheck class' do + it 'returns an empty content and a 204 status' do + result = described_class.handle_request + expect(result[:content]).to be_nil + expect(result[:status]).to eq 204 + end + + it 'adds the route forest' do + described_class.setup_routes + expect(described_class.routes.include?('forest')).to be true + expect(described_class.routes.length).to eq 1 + end + end + end + end + end +end diff --git a/packages/forest_admin_agent/spec/spec_helper.rb b/packages/forest_admin_agent/spec/spec_helper.rb index 1166c0792..04094364e 100644 --- a/packages/forest_admin_agent/spec/spec_helper.rb +++ b/packages/forest_admin_agent/spec/spec_helper.rb @@ -1,9 +1,12 @@ require 'simplecov' require 'simplecov_json_formatter' +require 'forest_admin_agent' + SimpleCov.formatter = SimpleCov::Formatter::JSONFormatter SimpleCov.start do add_filter 'spec' end + # Previous content of test helper now starts here # This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. From 63251ffe8399e26ebd045a676bcad4fb0333496b Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 21 Sep 2023 12:05:38 +0200 Subject: [PATCH 19/40] fix: test healthcheck --- .../routes/system/healthcheck_spec.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/system/healthcheck_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/system/healthcheck_spec.rb index 4eeb0b151..d70a75950 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/system/healthcheck_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/system/healthcheck_spec.rb @@ -5,6 +5,8 @@ module ForestAdminAgent module Routes module System describe HealthCheck do + subject(:healthcheck) { described_class.new } + before do agent_factory = ForestAdminAgent::Builder::AgentFactory.instance agent_factory.setup( @@ -19,15 +21,15 @@ module System context 'when testing the HealthCheck class' do it 'returns an empty content and a 204 status' do - result = described_class.handle_request + result = healthcheck.handle_request expect(result[:content]).to be_nil expect(result[:status]).to eq 204 end it 'adds the route forest' do - described_class.setup_routes - expect(described_class.routes.include?('forest')).to be true - expect(described_class.routes.length).to eq 1 + healthcheck.setup_routes + expect(healthcheck.routes.include?('forest')).to be true + expect(healthcheck.routes.length).to eq 1 end end end From a007c9fa94b5314a227997e74d2b05abcf11df2b Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 21 Sep 2023 17:42:03 +0200 Subject: [PATCH 20/40] chore: add tests on authentication --- .rubocop.yml | 3 + .../routes/security/authentication.rb | 1 + .../routes/security/authentication_spec.rb | 109 ++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 20176f24e..e9e44d321 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -116,3 +116,6 @@ Layout/LineLength: RSpec/MultipleExpectations: Max: 5 + +RSpec/ExampleLength: + Max: 20 diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb index d087732cc..87de6c4c1 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb @@ -43,6 +43,7 @@ def handle_authentication_callback(args = {}) true, { algorithm: 'HS256' } )[0] + { content: { token: token, diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb new file mode 100644 index 000000000..2e32b152e --- /dev/null +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb @@ -0,0 +1,109 @@ +require 'spec_helper' +require 'singleton' + +module ForestAdminAgent + module Routes + module Security + describe Authentication do + subject(:authentication) { described_class.new } + + before do + agent_factory = ForestAdminAgent::Builder::AgentFactory.instance + agent_factory.setup( + { + auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', + env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb', + is_production: false, + cache_dir: 'tmp/cache/forest_admin' + } + ) + end + + context 'when setup the routes' do + it 'adds the route forest_authentication' do + authentication.setup_routes + expect(authentication.routes.include?('forest_authentication')).to be true + expect(authentication.routes.include?('forest_authentication-callback')).to be true + expect(authentication.routes.include?('forest_logout')).to be true + expect(authentication.routes.length).to eq 3 + end + end + + context 'when handle the authentication' do + let(:rendering_id) { '10' } + let(:user) do + { + 'id' => '1', + 'email' => 'john.doe@example.com', + 'first_name' => 'John', + 'last_name' => 'Doe', + 'team' => 'Operations', + 'tags' => [ + { + 'key' => 'demo', + 'value' => '1234' + } + ], + 'rendering_id' => rendering_id, + 'exp' => (DateTime.now + (1 / 24.0)).to_time.to_i, + 'permission_level' => 'admin' + } + end + let(:token) { JWT.encode :user, ForestAdminAgent::Facades::Container.get(:auth_secret), 'HS256' } + let(:auth_manager) { instance_double(ForestAdminAgent::Auth::AuthManager) } + + before do + allow(ForestAdminAgent::Auth::AuthManager).to receive(:new).and_return(auth_manager) + allow(auth_manager).to receive(:start).with(any_args).and_return('https://api.development.forestadmin.com/oidc/...') + allow(auth_manager).to receive(:verify_code_and_generate_token).with(any_args).and_return(token) + end + + it 'returns an auth url on the handle_authentication method' do + params = { 'renderingId' => rendering_id } + result = authentication.handle_authentication params + expect(result[:content][:authorizationUrl]).to eq 'https://api.development.forestadmin.com/oidc/...' + end + + it 'raises an error if renderingId is not present' do + params = {} + expect do + authentication.handle_authentication params + end.to raise_error(Error, + ForestAdminAgent::Utils::ErrorMessages::MISSING_RENDERING_ID) + end + + it 'raises an error if renderingId is not an integer' do + params = { 'renderingId' => 'abc' } + expect do + authentication.handle_authentication params + end.to raise_error(Error, + ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID) + end + + it 'returns a token on the handle_authentication_callback method' do + result = authentication.handle_authentication_callback 'code' => 'abc', + 'state' => "{'renderingId': #{rendering_id}}" + expect(result[:content][:token]).to eq token + expect(result[:content][:tokenData]).to eq JWT.decode( + token, + Facades::Container.get(:auth_secret), + true, + { algorithm: 'HS256' } + )[0] + end + end + + context 'when handle the logout route' do + it 'returns a 204 status code' do + result = authentication.handle_authentication_logout + expect(result[:status]).to eq 204 + end + end + + it 'when handle auth it should return an AuthManager instance' do + expect(authentication.auth).to be_an_instance_of(ForestAdminAgent::Auth::AuthManager) + end + end + end + end +end From 79f01166dc070b0a0e48ed9431a1abc149923f21 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 22 Sep 2023 14:26:02 +0200 Subject: [PATCH 21/40] chore: auth manager call debug from container for implementing ssl_verify --- .../lib/forest_admin_agent/auth/auth_manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb index 52b391616..ecddd2c3d 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb @@ -15,7 +15,7 @@ def start(rendering_id) def verify_code_and_generate_token(params) raise Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_MISSING unless params['state'] - if Rails.env.development? || Rails.env.test? + if Facades::Container.get(:debug) OpenIDConnect.http_config do |options| options.ssl.verify = false end From fa060a316e60af3cb5738a035364227f6fe64dcc Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 22 Sep 2023 14:27:26 +0200 Subject: [PATCH 22/40] chore: add tests on auth manager --- .../auth/auth_manager_spec.rb | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/auth_manager_spec.rb diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/auth_manager_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/auth_manager_spec.rb new file mode 100644 index 000000000..fe497fa37 --- /dev/null +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/auth_manager_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' +require 'singleton' + +module ForestAdminAgent + module Auth + describe AuthManager do + subject(:auth_manager) { described_class.new } + + let(:oidc) { instance_double(ForestAdminAgent::Auth::OidcClientManager) } + let(:forest_provider) { instance_double(ForestAdminAgent::Auth::OAuth2::ForestProvider) } + let(:forest_resource_owner) { instance_double(ForestAdminAgent::Auth::OAuth2::ForestResourceOwner) } + let(:access_token) { instance_double(OpenIDConnect::AccessToken) } + + before do + agent_factory = ForestAdminAgent::Builder::AgentFactory.instance + agent_factory.setup( + { + auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', + env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb', + is_production: false, + cache_dir: 'tmp/cache/forest_admin', + debug: true + } + ) + + allow(ForestAdminAgent::Auth::OidcClientManager).to receive(:new).and_return(oidc) + allow(oidc).to receive(:make_forest_provider).with(any_args).and_return(forest_provider) + allow(forest_provider).to receive(:authorization_uri).with(any_args).and_return('https://api.development.forestadmin.com/oidc/...') + allow(forest_provider).to receive(:authorization_code=).with(any_args).and_return(nil) + allow(forest_provider).to receive(:access_token!).with(any_args).and_return(access_token) + allow(forest_provider).to receive(:get_resource_owner).with(access_token).and_return(forest_resource_owner) + allow(forest_resource_owner).to receive(:make_jwt).and_return('jwt') + end + + context 'when testing the AuthManager class' do + it 'returns an auth url on the start method' do + result = auth_manager.start 10 + expect(result).to eq 'https://api.development.forestadmin.com/oidc/...' + end + + it 'returns a token on the verify_code_and_generate_token method' do + result = auth_manager.verify_code_and_generate_token 'code' => 'abc', + 'state' => "{'renderingId': 10}" + expect(result).to eq 'jwt' + end + + it 'raises an error when the state is missing' do + expect do + auth_manager.verify_code_and_generate_token 'code' => 'abc' + end.to raise_error(ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_MISSING) + end + + it 'raises an error when the rendering state is missing' do + expect do + auth_manager.verify_code_and_generate_token 'code' => 'abc', + 'state' => "{'key': 'value'}" + end.to raise_error(ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_RENDERING_ID) + end + + it 'raises an error when the renderingId is not valid' do + expect do + auth_manager.verify_code_and_generate_token 'code' => 'abc', + 'state' => "{'renderingId': 'abc'}" + end.to raise_error(ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID) + end + end + end + end +end +# def build_stack +# container = Dry::Container.new.register(:cache, Lightly.new(life: 1)) +# container.register(:cache, Lightly.new(life: 1)) +# container.resolve(:cache).get 'config' do +# { +# auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', +# env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb' +# } +# end +# +# container +# end +# +# before do +# allow(ForestAdminAgent::Facades::Container).to receive(:instance).and_return(build_stack) +# end From cb246d7881b8b4a7df8b0939c045a1416a65002b Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 22 Sep 2023 14:43:05 +0200 Subject: [PATCH 23/40] fix: remove useless comment --- .../forest_admin_agent/auth/auth_manager_spec.rb | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/auth_manager_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/auth_manager_spec.rb index fe497fa37..a2f8dfe45 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/auth_manager_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/auth_manager_spec.rb @@ -67,19 +67,3 @@ module Auth end end end -# def build_stack -# container = Dry::Container.new.register(:cache, Lightly.new(life: 1)) -# container.register(:cache, Lightly.new(life: 1)) -# container.resolve(:cache).get 'config' do -# { -# auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', -# env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb' -# } -# end -# -# container -# end -# -# before do -# allow(ForestAdminAgent::Facades::Container).to receive(:instance).and_return(build_stack) -# end From 6317b97ee99810c15df528af0b0e234444c25b15 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 22 Sep 2023 15:12:29 +0200 Subject: [PATCH 24/40] chore: add test on oidc client manager --- .../auth/oidc_client_manager_spec.rb | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb new file mode 100644 index 000000000..2bba6ee4b --- /dev/null +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +module ForestAdminAgent + module Auth + describe OidcClientManager do + subject(:oidc_client_manager) { described_class.new } + + let(:rendering_id) { 10 } + + before do + agent_factory = ForestAdminAgent::Builder::AgentFactory.instance + agent_factory.setup( + { + auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', + env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb', + is_production: false, + cache_dir: 'tmp/cache/forest_admin', + forest_server_url: 'https://api.development.forestadmin.com' + } + ) + end + + context 'when testing the OidcClientManager class' do + it 'returns a forest provider on the make_forest_provider method' do + result = oidc_client_manager.make_forest_provider :rendering_id + expect(result).to be_a ForestAdminAgent::Auth::OAuth2::ForestProvider + end + + it 'setups the cache on the setup_cache method' do + cache_key = "#{Facades::Container.get(:auth_secret)}-client-data" + config_agent = ForestAdminAgent::Facades::Container.config_from_cache + cache = oidc_client_manager.send(:setup_cache, cache_key, config_agent) + expect(cache).to be_a Hash + expect(cache.key?(:client_id)).to be true + expect(cache[:issuer]).to eq 'https://api.development.forestadmin.com' + expect(cache[:redirect_uri]).to eq 'http://localhost:3000/forest/authentication/callback' + end + end + end + end +end From 10b1842bfc0f346f449bfd54758a5dd0a76bb2c8 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 22 Sep 2023 17:42:18 +0200 Subject: [PATCH 25/40] chore: add sig on rendering_id forestProvider --- .../sig/forest_admin_agent/auth/oauth2/forest_provider.rbs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/forest_admin_agent/sig/forest_admin_agent/auth/oauth2/forest_provider.rbs b/packages/forest_admin_agent/sig/forest_admin_agent/auth/oauth2/forest_provider.rbs index 31df7df41..a07f05f97 100644 --- a/packages/forest_admin_agent/sig/forest_admin_agent/auth/oauth2/forest_provider.rbs +++ b/packages/forest_admin_agent/sig/forest_admin_agent/auth/oauth2/forest_provider.rbs @@ -2,6 +2,8 @@ module ForestAdminAgent module Auth module OAuth2 class ForestProvider + attr_reader rendering_id: Integer + def initialize: (Integer , Array[String]) -> void def get_resource_owner: -> ForestResourceOwner From 63898014c4eae022e3aa7b3f43fe5426199ad18b Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 22 Sep 2023 17:43:00 +0200 Subject: [PATCH 26/40] chore: remove oidc_config from coverage --- packages/forest_admin_agent/spec/spec_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/forest_admin_agent/spec/spec_helper.rb b/packages/forest_admin_agent/spec/spec_helper.rb index 04094364e..5bf5c0b93 100644 --- a/packages/forest_admin_agent/spec/spec_helper.rb +++ b/packages/forest_admin_agent/spec/spec_helper.rb @@ -5,6 +5,7 @@ SimpleCov.formatter = SimpleCov::Formatter::JSONFormatter SimpleCov.start do add_filter 'spec' + add_filter 'lib/forest_admin_agent/auth/oauth2/oidc_config.rb' end # Previous content of test helper now starts here @@ -24,6 +25,7 @@ # # See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| + config.exclude_pattern = 'spec/**/spec_helper.rb' # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest # assertions if you prefer. From 51ec4bda636d3e41264d8c878816b2f8cfc83809 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 22 Sep 2023 17:43:29 +0200 Subject: [PATCH 27/40] chore: autoloader set inflection for oauth2 folder --- packages/forest_admin_agent/lib/forest_admin_agent.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent.rb b/packages/forest_admin_agent/lib/forest_admin_agent.rb index 762297b2a..40ca59727 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent.rb @@ -2,6 +2,7 @@ require 'zeitwerk' loader = Zeitwerk::Loader.for_gem +loader.inflector.inflect('oauth2' => 'OAuth2') loader.setup module ForestAdminAgent From fec970c651f0bf6f189730c97a883449dd4f9b78 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 25 Sep 2023 12:06:45 +0200 Subject: [PATCH 28/40] fix: test on oidc client manager --- .../forest_admin_agent/auth/oidc_client_manager_spec.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb index 2bba6ee4b..9d5763931 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb @@ -6,6 +6,7 @@ module Auth subject(:oidc_client_manager) { described_class.new } let(:rendering_id) { 10 } + let(:oidc_discover_response) { instance_double(OpenIDConnect::Discovery::Provider::Config::Response) } before do agent_factory = ForestAdminAgent::Builder::AgentFactory.instance @@ -18,6 +19,11 @@ module Auth forest_server_url: 'https://api.development.forestadmin.com' } ) + allow(OpenIDConnect::Discovery::Provider::Config::Response).to receive(:new).and_return(oidc_discover_response) + allow(oidc_discover_response).to receive(:expected_issuer=).receive_messages('https://api.development.forestadmin.com') + allow(oidc_discover_response).to receive(:validate!).and_return(true) + allow(oidc_discover_response).to receive(:raw) + .receive_messages({ 'registration_endpoint' => 'https://api.development.forestadmin.com/oidc/reg' }) end context 'when testing the OidcClientManager class' do @@ -30,9 +36,9 @@ module Auth cache_key = "#{Facades::Container.get(:auth_secret)}-client-data" config_agent = ForestAdminAgent::Facades::Container.config_from_cache cache = oidc_client_manager.send(:setup_cache, cache_key, config_agent) + puts cache.inspect expect(cache).to be_a Hash expect(cache.key?(:client_id)).to be true - expect(cache[:issuer]).to eq 'https://api.development.forestadmin.com' expect(cache[:redirect_uri]).to eq 'http://localhost:3000/forest/authentication/callback' end end From 073595e2d9502499f49185ea69ca19266a4b29f7 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 25 Sep 2023 15:45:26 +0200 Subject: [PATCH 29/40] fix(auth): forest_provider exception call --- .../auth/oauth2/forest_provider.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb index feeb33460..0488c4990 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb @@ -38,22 +38,22 @@ def check_response when 200 server_error = response.body.key?('errors') ? response.body['errors'][0] : nil if server_error && - server_error['name'] == ForestAdminAgent::Utils::ErrorMessages::TWO_FACTOR_AUTHENTICATION_REQUIRED - raise Error, ForestAdminAgent::Utils::ErrorMessages::TWO_FACTOR_AUTHENTICATION_REQUIRED + server_error['name'] == Utils::ErrorMessages::TWO_FACTOR_AUTHENTICATION_REQUIRED + raise Error, Utils::ErrorMessages::TWO_FACTOR_AUTHENTICATION_REQUIRED end response.body.with_indifferent_access when 400 - raise BadRequest.new('API Access Failed', response) + raise OpenIDConnect::BadRequest.new('API Access Failed', response) when 401 - raise Unauthorized.new(ForestAdminAgent::Utils::ErrorMessages::AUTHORIZATION_FAILED, response) + raise OpenIDConnect::Unauthorized.new(Utils::ErrorMessages::AUTHORIZATION_FAILED, response) when 404 - raise HttpError.new(res.status, ForestAdminAgent::Utils::ErrorMessages::SECRET_NOT_FOUND, response) + raise OpenIDConnect::HttpError.new(response.status, Utils::ErrorMessages::SECRET_NOT_FOUND, response) when 422 - raise HttpError.new(res.status, - ForestAdminAgent::Utils::ErrorMessages::SECRET_AND_RENDERINGID_INCONSISTENT, response) + raise OpenIDConnect::HttpError.new(response.status, + Utils::ErrorMessages::SECRET_AND_RENDERINGID_INCONSISTENT, response) else - raise HttpError.new(res.status, 'Unknown HttpError', response) + raise OpenIDConnect::HttpError.new(response.status, 'Unknown HttpError', response) end end end From 77549ddd75417d99c9c2cac22192b9c5ed65301e Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 25 Sep 2023 15:46:01 +0200 Subject: [PATCH 30/40] chore: add test on forest_provider --- .rubocop.yml | 7 +- .../auth/oauth2/forest_provider_spec.rb | 114 ++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_provider_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index e9e44d321..6761f87de 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -114,8 +114,11 @@ Style/RedundantConstantBase: Layout/LineLength: Max: 120 +RSpec/ExampleLength: + Max: 20 + RSpec/MultipleExpectations: Max: 5 -RSpec/ExampleLength: - Max: 20 +RSpec/MultipleMemoizedHelpers: + Max: 10 diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_provider_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_provider_spec.rb new file mode 100644 index 000000000..ae881fa47 --- /dev/null +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_provider_spec.rb @@ -0,0 +1,114 @@ +require 'spec_helper' +require 'faraday' + +module ForestAdminAgent + module Auth + module OAuth2 + include ForestAdminAgent::Utils + describe ForestProvider do + let(:rendering_id) { 10 } + let(:attributes) do + { + identifier: 'identifier', + redirect_uri: 'redirect_uri', + host: 'host', + secret: 'secret' + } + end + let(:access_token) { instance_double(OpenIDConnect::AccessToken) } + + subject(:forest_provider) { described_class.new(rendering_id, attributes) } + + context 'when creating a new ForestProvider' do + it 'initializes the forest provider' do + expect(forest_provider.rendering_id).to eq rendering_id + expect(forest_provider.authorization_endpoint).to eq '/oidc/auth' + expect(forest_provider.token_endpoint).to eq '/oidc/token' + expect(forest_provider.userinfo_endpoint).to eq "/liana/v2/renderings/#{rendering_id}/authorization" + end + end + + context 'when getting the resource owner' do + before do + allow(forest_provider).to receive_messages(secret: 'secret') + allow(forest_provider).to receive_messages(check_response: { data: 'data' }) + allow(access_token).to receive_messages(access_token: 'access_token') + allow(access_token).to receive_messages(client: forest_provider) + end + + it 'returns the resource owner' do + result = forest_provider.get_resource_owner(access_token) + expect(result).to be_instance_of ForestAdminAgent::Auth::OAuth2::ForestResourceOwner + end + end + + context 'when check response is called' do + before do + allow(access_token).to receive_messages(access_token: 'access_token') + allow(access_token).to receive_messages(client: forest_provider) + allow(Faraday::Connection).to receive(:new).and_return(faraday_connection) + end + + context 'when a response return 200 status' do + let(:faraday_connection) { instance_double(Faraday::Connection) } + let(:response_ok) { instance_double(Faraday::Response, status: 200, body: { 'data' => 'data' }) } + let(:response_ok_with_error) do + instance_double( + Faraday::Response, + status: 200, + body: { 'errors' => [{ 'name' => ErrorMessages::TWO_FACTOR_AUTHENTICATION_REQUIRED }] } + ) + end + + it 'returns the response body' do + allow(faraday_connection).to receive(:get).and_return(response_ok) + result = forest_provider.get_resource_owner(access_token) + expect(result).to be_instance_of ForestAdminAgent::Auth::OAuth2::ForestResourceOwner + end + + it 'raise an error when the body contains an error' do + allow(faraday_connection).to receive(:get).and_return(response_ok_with_error) + expect do + forest_provider.get_resource_owner(access_token) + end.to raise_error(ErrorMessages::TWO_FACTOR_AUTHENTICATION_REQUIRED) + end + end + + context 'when checking a response not 200 status' do + let(:faraday_connection) { instance_double(Faraday::Connection) } + let(:response_bad_request) { instance_double(Faraday::Response, status: 400) } + let(:response_unauthorized) { instance_double(Faraday::Response, status: 401) } + let(:response_not_found) { instance_double(Faraday::Response, status: 404) } + let(:response_unprocessable) { instance_double(Faraday::Response, status: 422) } + let(:response_internal_error) { instance_double(Faraday::Response, status: 500) } + + it 'raises an error when the response is 400' do + allow(faraday_connection).to receive(:get).and_return(response_bad_request) + expect { forest_provider.get_resource_owner(access_token) }.to raise_error(OpenIDConnect::BadRequest) + end + + it 'raises an error when the response is 401' do + allow(faraday_connection).to receive(:get).and_return(response_unauthorized) + expect { forest_provider.get_resource_owner(access_token) }.to raise_error(OpenIDConnect::Unauthorized) + end + + it 'raises an error when the response is 404' do + allow(faraday_connection).to receive(:get).and_return(response_not_found) + expect { forest_provider.get_resource_owner(access_token) }.to raise_error(OpenIDConnect::HttpError) + end + + it 'raises an error when the response is 422' do + allow(faraday_connection).to receive(:get).and_return(response_unprocessable) + expect { forest_provider.get_resource_owner(access_token) }.to raise_error(OpenIDConnect::HttpError) + end + + it 'raises an error when the response is 500' do + allow(faraday_connection).to receive(:get).and_return(response_internal_error) + expect { forest_provider.get_resource_owner(access_token) }.to raise_error(OpenIDConnect::HttpError) + end + end + end + end + end + end +end From c097c2fad528a63225f6d139839280e50191a93b Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 25 Sep 2023 17:16:48 +0200 Subject: [PATCH 31/40] fix: test on oidc client manager and error when forest is down --- .rubocop.yml | 1 + .../auth/oauth2/oidc_config.rb | 2 +- .../auth/oidc_client_manager.rb | 2 + .../auth/oidc_client_manager_spec.rb | 37 +++++++++++++++---- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 6761f87de..a94e31595 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -28,6 +28,7 @@ Metrics/AbcSize: Exclude: - 'packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb' - 'packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_provider.rb' + - 'packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb' Metrics/CyclomaticComplexity: Exclude: diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/oidc_config.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/oidc_config.rb index 13750abbb..0cf6f1a3c 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/oidc_config.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/oidc_config.rb @@ -11,7 +11,7 @@ def self.discover!(identifier, cache_options = {}) response.validate! end rescue SWD::Exception, OpenIDConnect::ValidationFailed => e - raise OpenIDConnect::DiscoveryFailed, e.message + raise OpenIDConnect::Discovery::DiscoveryFailed, e.message end class Resource < OpenIDConnect::Discovery::Provider::Config::Resource diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb index 530e70e74..56110efd6 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb @@ -36,6 +36,8 @@ def setup_cache(env_secret, config_agent) issuer: oidc_config.raw['issuer'], redirect_uri: credentials['redirect_uris'].first } + rescue OpenIDConnect::Discovery::DiscoveryFailed + raise Error, ForestAdminAgent::Utils::ErrorMessages::SERVER_DOWN end end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb index 9d5763931..622428a38 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb @@ -7,6 +7,11 @@ module Auth let(:rendering_id) { 10 } let(:oidc_discover_response) { instance_double(OpenIDConnect::Discovery::Provider::Config::Response) } + let(:oidc_discover_resource) { instance_double(OpenIDConnect::Discovery::Provider::Config::Resource) } + let(:faraday_connection) { instance_double(Faraday::Connection) } + let(:register) do + instance_double(Faraday::Response, body: { 'client_id' => 'client_id', 'redirect_uris' => ['redirect_uri'] }) + end before do agent_factory = ForestAdminAgent::Builder::AgentFactory.instance @@ -19,14 +24,23 @@ module Auth forest_server_url: 'https://api.development.forestadmin.com' } ) - allow(OpenIDConnect::Discovery::Provider::Config::Response).to receive(:new).and_return(oidc_discover_response) - allow(oidc_discover_response).to receive(:expected_issuer=).receive_messages('https://api.development.forestadmin.com') - allow(oidc_discover_response).to receive(:validate!).and_return(true) - allow(oidc_discover_response).to receive(:raw) - .receive_messages({ 'registration_endpoint' => 'https://api.development.forestadmin.com/oidc/reg' }) end - context 'when testing the OidcClientManager class' do + context 'when then oidc is called' do + before do + allow(OpenIDConnect::Discovery::Provider::Config::Response).to receive(:new) + .and_return(oidc_discover_response) + allow(oidc_discover_response).to receive_messages(:expected_issuer= => 'https://api.development.forestadmin.com') + allow(oidc_discover_response).to receive_messages(validate!: true) + allow(oidc_discover_response).to receive(:raw) + .and_return({ 'registration_endpoint' => 'https://api.development.forestadmin.com/oidc/reg' }) + allow(OpenIDConnect::Discovery::Provider::Config::Resource).to receive(:new) + .and_return(oidc_discover_resource) + allow(oidc_discover_resource).to receive(:discover!).and_return(oidc_discover_response) + allow(Faraday::Connection).to receive(:new).and_return(faraday_connection) + allow(faraday_connection).to receive(:post).and_return(register) + end + it 'returns a forest provider on the make_forest_provider method' do result = oidc_client_manager.make_forest_provider :rendering_id expect(result).to be_a ForestAdminAgent::Auth::OAuth2::ForestProvider @@ -36,10 +50,17 @@ module Auth cache_key = "#{Facades::Container.get(:auth_secret)}-client-data" config_agent = ForestAdminAgent::Facades::Container.config_from_cache cache = oidc_client_manager.send(:setup_cache, cache_key, config_agent) - puts cache.inspect expect(cache).to be_a Hash expect(cache.key?(:client_id)).to be true - expect(cache[:redirect_uri]).to eq 'http://localhost:3000/forest/authentication/callback' + expect(cache[:redirect_uri]).to eq 'redirect_uri' + end + end + + context 'when then oidc is called and forest api is down' do + it 'raises an error' do + expect do + oidc_client_manager.make_forest_provider :rendering_id + end.to raise_error(ForestAdminAgent::Utils::ErrorMessages::SERVER_DOWN) end end end From d447e8c71de1bdba8d25616d21e755dfb5fe1b57 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 25 Sep 2023 18:07:53 +0200 Subject: [PATCH 32/40] chore: add test on forest resource owner --- .../auth/oauth2/forest_resource_owner_spec.rb | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_resource_owner_spec.rb diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_resource_owner_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_resource_owner_spec.rb new file mode 100644 index 000000000..f7096e0e9 --- /dev/null +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_resource_owner_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' +require 'singleton' + +module ForestAdminAgent + module Auth + module OAuth2 + describe ForestResourceOwner do + subject(:forest_resource_owner) { described_class.new(data, rendering_id) } + + let(:rendering_id) { 10 } + let(:data) do + { + 'id' => 'id', + 'attributes' => { + 'email' => 'email', + 'first_name' => 'john', + 'last_name' => 'doe', + 'teams' => ['team'], + 'tags' => ['tag'], + 'permission_level' => 'permission_level' + } + } + end + + before do + agent_factory = ForestAdminAgent::Builder::AgentFactory.instance + agent_factory.setup( + { + auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', + env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb', + is_production: false, + cache_dir: 'tmp/cache/forest_admin', + debug: true + } + ) + end + + context 'when creating a new ForestResourceOwner' do + it 'initializes the forest resource owner' do + expect(forest_resource_owner.id).to eq 'id' + expect(forest_resource_owner.expiration_in_seconds).to eq (DateTime.now + (1 / 24.0)).to_time.to_i + end + + it 'makes a jwt' do + jwt = forest_resource_owner.make_jwt + decoded_jwt = JWT.decode jwt, Facades::Container.get(:auth_secret), true, { algorithm: 'HS256' } + puts decoded_jwt + h = { + 'id' => 'id', + 'email' => 'email', + 'first_name' => 'john', + 'last_name' => 'doe', + 'team' => 'team', + 'tags' => ['tag'], + 'rendering_id' => 10, + 'exp' => forest_resource_owner.expiration_in_seconds, + 'permission_level' => 'permission_level' + } + expect(decoded_jwt[0]).to eq h + end + end + end + end + end +end From e0cb5a4e484200545da7ae0ddea3fff2777a6ee7 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 26 Sep 2023 14:54:03 +0200 Subject: [PATCH 33/40] chore: test cache dir --- .../spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb index 622428a38..11599b62f 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb @@ -14,6 +14,7 @@ module Auth end before do + Dir.mkdir('tmp/cache/forest_admin') agent_factory = ForestAdminAgent::Builder::AgentFactory.instance agent_factory.setup( { From 6322c59efd83bbbd90819076ffce443f4320a73d Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 26 Sep 2023 14:56:56 +0200 Subject: [PATCH 34/40] fix: dir cache --- .rubocop.yml | 3 +++ .../lib/forest_admin_agent/auth/oidc_client_manager_spec.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index a94e31595..5a87c4751 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -123,3 +123,6 @@ RSpec/MultipleExpectations: RSpec/MultipleMemoizedHelpers: Max: 10 + +Lint/NonAtomicFileOperation: + Enabled: false diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb index 11599b62f..94535f5d3 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb @@ -14,7 +14,7 @@ module Auth end before do - Dir.mkdir('tmp/cache/forest_admin') + Dir.mkdir('tmp/cache/forest_admin') unless Dir.exist?('tmp/cache/forest_admin') agent_factory = ForestAdminAgent::Builder::AgentFactory.instance agent_factory.setup( { From 4e49bbedfb8b57510307d206d7ab3eb56746a9e6 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 26 Sep 2023 16:31:29 +0200 Subject: [PATCH 35/40] refactor: oidc client manager --- .../lib/forest_admin_agent/auth/oidc_client_manager.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb index 56110efd6..71070f634 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oidc_client_manager.rb @@ -20,7 +20,7 @@ def make_forest_provider(rendering_id) def setup_cache(env_secret, config_agent) lightly = Lightly.new(life: TTL, dir: "#{config_agent[:cache_dir]}/issuer") lightly.get env_secret do - oidc_config = OAuth2::OidcConfig.discover! config_agent[:forest_server_url] + oidc_config = retrieve_config(config_agent[:forest_server_url]) credentials = register( config_agent[:env_secret], oidc_config.raw['registration_endpoint'], @@ -36,8 +36,6 @@ def setup_cache(env_secret, config_agent) issuer: oidc_config.raw['issuer'], redirect_uri: credentials['redirect_uris'].first } - rescue OpenIDConnect::Discovery::DiscoveryFailed - raise Error, ForestAdminAgent::Utils::ErrorMessages::SERVER_DOWN end end @@ -62,6 +60,12 @@ def render_provider(cache, rendering_id, secret) } ) end + + def retrieve_config(uri) + OAuth2::OidcConfig.discover! uri + rescue OpenIDConnect::Discovery::DiscoveryFailed + raise Error, ForestAdminAgent::Utils::ErrorMessages::SERVER_DOWN + end end end end From 1d2a2c971080fb265be3d95845b84e123300d685 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 26 Sep 2023 16:32:18 +0200 Subject: [PATCH 36/40] chore: move config agent factory to spec_helper --- .../auth/auth_manager_spec.rb | 11 ------ .../auth/oauth2/forest_resource_owner_spec.rb | 13 ------- .../auth/oidc_client_manager_spec.rb | 34 ++++++------------- .../routes/security/authentication_spec.rb | 12 ------- .../routes/system/healthcheck_spec.rb | 12 ------- .../forest_admin_agent/spec/spec_helper.rb | 17 ++++++++++ 6 files changed, 28 insertions(+), 71 deletions(-) diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/auth_manager_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/auth_manager_spec.rb index a2f8dfe45..db1aafed4 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/auth_manager_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/auth_manager_spec.rb @@ -12,17 +12,6 @@ module Auth let(:access_token) { instance_double(OpenIDConnect::AccessToken) } before do - agent_factory = ForestAdminAgent::Builder::AgentFactory.instance - agent_factory.setup( - { - auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', - env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb', - is_production: false, - cache_dir: 'tmp/cache/forest_admin', - debug: true - } - ) - allow(ForestAdminAgent::Auth::OidcClientManager).to receive(:new).and_return(oidc) allow(oidc).to receive(:make_forest_provider).with(any_args).and_return(forest_provider) allow(forest_provider).to receive(:authorization_uri).with(any_args).and_return('https://api.development.forestadmin.com/oidc/...') diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_resource_owner_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_resource_owner_spec.rb index f7096e0e9..9e86474bc 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_resource_owner_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_resource_owner_spec.rb @@ -22,19 +22,6 @@ module OAuth2 } end - before do - agent_factory = ForestAdminAgent::Builder::AgentFactory.instance - agent_factory.setup( - { - auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', - env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb', - is_production: false, - cache_dir: 'tmp/cache/forest_admin', - debug: true - } - ) - end - context 'when creating a new ForestResourceOwner' do it 'initializes the forest resource owner' do expect(forest_resource_owner.id).to eq 'id' diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb index 94535f5d3..61a4ceb1e 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb @@ -9,25 +9,21 @@ module Auth let(:oidc_discover_response) { instance_double(OpenIDConnect::Discovery::Provider::Config::Response) } let(:oidc_discover_resource) { instance_double(OpenIDConnect::Discovery::Provider::Config::Resource) } let(:faraday_connection) { instance_double(Faraday::Connection) } - let(:register) do - instance_double(Faraday::Response, body: { 'client_id' => 'client_id', 'redirect_uris' => ['redirect_uri'] }) - end - before do - Dir.mkdir('tmp/cache/forest_admin') unless Dir.exist?('tmp/cache/forest_admin') - agent_factory = ForestAdminAgent::Builder::AgentFactory.instance - agent_factory.setup( - { - auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', - env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb', - is_production: false, - cache_dir: 'tmp/cache/forest_admin', - forest_server_url: 'https://api.development.forestadmin.com' - } - ) + context 'when then oidc is called and forest api is down' do + it 'raises an error' do + class_double(ForestAdminAgent::Auth::OAuth2::OidcConfig, discover!: OpenIDConnect::Discovery::DiscoveryFailed) + expect do + oidc_client_manager.make_forest_provider :rendering_id + end.to raise_error(ForestAdminAgent::Utils::ErrorMessages::SERVER_DOWN) + end end context 'when then oidc is called' do + let(:register) do + instance_double(Faraday::Response, body: { 'client_id' => 'client_id', 'redirect_uris' => ['redirect_uri'] }) + end + before do allow(OpenIDConnect::Discovery::Provider::Config::Response).to receive(:new) .and_return(oidc_discover_response) @@ -56,14 +52,6 @@ module Auth expect(cache[:redirect_uri]).to eq 'redirect_uri' end end - - context 'when then oidc is called and forest api is down' do - it 'raises an error' do - expect do - oidc_client_manager.make_forest_provider :rendering_id - end.to raise_error(ForestAdminAgent::Utils::ErrorMessages::SERVER_DOWN) - end - end end end end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb index 2e32b152e..ca925ff50 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb @@ -7,18 +7,6 @@ module Security describe Authentication do subject(:authentication) { described_class.new } - before do - agent_factory = ForestAdminAgent::Builder::AgentFactory.instance - agent_factory.setup( - { - auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', - env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb', - is_production: false, - cache_dir: 'tmp/cache/forest_admin' - } - ) - end - context 'when setup the routes' do it 'adds the route forest_authentication' do authentication.setup_routes diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/system/healthcheck_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/system/healthcheck_spec.rb index d70a75950..815884071 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/system/healthcheck_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/system/healthcheck_spec.rb @@ -7,18 +7,6 @@ module System describe HealthCheck do subject(:healthcheck) { described_class.new } - before do - agent_factory = ForestAdminAgent::Builder::AgentFactory.instance - agent_factory.setup( - { - auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', - env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb', - is_production: false, - cache_dir: 'tmp/cache/forest_admin' - } - ) - end - context 'when testing the HealthCheck class' do it 'returns an empty content and a 204 status' do result = healthcheck.handle_request diff --git a/packages/forest_admin_agent/spec/spec_helper.rb b/packages/forest_admin_agent/spec/spec_helper.rb index 5bf5c0b93..6b1b86745 100644 --- a/packages/forest_admin_agent/spec/spec_helper.rb +++ b/packages/forest_admin_agent/spec/spec_helper.rb @@ -1,3 +1,4 @@ +require 'lightly' require 'simplecov' require 'simplecov_json_formatter' require 'forest_admin_agent' @@ -26,6 +27,22 @@ # See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| config.exclude_pattern = 'spec/**/spec_helper.rb' + config.before do + lightly = Lightly.new + lightly.clear 'config' + + agent_factory = ForestAdminAgent::Builder::AgentFactory.instance + agent_factory.setup( + { + auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e', + env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb', + is_production: false, + cache_dir: 'tmp/cache/forest_admin', + forest_server_url: 'https://api.development.forestadmin.com', + debug: true + } + ) + end # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest # assertions if you prefer. From 1d3b034a27b1f7728a841d98eff944f5b0421fac Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 26 Sep 2023 16:32:40 +0200 Subject: [PATCH 37/40] chore: remove test on rubocop --- .rubocop.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 5a87c4751..a94e31595 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -123,6 +123,3 @@ RSpec/MultipleExpectations: RSpec/MultipleMemoizedHelpers: Max: 10 - -Lint/NonAtomicFileOperation: - Enabled: false From c7edc0fe0cc3f96fb60f00f276d42909815dacc4 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 26 Sep 2023 17:24:01 +0200 Subject: [PATCH 38/40] refactor: rename get method from facade container --- .../lib/forest_admin_agent/auth/auth_manager.rb | 2 +- .../lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb | 2 +- .../lib/forest_admin_agent/facades/container.rb | 2 +- .../lib/forest_admin_agent/routes/security/authentication.rb | 2 +- .../auth/oauth2/forest_resource_owner_spec.rb | 2 +- .../lib/forest_admin_agent/auth/oidc_client_manager_spec.rb | 2 +- .../forest_admin_agent/routes/security/authentication_spec.rb | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb index ecddd2c3d..992604040 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb @@ -15,7 +15,7 @@ def start(rendering_id) def verify_code_and_generate_token(params) raise Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_MISSING unless params['state'] - if Facades::Container.get(:debug) + if Facades::Container.cache(:debug) OpenIDConnect.http_config do |options| options.ssl.verify = false end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb index 8fc2b10ce..4616e6b75 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb @@ -33,7 +33,7 @@ def make_jwt } JWT.encode user, - Facades::Container.get(:auth_secret), + Facades::Container.cache(:auth_secret), 'HS256' end end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/facades/container.rb b/packages/forest_admin_agent/lib/forest_admin_agent/facades/container.rb index f97c77900..bf35a7756 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/facades/container.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/facades/container.rb @@ -9,7 +9,7 @@ def self.config_from_cache instance.resolve(:cache).get('config') end - def self.get(key) + def self.cache(key) raise "Key #{key} not found in container" unless config_from_cache.key?(key) config_from_cache[key] diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb index 87de6c4c1..f2efbb2be 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb @@ -39,7 +39,7 @@ def handle_authentication_callback(args = {}) token = auth.verify_code_and_generate_token(args) token_data = JWT.decode( token, - Facades::Container.get(:auth_secret), + Facades::Container.cache(:auth_secret), true, { algorithm: 'HS256' } )[0] diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_resource_owner_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_resource_owner_spec.rb index 9e86474bc..d74b3335a 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_resource_owner_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oauth2/forest_resource_owner_spec.rb @@ -30,7 +30,7 @@ module OAuth2 it 'makes a jwt' do jwt = forest_resource_owner.make_jwt - decoded_jwt = JWT.decode jwt, Facades::Container.get(:auth_secret), true, { algorithm: 'HS256' } + decoded_jwt = JWT.decode jwt, Facades::Container.cache(:auth_secret), true, { algorithm: 'HS256' } puts decoded_jwt h = { 'id' => 'id', diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb index 61a4ceb1e..cb0ee9f4e 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/auth/oidc_client_manager_spec.rb @@ -44,7 +44,7 @@ module Auth end it 'setups the cache on the setup_cache method' do - cache_key = "#{Facades::Container.get(:auth_secret)}-client-data" + cache_key = "#{Facades::Container.cache(:auth_secret)}-client-data" config_agent = ForestAdminAgent::Facades::Container.config_from_cache cache = oidc_client_manager.send(:setup_cache, cache_key, config_agent) expect(cache).to be_a Hash diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb index ca925ff50..6b64bb401 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb @@ -74,7 +74,7 @@ module Security expect(result[:content][:token]).to eq token expect(result[:content][:tokenData]).to eq JWT.decode( token, - Facades::Container.get(:auth_secret), + Facades::Container.cache(:auth_secret), true, { algorithm: 'HS256' } )[0] From ffe4cd2a24f6505689909398fad21d10931c1110 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 26 Sep 2023 17:26:08 +0200 Subject: [PATCH 39/40] fix: test --- .../forest_admin_agent/routes/security/authentication_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb index 6b64bb401..be59aa5ab 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb @@ -37,7 +37,7 @@ module Security 'permission_level' => 'admin' } end - let(:token) { JWT.encode :user, ForestAdminAgent::Facades::Container.get(:auth_secret), 'HS256' } + let(:token) { JWT.encode :user, ForestAdminAgent::Facades::Container.cache(:auth_secret), 'HS256' } let(:auth_manager) { instance_double(ForestAdminAgent::Auth::AuthManager) } before do From 7280ab00635c014c784787d10e41c646287b6430 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 26 Sep 2023 17:33:43 +0200 Subject: [PATCH 40/40] chore: update spec_helper --- packages/forest_admin_agent/spec/spec_helper.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/forest_admin_agent/spec/spec_helper.rb b/packages/forest_admin_agent/spec/spec_helper.rb index 6b1b86745..15d1fa68a 100644 --- a/packages/forest_admin_agent/spec/spec_helper.rb +++ b/packages/forest_admin_agent/spec/spec_helper.rb @@ -26,7 +26,6 @@ # # See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| - config.exclude_pattern = 'spec/**/spec_helper.rb' config.before do lightly = Lightly.new lightly.clear 'config'