module Roda::RodaPlugins::RouteCsrf::InstanceMethods
def check_csrf!(opts=OPTS, &block)
Otherwise, if a block is given, treat it as a routing block and yield to it, and
If the CSRF token is valid or the request does not require a CSRF token, return nil.
Check that the submitted CSRF token is valid, if the request requires a CSRF token.
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
def csrf_compare(s1, s2)
def csrf_compare(s1, s2) Rack::Utils.secure_compare(s1, s2) end
def csrf_field
The name of the hidden input tag containing the CSRF token. Also used as the name
def csrf_field csrf_options[:field] end
def csrf_header
The HTTP header name to use when submitting CSRF tokens in an HTTP header, if
def csrf_header csrf_options[:header] end
def csrf_hmac(random_data, method, path)
def csrf_hmac(random_data, method, path) OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, csrf_secret, "#{method.to_s.upcase}#{path}#{random_data}") end
def csrf_invalid_message(opts)
Returns error message string if the CSRF token is not valid.
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 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]] 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, @_request.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
def csrf_metatag
An HTML meta tag string containing a CSRF token that is not request-specific.
def csrf_metatag "<meta name=\"#{csrf_field}\" content=\"#{csrf_token}\" \/>" end
def csrf_options
def csrf_options opts[:route_csrf] end
def csrf_path(action)
worry about the different types of form actions (relative paths, absolute
This makes it easier to generate request-specific tokens without having to
Given a form action, return the appropriate path to use for the CSRF token.
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
def csrf_secret
secret, stored base64 encoded in the session (to handle cases where
If a secret has not already been specified, generate a random 32-byte
def csrf_secret key = session[csrf_options[:key]] ||= SecureRandom.base64(32) Base64_.decode64(key) end
def csrf_tag(*args)
An HTML hidden input tag string containing the CSRF token. See csrf_token for
def csrf_tag(*args) "<input type=\"hidden\" name=\"#{csrf_field}\" value=\"#{csrf_token(*args)}\" \/>" end
def csrf_token(path=nil, method=('POST' if path))
be assumed. To generate a token for a non-POST request method, pass the
argument. By default, it a path is provided, the POST request method will
The value of the csrf token. For a path specific token, provide a path
def csrf_token(path=nil, method=('POST' if path)) token = SecureRandom.random_bytes(31) token << csrf_hmac(token, method, path) [token].pack("m0") end
def use_request_specific_csrf_tokens?
def use_request_specific_csrf_tokens? csrf_options[:require_request_specific_tokens] end
def valid_csrf?(opts=OPTS)
Whether the submitted CSRF token is valid for the request. True if the
def valid_csrf?(opts=OPTS) csrf_invalid_message(opts).nil? end