From 7ca7b8a42e98e8eb162b43f651b303656f6c91c1 Mon Sep 17 00:00:00 2001 From: "Aleksandar N. Kostadinov" Date: Thu, 19 Dec 2024 15:13:14 +0200 Subject: [PATCH] move connector extensions out of app/ --- app/lib/api_authentication/by_access_token.rb | 127 +---------------- .../access_token_authentication.rb | 28 ++-- lib/api_authentication.rb | 130 ++++++++++++++++++ .../by_authentication_token_test.rb | 2 +- 4 files changed, 146 insertions(+), 141 deletions(-) create mode 100644 lib/api_authentication.rb diff --git a/app/lib/api_authentication/by_access_token.rb b/app/lib/api_authentication/by_access_token.rb index cc114011b4..de9cf1c7c3 100644 --- a/app/lib/api_authentication/by_access_token.rb +++ b/app/lib/api_authentication/by_access_token.rb @@ -16,52 +16,10 @@ def current_user before_action :verify_access_token_scopes around_action :enforce_access_token_permission - rescue_from ApiAuthentication::ByAccessToken::Error, + rescue_from ApiAuthentication::Error, with: :show_access_key_permission_error end - module ReadOnlyTransaction - def read_only_transaction? - ::ApiAuthentication::ByAccessToken::PermissionEnforcer.read_only? - end - end - - module ConnectionExtension - extend ActiveSupport::Concern - - included do - prepend TransactionMethods - end - - module TransactionMethods - include ReadOnlyTransaction - - def begin_db_transaction - transaction = ::ApiAuthentication::ByAccessToken::PermissionEnforcer.start_transaction - transaction ? execute(transaction) : super - end - end - end - - module OracleEnhancedConnectionExtension - extend ActiveSupport::Concern - - included do - prepend TransactionMethods - end - - module TransactionMethods - include ReadOnlyTransaction - - def begin_db_transaction - super - - transaction = ::ApiAuthentication::ByAccessToken::PermissionEnforcer.set_transaction - execute(transaction) if transaction - end - end - end - protected module ClassMethods @@ -134,89 +92,6 @@ def verify_write_permission raise PermissionError unless authenticated_token.try(:permission) == PermissionEnforcer::READ_WRITE end - Error = Class.new(StandardError) - - ScopeError = Class.new(Error) - PermissionError = Class.new(Error) - - - module PermissionEnforcer - - extend self - - READ_ONLY = 'ro' - READ_WRITE = 'rw' - - def set_transaction - case level - when READ_ONLY then 'SET TRANSACTION READ ONLY' - when READ_WRITE then 'SET TRANSACTION READ WRITE' - end - end - - def start_transaction - case level - when READ_ONLY then 'START TRANSACTION READ ONLY' - when READ_WRITE then 'START TRANSACTION READ WRITE' - end - end - - class EnforceError < StandardError - end - - def enforce(access_token, &block) - self.level = access_token&.permission - - return yield unless requires_transaction? - - if connection.transaction_open? - raise "Can't use read-only Access Token with transactional fixtures" if Rails.env.test? - - error = EnforceError.new("couldn't open new transaction to enforce read-only access token") - System::ErrorReporting.report_error(error) - end - - connection.transaction(requires_new: true, &block) - rescue ActiveRecord::StatementInvalid => error - if error.message =~ /read(-|\s)only transaction/i - raise PermissionError, error.message, caller - else - raise - end - ensure - Rails.logger.info "PermissionEnforcer#ensure clear level" - self.level = nil - end - - def read_only? - level == READ_ONLY - end - - private - - def requires_transaction? - case level - when READ_ONLY then true - when READ_WRITE then false - end - end - - THREAD_VARIABLE = :__permission_enforcer_level - - def level=(level) - Rails.logger.info "PermissionEnforcer: level = #{level}" - Thread.current[THREAD_VARIABLE] = level - end - - def level - Thread.current[THREAD_VARIABLE] - end - - def connection - ActiveRecord::Base.connection - end - end - private def access_token diff --git a/config/initializers/access_token_authentication.rb b/config/initializers/access_token_authentication.rb index dfdcee839b..400c9e648a 100644 --- a/config/initializers/access_token_authentication.rb +++ b/config/initializers/access_token_authentication.rb @@ -1,23 +1,23 @@ # frozen_string_literal: true -Rails.application.config.to_prepare do - ActiveSupport.on_load(:active_record) do - if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) - ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do - include ApiAuthentication::ByAccessToken::ConnectionExtension - end +ActiveSupport.on_load(:active_record) do + require "api_authentication.rb" + + if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) + ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do + include ApiAuthentication::ConnectorExtensions::ConnectionExtension end + end - if defined?(ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter) - ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do - include ApiAuthentication::ByAccessToken::OracleEnhancedConnectionExtension - end + if defined?(ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter) + ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do + include ApiAuthentication::ConnectorExtensions::OracleEnhancedConnectionExtension end + end - if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) - ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do - include ApiAuthentication::ByAccessToken::ConnectionExtension - end + if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do + include ApiAuthentication::ConnectorExtensions::ConnectionExtension end end end diff --git a/lib/api_authentication.rb b/lib/api_authentication.rb new file mode 100644 index 0000000000..3243869f55 --- /dev/null +++ b/lib/api_authentication.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +module ApiAuthentication + + Error = Class.new(StandardError) + + ScopeError = Class.new(Error) + PermissionError = Class.new(Error) + + module PermissionEnforcer + + extend self + + READ_ONLY = 'ro' + READ_WRITE = 'rw' + + def set_transaction + case level + when READ_ONLY then 'SET TRANSACTION READ ONLY' + when READ_WRITE then 'SET TRANSACTION READ WRITE' + end + end + + def start_transaction + case level + when READ_ONLY then 'START TRANSACTION READ ONLY' + when READ_WRITE then 'START TRANSACTION READ WRITE' + end + end + + class EnforceError < StandardError + end + + def enforce(access_token, &block) + self.level = access_token&.permission + + return yield unless requires_transaction? + + if connection.transaction_open? + raise "Can't use read-only Access Token with transactional fixtures" if Rails.env.test? + + error = EnforceError.new("couldn't open new transaction to enforce read-only access token") + System::ErrorReporting.report_error(error) + end + + connection.transaction(requires_new: true, &block) + rescue ActiveRecord::StatementInvalid => error + if error.message =~ /read(-|\s)only transaction/i + raise PermissionError, error.message, caller + else + raise + end + ensure + Rails.logger.info "PermissionEnforcer#ensure clear level" + self.level = nil + end + + def read_only? + level == READ_ONLY + end + + private + + def requires_transaction? + case level + when READ_ONLY then true + when READ_WRITE then false + end + end + + THREAD_VARIABLE = :__permission_enforcer_level + + def level=(level) + Rails.logger.info "PermissionEnforcer: level = #{level}" + Thread.current[THREAD_VARIABLE] = level + end + + def level + Thread.current[THREAD_VARIABLE] + end + + def connection + ActiveRecord::Base.connection + end + end + + module ConnectorExtensions + module ReadOnlyTransaction + def read_only_transaction? + ::ApiAuthentication::PermissionEnforcer.read_only? + end + end + + module ConnectionExtension + extend ActiveSupport::Concern + + included do + prepend TransactionMethods + end + + module TransactionMethods + include ReadOnlyTransaction + + def begin_db_transaction + transaction = ::ApiAuthentication::PermissionEnforcer.start_transaction + transaction ? execute(transaction) : super + end + end + end + + module OracleEnhancedConnectionExtension + extend ActiveSupport::Concern + + included do + prepend TransactionMethods + end + + module TransactionMethods + include ReadOnlyTransaction + + def begin_db_transaction + super + + transaction = ::ApiAuthentication::PermissionEnforcer.set_transaction + execute(transaction) if transaction + end + end + end + end +end diff --git a/test/unit/api_authentication/by_authentication_token_test.rb b/test/unit/api_authentication/by_authentication_token_test.rb index d81cf92839..c54c43f844 100644 --- a/test/unit/api_authentication/by_authentication_token_test.rb +++ b/test/unit/api_authentication/by_authentication_token_test.rb @@ -44,7 +44,7 @@ def test_authenticated_token_correct_scope def test_rescue_handlers handlers = { - 'ApiAuthentication::ByAccessToken::Error' => :show_access_key_permission_error + 'ApiAuthentication::Error' => :show_access_key_permission_error } assert_equal handlers, rescue_handlers.to_h end