From 7887fdab03d114b7e06e386aa2b50183397b1308 Mon Sep 17 00:00:00 2001 From: Nicolas Alexandre Date: Thu, 20 Jun 2024 16:21:25 +0200 Subject: [PATCH 1/4] feat: add route scope invalidation --- .../lib/forest_admin_agent/http/router.rb | 1 + .../routes/security/scope_invalidation.rb | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/routes/security/scope_invalidation.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 10cb385bd..4bb101651 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 @@ -9,6 +9,7 @@ def self.routes api_charts_routes, System::HealthCheck.new.routes, Security::Authentication.new.routes, + Security::ScopeInvalidation.new.routes, Charts::Charts.new.routes, Resources::Count.new.routes, Resources::Delete.new.routes, diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/scope_invalidation.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/scope_invalidation.rb new file mode 100644 index 000000000..7eb1cc5df --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/scope_invalidation.rb @@ -0,0 +1,28 @@ +module ForestAdminAgent + module Routes + module Security + class ScopeInvalidation < AbstractRoute + include ForestAdminAgent::Builder + include ForestAdminAgent::Services + def setup_routes + add_route( + 'forest_scope_invalidation', + 'POST', + '/scope-cache-invalidation', + ->(args) { handle_request(args) } + ) + + self + end + + def handle_request(args) + # Check if user is logged + Utils::QueryStringParser.parse_caller(args) + Permissions.invalidate_cache('forest.scopes') + + { content: nil, status: 204 } + end + end + end + end +end From 94b7f17c0efa3ace25e41b283a5f683a1f93624b Mon Sep 17 00:00:00 2001 From: Nicolas Alexandre Date: Thu, 20 Jun 2024 16:29:20 +0200 Subject: [PATCH 2/4] feat: add proxy meethod remove collection --- .../lib/forest_admin_agent/builder/agent_factory.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/builder/agent_factory.rb b/packages/forest_admin_agent/lib/forest_admin_agent/builder/agent_factory.rb index 872d74799..4e32b2940 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/builder/agent_factory.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/builder/agent_factory.rb @@ -26,6 +26,10 @@ def add_datasource(datasource, options = {}) self end + def remove_collection(names) + @customizer.remove_collection(names) + end + def add_chart(name, &definition) @customizer.add_chart(name, &definition) From 006edc7b649e1d14bf3e04702c9b61347e6ac62c Mon Sep 17 00:00:00 2001 From: Nicolas Alexandre Date: Thu, 20 Jun 2024 16:47:39 +0200 Subject: [PATCH 3/4] test: add test on scope invalidation route --- .../security/scope_invalidation_spec.rb | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/scope_invalidation_spec.rb diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/scope_invalidation_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/scope_invalidation_spec.rb new file mode 100644 index 000000000..e97b5fa05 --- /dev/null +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/scope_invalidation_spec.rb @@ -0,0 +1,113 @@ +require 'spec_helper' +require 'singleton' + +module ForestAdminAgent + module Routes + module Security + describe Authentication do + subject(:authentication) { described_class.new } + + 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.cache(: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 + args = { params: { 'renderingId' => rendering_id } } + result = authentication.handle_authentication args + expect(result[:content][:authorizationUrl]).to eq 'https://api.development.forestadmin.com/oidc/...' + end + + it 'raises an error if renderingId is not present' do + args = { params: {} } + expect do + authentication.handle_authentication args + end.to raise_error(Error, + ForestAdminAgent::Utils::ErrorMessages::MISSING_RENDERING_ID) + end + + it 'raises an error if renderingId is not an integer' do + args = { params: { 'renderingId' => 'abc' } } + expect do + authentication.handle_authentication args + end.to raise_error(Error, + ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID) + end + + it 'returns a token on the handle_authentication_callback method' do + args = { params: { 'code' => 'abc', 'state' => "{'renderingId': #{rendering_id}}" } } + result = authentication.handle_authentication_callback args + expect(result[:content][:token]).to eq token + expect(result[:content][:tokenData]).to eq JWT.decode( + token, + Facades::Container.cache(:auth_secret), + true, + { algorithm: 'HS256' } + )[0] + end + end + + context 'when callback is called with error argument in the query' do + it 'raises an error' do + args = { + params: { + error: 'TrialBlockedError', + error_description: 'Your free trial has ended...', + state: '{"renderingId"=>128}' + } + } + + expect do + authentication.handle_authentication_callback args + end.to raise_error(ForestAdminAgent::Http::Exceptions::AuthenticationOpenIdClient) + 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 74887284daea56055a84d911a02a0fdc8434f3cd Mon Sep 17 00:00:00 2001 From: Nicolas Alexandre Date: Thu, 20 Jun 2024 17:19:26 +0200 Subject: [PATCH 4/4] test: add test on scope invalidation route --- .../security/scope_invalidation_spec.rb | 111 ++++-------------- 1 file changed, 24 insertions(+), 87 deletions(-) diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/scope_invalidation_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/scope_invalidation_spec.rb index e97b5fa05..a65fbcdbf 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/scope_invalidation_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/scope_invalidation_spec.rb @@ -1,112 +1,49 @@ require 'spec_helper' -require 'singleton' +require 'shared/caller' module ForestAdminAgent module Routes module Security - describe Authentication do - subject(:authentication) { described_class.new } + describe ScopeInvalidation do + include_context 'with caller' + subject(:scope_invalidation) { described_class.new } 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 + it 'adds the route forest_scope_invalidation' do + scope_invalidation.setup_routes + expect(scope_invalidation.routes.include?('forest_scope_invalidation')).to be true + expect(scope_invalidation.routes.length).to eq 1 end end - context 'when handle the authentication' do - let(:rendering_id) { '10' } - let(:user) do + context 'when handle the scope invalidation' do + let(:args) 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' + headers: { 'HTTP_AUTHORIZATION' => bearer }, + params: { + 'collection_name' => 'user', + 'timezone' => 'Europe/Paris' + } } end - let(:token) { JWT.encode :user, ForestAdminAgent::Facades::Container.cache(:auth_secret), 'HS256' } - let(:auth_manager) { instance_double(ForestAdminAgent::Auth::AuthManager) } + let(:permissions) { class_double(ForestAdminAgent::Services::Permissions).as_stubbed_const } 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) + allow(permissions).to receive(:invalidate_cache).with(any_args).and_return(nil) end - it 'returns an auth url on the handle_authentication method' do - args = { params: { 'renderingId' => rendering_id } } - result = authentication.handle_authentication args - expect(result[:content][:authorizationUrl]).to eq 'https://api.development.forestadmin.com/oidc/...' - end - - it 'raises an error if renderingId is not present' do - args = { params: {} } - expect do - authentication.handle_authentication args - end.to raise_error(Error, - ForestAdminAgent::Utils::ErrorMessages::MISSING_RENDERING_ID) + it 'return 204 response' do + result = scope_invalidation.handle_request(args) + expect(result[:content]).to be_nil + expect(result[:status]).to eq 204 end - it 'raises an error if renderingId is not an integer' do - args = { params: { 'renderingId' => 'abc' } } - expect do - authentication.handle_authentication args - end.to raise_error(Error, - ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID) - end + it 'call the invalidate_cache method' do + scope_invalidation.handle_request(args) - it 'returns a token on the handle_authentication_callback method' do - args = { params: { 'code' => 'abc', 'state' => "{'renderingId': #{rendering_id}}" } } - result = authentication.handle_authentication_callback args - expect(result[:content][:token]).to eq token - expect(result[:content][:tokenData]).to eq JWT.decode( - token, - Facades::Container.cache(:auth_secret), - true, - { algorithm: 'HS256' } - )[0] + expect(permissions).to have_received(:invalidate_cache).with('forest.scopes') end end - - context 'when callback is called with error argument in the query' do - it 'raises an error' do - args = { - params: { - error: 'TrialBlockedError', - error_description: 'Your free trial has ended...', - state: '{"renderingId"=>128}' - } - } - - expect do - authentication.handle_authentication_callback args - end.to raise_error(ForestAdminAgent::Http::Exceptions::AuthenticationOpenIdClient) - 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