lib/roda/plugins/route_csrf.rb in roda-3.76.0 vs lib/roda/plugins/route_csrf.rb in roda-3.77.0
- old
+ new
@@ -40,10 +40,13 @@
# plugin :route_csrf
#
# This plugin supports the following options:
#
# :field :: Form input parameter name for CSRF token (default: '_csrf')
+ # :formaction_field :: Form input parameter name for path-specific CSRF tokens (used by the
+ # +csrf_formaction_tag+ method). If present, this parameter should be
+ # submitted as a hash, keyed by path, with CSRF token values.
# :header :: HTTP header name for CSRF token (default: 'X-CSRF-Token')
# :key :: Session key for CSRF secret (default: '_roda_csrf_secret')
# :require_request_specific_tokens :: Whether request-specific tokens are required (default: true).
# A false value will allow tokens that are not request-specific
# to also work. You should only set this to false if it is
@@ -84,10 +87,14 @@
# token is necessary for the request and there is no token provided
# or the provided token is not valid. Options can be provided to
# override any of the plugin options for this specific call.
# The :token option can be used to specify the provided CSRF token
# (instead of looking for the token in the submitted parameters).
+ # csrf_formaction_tag(path, method='POST') :: An HTML hidden input tag string containing the CSRF token, suitable
+ # for placing in an HTML form that has inputs that use formaction
+ # attributes to change the endpoint to which the form is submitted.
+ # Takes the same arguments as csrf_token.
# csrf_field :: The field name to use for the hidden tag containing the CSRF token.
# csrf_path(action) :: This takes an argument that would be the value of the HTML form's
# action attribute, and returns a path you can pass to csrf_token
# that should be valid for the form submission. The argument should
# either be nil or a string representing a relative path, absolute
@@ -150,10 +157,11 @@
# each time, mitigating compression ratio attacks such as BREACH.
module RouteCsrf
# Default CSRF option values
DEFAULTS = {
:field => '_csrf'.freeze,
+ :formaction_field => '_csrfs'.freeze,
:header => 'X-CSRF-Token'.freeze,
:key => '_roda_csrf_secret'.freeze,
:require_request_specific_tokens => true,
:csrf_failure => :raise,
:check_header => false,
@@ -250,10 +258,18 @@
# relative path, join to current path
URI.join(request.url, action).path
end
end
+ # An HTML hidden input tag string containing the CSRF token, used for inputs
+ # with formaction, so the same form can be used to submit to multiple endpoints
+ # depending on which button was clicked. See csrf_token for arguments, but the
+ # path argument is required.
+ def csrf_formaction_tag(path, *args)
+ "<input type=\"hidden\" name=\"#{csrf_options[:formaction_field]}[#{Rack::Utils.escape_html(path)}]\" value=\"#{csrf_token(path, *args)}\" \/>"
+ end
+
# An HTML hidden input tag string containing the CSRF token. See csrf_token for
# arguments.
def csrf_tag(*args)
"<input type=\"hidden\" name=\"#{csrf_field}\" value=\"#{csrf_token(*args)}\" \/>"
end
@@ -289,18 +305,21 @@
unless opts[:check_request_methods].include?(method)
return
end
+ path = @_request.path
+
unless encoded_token = opts[:token]
encoded_token = case opts[:check_header]
when :only
env[opts[:env_header]]
when true
return (csrf_invalid_message(opts.merge(:check_header=>false)) && csrf_invalid_message(opts.merge(:check_header=>:only)))
else
- @_request.params[opts[:field]]
+ params = @_request.params
+ ((formactions = params[opts[:formaction_field]]).is_a?(Hash) && (formactions[path])) || params[opts[:field]]
end
end
unless encoded_token.is_a?(String)
return "encoded token is not a string"
@@ -324,10 +343,10 @@
return "encoded token is not valid base64"
end
random_data = submitted_hmac.slice!(0...31)
- if csrf_compare(csrf_hmac(random_data, method, @_request.path), submitted_hmac)
+ if csrf_compare(csrf_hmac(random_data, method, path), submitted_hmac)
return
end
if opts[:require_request_specific_tokens]
"decoded token is not valid for request method and path"