class Rack::Csrf
def self.field
def self.field @@field end
def self.header
def self.header @@header end
def self.key
def self.key @@key end
def self.metatag(env, options = {})
def self.metatag(env, options = {}) name = options.fetch(:name, '_csrf') %Q(<meta name="#{name}" content="#{token(env)}" />) end
def self.rackified_header
def self.rackified_header "HTTP_#{@@header.gsub('-', '_').upcase}" end
def self.tag(env)
def self.tag(env) %Q(<input type="hidden" name="#{field}" value="#{token(env)}" />) end
def self.token(env)
def self.token(env) env['rack.session'][key] ||= SecureRandom.urlsafe_base64(32) end
def any? list, request
def any? list, request pi = request.path_info.empty? ? '/' : request.path_info list.any? do |route| route =~ (request.request_method + ':' + pi) end end
def call(env)
def call(env) unless env['rack.session'] fail SessionUnavailable, 'Rack::Csrf depends on session middleware' end req = Rack::Request.new(env) let_it_pass = skip_checking(req) || !@http_methods.include?(req.request_method) || found_a_valid_token?(req) if let_it_pass @app.call(env) else fail InvalidCsrfToken if @raise_if_invalid [403, {CONTENT_TYPE => 'text/html', CONTENT_LENGTH => '0'}, []] end end
def found_a_valid_token? request
def found_a_valid_token? request token = self.class.token(request.env) Rack::Utils.secure_compare(request.params[self.class.field].to_s, token) || Rack::Utils.secure_compare(request.env[self.class.rackified_header].to_s, token) end
def initialize(app, opts = {})
def initialize(app, opts = {}) @app = app @raise_if_invalid = opts.fetch(:raise, false) @skip_list = opts.fetch(:skip, []).map {|r| /\A#{r}\Z/i} @skip_if = opts[:skip_if] @check_only_list = opts.fetch(:check_only, []).map {|r| /\A#{r}\Z/i} @@field = opts[:field] if opts[:field] @@header = opts[:header] if opts[:header] @@key = opts[:key] if opts[:key] standard_http_methods = %w(POST PUT DELETE PATCH) check_also = opts.fetch(:check_also, []) @http_methods = (standard_http_methods + check_also).flatten.uniq end
def skip_checking request
triggered by the +check_only+ option), it does not appear in the
only list is not empty (i.e., we are working in the "reverse mode"
the conditional skipping code return true or, when the check
Returns +true+ if the given request appears in the skip list or
def skip_checking request to_be_skipped = any? @skip_list, request to_be_skipped ||= @skip_if && @skip_if.call(request) to_be_checked = any? @check_only_list, request to_be_skipped || (!@check_only_list.empty? && !to_be_checked) end