module Roda::RodaPlugins::RouteCsrf::InstanceMethods
Public Instance Methods
Source
# File lib/roda/plugins/route_csrf.rb, line 198 def check_csrf!(opts=OPTS, &block) if msg = csrf_invalid_message(opts) if block @_request.on(&block) end case failure_action = opts.fetch(:csrf_failure, csrf_options[:csrf_failure]) when :raise raise InvalidToken, msg when :empty_403 @_response.status = 403 headers = @_response.headers headers.clear headers[RodaResponseHeaders::CONTENT_TYPE] = 'text/html' headers[RodaResponseHeaders::CONTENT_LENGTH] ='0' throw :halt, @_response.finish_with_body([]) when :clear_session session.clear when :csrf_failure_method @_request.on{_roda_route_csrf_failure(@_request)} when Proc RodaPlugins.warn "Passing a Proc as the :csrf_failure option value to check_csrf! is deprecated" @_request.on{instance_exec(@_request, &failure_action)} # Deprecated else raise RodaError, "Unsupported :csrf_failure option: #{failure_action.inspect}" end end end
Check that the submitted CSRF token is valid, if the request requires a CSRF token. If the CSRF token is valid or the request does not require a CSRF token, return nil. Otherwise, if a block is given, treat it as a routing block and yield to it, and if a block is not given, use the :csrf_failure option to determine how to handle it.
Source
# File lib/roda/plugins/route_csrf.rb, line 229 def csrf_field csrf_options[:field] end
The name of the hidden input tag containing the CSRF token. Also used as the name for the meta tag.
Source
# File lib/roda/plugins/route_csrf.rb, line 267 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, 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.
Source
# File lib/roda/plugins/route_csrf.rb, line 235 def csrf_header csrf_options[:header] end
The HTTP header name to use when submitting CSRF tokens in an HTTP header, if such support is enabled (it is not by default).
Source
# File lib/roda/plugins/route_csrf.rb, line 241 def csrf_metatag "<meta name=\"#{csrf_field}\" content=\"#{csrf_token}\" \/>" end
An HTML meta tag string containing a CSRF token that is not request-specific. It is not recommended to use this, as it doesn’t support request-specific tokens.
Source
# File lib/roda/plugins/route_csrf.rb, line 249 def csrf_path(action) case action when nil, '', /\A[#?]/ # use current path request.path when /\A(?:https?:\/)?\// # Either full URI or absolute path, extract just the path URI.parse(action).path else # relative path, join to current path URI.join(request.url, action).path end end
Given a form action, return the appropriate path to use for the CSRF token. This makes it easier to generate request-specific tokens without having to worry about the different types of form actions (relative paths, absolute paths, URLs, empty paths).
Source
# File lib/roda/plugins/route_csrf.rb, line 273 def csrf_tag(*args) "<input type=\"hidden\" name=\"#{csrf_field}\" value=\"#{csrf_token(*args)}\" \/>" end
An HTML hidden input tag string containing the CSRF token. See csrf_token
for arguments.
Source
# File lib/roda/plugins/route_csrf.rb, line 281 def csrf_token(path=nil, method=('POST' if path)) token = SecureRandom.random_bytes(31) token << csrf_hmac(token, method, path) [token].pack("m0") end
The value of the csrf token. For a path specific token, provide a path argument. By default, it a path is provided, the POST request method will be assumed. To generate a token for a non-POST request method, pass the method as the second argument.
Source
# File lib/roda/plugins/route_csrf.rb, line 288 def use_request_specific_csrf_tokens? csrf_options[:require_request_specific_tokens] end
Whether request-specific CSRF tokens should be used by default.
Source
# File lib/roda/plugins/route_csrf.rb, line 294 def valid_csrf?(opts=OPTS) csrf_invalid_message(opts).nil? end
Whether the submitted CSRF token is valid for the request. True if the request does not require a CSRF token.
Private Instance Methods
Source
# File lib/roda/plugins/route_csrf.rb, line 367 def csrf_compare(s1, s2) Rack::Utils.secure_compare(s1, s2) end
Perform a constant-time comparison of the two strings, returning true if they match and false otherwise.
Source
# File lib/roda/plugins/route_csrf.rb, line 372 def csrf_hmac(random_data, method, path) OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, csrf_secret, "#{method.to_s.upcase}#{path}#{random_data}") end
Return the HMAC-SHA-256 for the secret and the given arguments.
Source
# File lib/roda/plugins/route_csrf.rb, line 302 def csrf_invalid_message(opts) opts = opts.empty? ? csrf_options : csrf_options.merge(opts) method = request.request_method 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 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" end if (rack_csrf_key = opts[:upgrade_from_rack_csrf_key]) && (rack_csrf_value = session[rack_csrf_key]) && csrf_compare(rack_csrf_value, encoded_token) return end # 31 byte random initialization vector # 32 byte HMAC # 63 bytes total # 84 bytes when base64 encoded unless encoded_token.bytesize == 84 return "encoded token length is not 84" end begin submitted_hmac = Base64_.decode64(encoded_token) rescue ArgumentError return "encoded token is not valid base64" end random_data = submitted_hmac.slice!(0...31) 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" else unless csrf_compare(csrf_hmac(random_data, '', ''), submitted_hmac) "decoded token is not valid for either request method and path or for blank method and path" end end end
Returns error message string if the CSRF token is not valid. Returns nil if the CSRF token is valid.
Source
# File lib/roda/plugins/route_csrf.rb, line 362 def csrf_options opts[:route_csrf] end
Helper for getting the plugin options.
Source
# File lib/roda/plugins/route_csrf.rb, line 379 def csrf_secret key = session[csrf_options[:key]] ||= SecureRandom.base64(32) Base64_.decode64(key) end
If a secret has not already been specified, generate a random 32-byte secret, stored base64 encoded in the session (to handle cases where JSON is used for session serialization).