diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 9fbb41c031..c0b779e9be 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -124,10 +124,26 @@ module ActionController # :nodoc:
# If you need to add verification to the beginning of the callback chain, use prepend: true.
# * :with - Set the method to handle unverified request.
#
- # Valid unverified request handling methods are:
+ # Built-in unverified request handling methods are:
# * :exception - Raises ActionController::InvalidAuthenticityToken exception.
# * :reset_session - Resets the session.
# * :null_session - Provides an empty session during request but doesn't reset it completely. Used as default if :with option is not specified.
+ #
+ # You can also implement custom strategy classes for unverified request handling:
+ #
+ # class CustomStrategy
+ # def initialize(controller)
+ # @controller = controller
+ # end
+ #
+ # def handle_unverified_request
+ # # Custom behaviour for unverfied request
+ # end
+ # end
+ #
+ # class ApplicationController < ActionController:x:Base
+ # protect_from_forgery with: CustomStrategy
+ # end
def protect_from_forgery(options = {})
options = options.reverse_merge(prepend: false)
@@ -148,9 +164,18 @@ module ActionController # :nodoc:
private
def protection_method_class(name)
- ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
- rescue NameError
- raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, or :reset_session"
+ return name if name.is_a?(Class)
+
+ case name
+ when :null_session
+ ProtectionMethods::NullSession
+ when :reset_session
+ ProtectionMethods::ResetSession
+ when :exception
+ ProtectionMethods::Exception
+ else
+ raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, :reset_session, or a custom forgery protection class."
+ end
end
end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 265dae382a..f300c81751 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -103,6 +103,24 @@ class RequestForgeryProtectionControllerUsingNullSession < ActionController::Bas
end
end
+class RequestForgeryProtectionControllerUsingCustomStrategy < ActionController::Base
+ include RequestForgeryProtectionActions
+
+ class FakeException < Exception; end
+
+ class CustomStrategy
+ def initialize(controller)
+ @controller = controller
+ end
+
+ def handle_unverified_request
+ raise FakeException, "Raised a fake exception."
+ end
+ end
+
+ protect_from_forgery only: %w(index meta same_origin_js negotiate_same_origin), with: CustomStrategy
+end
+
class PrependProtectForgeryBaseController < ActionController::Base
before_action :custom_action
attr_accessor :called_callbacks
@@ -699,6 +717,7 @@ end
class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase
include RequestForgeryProtectionTests
+
def assert_blocked(&block)
assert_raises(ActionController::InvalidAuthenticityToken, &block)
end
@@ -720,6 +739,14 @@ class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::T
end
end
+class RequestForgeryProtectionControllerUsingCustomStrategyTest < ActionController::TestCase
+ include RequestForgeryProtectionTests
+
+ def assert_blocked(&block)
+ assert_raises(RequestForgeryProtectionControllerUsingCustomStrategy::FakeException, &block)
+ end
+end
+
class PrependProtectForgeryBaseControllerTest < ActionController::TestCase
PrependTrueController = Class.new(PrependProtectForgeryBaseController) do
protect_from_forgery prepend: true