request_forgery_protection.rb 6.9 KB
Newer Older
A
Arun Agrawal 已提交
1
require 'rack/session/abstract/id'
2
require 'action_controller/metal/exceptions'
J
Jeremy Kemper 已提交
3

4
module ActionController #:nodoc:
5 6
  class InvalidAuthenticityToken < ActionControllerError #:nodoc:
  end
7

8 9 10
  # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
  # by including a token in the rendered html for your application. This token is
  # stored as a random string in the session, to which an attacker does not have
11
  # access. When a request reaches your application, \Rails verifies the received
V
Vijay Dev 已提交
12 13 14 15 16 17 18
  # token with the token in the session. Only HTML and JavaScript requests are checked,
  # so this will not protect your XML API (presumably you'll have a different
  # authentication scheme there anyway). Also, GET requests are not protected as these
  # should be idempotent.
  #
  # It's important to remember that XML or JSON requests are also affected and if
  # you're building an API you'll need something like:
19 20 21
  #
  #   class ApplicationController < ActionController::Base
  #     protect_from_forgery
A
AvnerCohen 已提交
22
  #     skip_before_filter :verify_authenticity_token, if: :json_request?
23 24 25 26 27 28 29
  #
  #     protected
  #
  #     def json_request?
  #       request.format.json?
  #     end
  #   end
30 31
  #
  # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
32 33
  # which checks the token and resets the session if it doesn't match what was expected.
  # A call to this method is generated for new \Rails applications by default.
34 35 36
  #
  # The token parameter is named <tt>authenticity_token</tt> by default. The name and
  # value of this token must be added to every layout that renders forms by including
37
  # <tt>csrf_meta_tags</tt> in the html +head+.
38 39 40
  #
  # Learn more about CSRF attacks and securing your application in the
  # {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
41
  module RequestForgeryProtection
42
    extend ActiveSupport::Concern
43

44
    include AbstractController::Helpers
45
    include AbstractController::Callbacks
46 47

    included do
48 49
      # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+
      # sets it to <tt>:authenticity_token</tt> by default.
50 51
      config_accessor :request_forgery_protection_token
      self.request_forgery_protection_token ||= :authenticity_token
52

53
      # Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
54 55
      config_accessor :allow_forgery_protection
      self.allow_forgery_protection = true if allow_forgery_protection.nil?
56 57 58

      helper_method :form_authenticity_token
      helper_method :protect_against_forgery?
59
    end
60

61
    module ClassMethods
62
      # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
63
      #
D
David Heinemeier Hansson 已提交
64
      #   class FooController < ApplicationController
A
AvnerCohen 已提交
65
      #     protect_from_forgery except: :index
D
David Heinemeier Hansson 已提交
66
      #
67 68 69 70 71 72
      # You can disable csrf protection on controller-by-controller basis:
      #
      #   skip_before_filter :verify_authenticity_token
      #
      # It can also be disabled for specific controller actions:
      #
A
AvnerCohen 已提交
73
      #   skip_before_filter :verify_authenticity_token, except: [:create]
D
David Heinemeier Hansson 已提交
74 75 76
      #
      # Valid Options:
      #
77
      # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
78 79 80 81 82 83
      # * <tt>:with</tt> - Set the method to handle unverified request.
      #
      # Valid unverified request handling methods are:
      # * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
      # * <tt>:reset_session</tt> - Resets the session.
      # * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
84
      def protect_from_forgery(options = {})
85
        include protection_method_module(options[:with] || :null_session)
86
        self.request_forgery_protection_token ||= :authenticity_token
87
        prepend_before_filter :verify_authenticity_token, options
88
      end
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123

      private

      def protection_method_module(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'
      end
    end

    module ProtectionMethods
      module NullSession
        protected

        # This is the method that defines the application behavior when a request is found to be unverified.
        def handle_unverified_request
          request.session = NullSessionHash.new
          request.env['action_dispatch.request.flash_hash'] = nil
          request.env['rack.session.options'] = { skip: true }
          request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
        end

        class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
          def initialize
            super(nil, nil)
            @loaded = true
          end

          def exists?
            true
          end
        end

        class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
          def self.build(request)
124 125 126
            key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
            host          = request.host
            secure        = request.ssl?
127

128
            new(key_generator, host, secure)
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
          end

          def write(*)
            # nothing
          end
        end
      end

      module ResetSession
        protected

        def handle_unverified_request
          reset_session
        end
      end

      module Exception
        protected

        def handle_unverified_request
          raise ActionController::InvalidAuthenticityToken
        end
      end
152 153 154
    end

    protected
155
      # The actual before_filter that is used. Modify this to change how you handle unverified requests.
156
      def verify_authenticity_token
157
        unless verified_request?
158
          logger.warn "Can't verify CSRF token authenticity" if logger
159 160
          handle_unverified_request
        end
161 162
      end

163
      # Returns true or false if a request is verified. Checks:
164 165
      #
      # * is it a GET request?  Gets should be safe and idempotent
166
      # * Does the form_authenticity_token match the given token value from the params?
167
      # * Does the X-CSRF-Token header match the form_authenticity_token
168
      def verified_request?
169 170 171
        !protect_against_forgery? || request.get? ||
          form_authenticity_token == params[request_forgery_protection_token] ||
          form_authenticity_token == request.headers['X-CSRF-Token']
172
      end
Y
Yehuda Katz 已提交
173

P
Pratik Naik 已提交
174
      # Sets the token value for the current session.
175
      def form_authenticity_token
176
        session[:_csrf_token] ||= SecureRandom.base64(32)
177
      end
178

179 180 181 182 183
      # The form's authenticity parameter. Override to provide your own.
      def form_authenticity_param
        params[request_forgery_protection_token]
      end

184
      def protect_against_forgery?
185
        allow_forgery_protection
186
      end
187
  end
188
end