module ActionController::RequestForgeryProtection
def any_authenticity_token_valid? # :doc:
Checks if any of the authenticity tokens from the request are valid.
def any_authenticity_token_valid? # :doc: request_authenticity_tokens.any? do |token| valid_authenticity_token?(session, token) end end
def compare_with_global_token(token, session) # :doc:
def compare_with_global_token(token, session) # :doc: ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, global_csrf_token(session)) end
def compare_with_real_token(token, session) # :doc:
def compare_with_real_token(token, session) # :doc: ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session)) end
def csrf_token_hmac(session, identifier) # :doc:
def csrf_token_hmac(session, identifier) # :doc: OpenSSL::HMAC.digest( OpenSSL::Digest::SHA256.new, real_csrf_token(session), identifier ) end
def decode_csrf_token(encoded_csrf_token) # :nodoc:
def decode_csrf_token(encoded_csrf_token) # :nodoc: if urlsafe_csrf_tokens Base64.urlsafe_decode64(encoded_csrf_token) else begin Base64.strict_decode64(encoded_csrf_token) rescue ArgumentError Base64.urlsafe_decode64(encoded_csrf_token) end end end
def encode_csrf_token(csrf_token) # :nodoc:
def encode_csrf_token(csrf_token) # :nodoc: if urlsafe_csrf_tokens Base64.urlsafe_encode64(csrf_token, padding: false) else Base64.strict_encode64(csrf_token) end end
def form_authenticity_param # :doc:
The form's authenticity parameter. Override to provide your own.
def form_authenticity_param # :doc: params[request_forgery_protection_token] end
def form_authenticity_token(form_options: {}) # :doc:
Creates the authenticity token for the current request.
def form_authenticity_token(form_options: {}) # :doc: masked_authenticity_token(session, form_options: form_options) end
def generate_csrf_token # :nodoc:
def generate_csrf_token # :nodoc: if urlsafe_csrf_tokens SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH) else SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH) end end
def global_csrf_token(session) # :doc:
def global_csrf_token(session) # :doc: csrf_token_hmac(session, GLOBAL_CSRF_TOKEN_IDENTIFIER) end
def handle_unverified_request # :doc:
def handle_unverified_request # :doc: protection_strategy = forgery_protection_strategy.new(self) if protection_strategy.respond_to?(:warning_message) protection_strategy.warning_message = unverified_request_warning_message end protection_strategy.handle_unverified_request end
def mark_for_same_origin_verification! # :doc:
GET requests are checked for cross-origin JavaScript after rendering.
def mark_for_same_origin_verification! # :doc: @marked_for_same_origin_verification = request.get? end
def marked_for_same_origin_verification? # :doc:
JavaScript responses are only served to same-origin GET requests.
If the +verify_authenticity_token+ before_action ran, verify that
def marked_for_same_origin_verification? # :doc: @marked_for_same_origin_verification ||= false end
def mask_token(raw_token) # :doc:
def mask_token(raw_token) # :doc: one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH) encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token) masked_token = one_time_pad + encrypted_csrf_token encode_csrf_token(masked_token) end
def masked_authenticity_token(session, form_options: {})
on each request. The masking is used to mitigate SSL attacks
Creates a masked version of the authenticity token that varies
def masked_authenticity_token(session, form_options: {}) action, method = form_options.values_at(:action, :method) raw_token = if per_form_csrf_tokens && action && method action_path = normalize_action_path(action) per_form_csrf_token(session, action_path, method) else global_csrf_token(session) end mask_token(raw_token) end
def non_xhr_javascript_response? # :doc:
Check for cross-origin JavaScript responses.
def non_xhr_javascript_response? # :doc: %r(\A(?:text|application)/javascript).match?(media_type) && !request.xhr? end
def normalize_action_path(action_path) # :doc:
def normalize_action_path(action_path) # :doc: uri = URI.parse(action_path) uri.path.chomp("/") end
def per_form_csrf_token(session, action_path, method) # :doc:
def per_form_csrf_token(session, action_path, method) # :doc: csrf_token_hmac(session, [action_path, method.downcase].join("#")) end
def protect_against_forgery? # :doc:
Checks if the controller allows forgery protection.
def protect_against_forgery? # :doc: allow_forgery_protection && (!session.respond_to?(:enabled?) || session.enabled?) end
def real_csrf_token(session) # :doc:
def real_csrf_token(session) # :doc: session[:_csrf_token] ||= generate_csrf_token decode_csrf_token(session[:_csrf_token]) end
def request_authenticity_tokens # :doc:
Possible authenticity tokens sent in the request.
def request_authenticity_tokens # :doc: [form_authenticity_param, request.x_csrf_token] end
def unmask_token(masked_token) # :doc:
def unmask_token(masked_token) # :doc: # Split the token into the one-time pad and the encrypted # value and decrypt it. one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH] encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1] xor_byte_strings(one_time_pad, encrypted_csrf_token) end
def unverified_request_warning_message # :nodoc:
def unverified_request_warning_message # :nodoc: if valid_request_origin? "Can't verify CSRF token authenticity." else "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})" end end
def valid_authenticity_token?(session, encoded_masked_token) # :doc:
+masked_authenticity_token+.
session token. Essentially the inverse of
Checks the client's masked token to see if it matches the
def valid_authenticity_token?(session, encoded_masked_token) # :doc: if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String) return false end begin masked_token = decode_csrf_token(encoded_masked_token) rescue ArgumentError # encoded_masked_token is invalid Base64 return false end # See if it's actually a masked token or not. In order to # deploy this code, we should be able to handle any unmasked # tokens that we've issued without error. if masked_token.length == AUTHENTICITY_TOKEN_LENGTH # This is actually an unmasked token. This is expected if # you have just upgraded to masked tokens, but should stop # happening shortly after installing this gem. compare_with_real_token masked_token, session elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2 csrf_token = unmask_token(masked_token) compare_with_global_token(csrf_token, session) || compare_with_real_token(csrf_token, session) || valid_per_form_csrf_token?(csrf_token, session) else false # Token is malformed. end end
def valid_per_form_csrf_token?(token, session) # :doc:
def valid_per_form_csrf_token?(token, session) # :doc: if per_form_csrf_tokens correct_token = per_form_csrf_token( session, request.path.chomp("/"), request.request_method ) ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, correct_token) else false end end
def valid_request_origin? # :doc:
Origin header.
Checks if the request originated from the same origin by looking at the
def valid_request_origin? # :doc: if forgery_protection_origin_check # We accept blank origin headers because some user agents don't send it. raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null" request.origin.nil? || request.origin == request.base_url else true end end
def verified_request? # :doc:
* Does the +X-CSRF-Token+ header match the form_authenticity_token?
* Does the form_authenticity_token match the given token value from the params?
* Is it a GET or HEAD request? GETs should be safe and idempotent
Returns true or false if a request is verified. Checks:
def verified_request? # :doc: !protect_against_forgery? || request.get? || request.head? || (valid_request_origin? && any_authenticity_token_valid?) end
def verify_authenticity_token # :doc:
follow the browser's same-origin policy.
verify that JavaScript responses are for XHR requests, ensuring they
enabled on an action, this before_action flags its after_action to
due for same-origin request verification. If protect_from_forgery is
Lean on the protect_from_forgery declaration to mark which actions are
verification.
strategy instead. If you override, you'll disable same-origin
Don't override this directly. Provide your own forgery protection
The actual before_action that is used to verify the CSRF token.
def verify_authenticity_token # :doc: mark_for_same_origin_verification! if !verified_request? logger.warn unverified_request_warning_message if logger && log_warning_on_csrf_failure handle_unverified_request end end
def verify_same_origin_request # :doc:
we aren't serving an unauthorized cross-origin response.
forgery protection enabled for this request) then also verify that
If +verify_authenticity_token+ was run (indicating that we have
def verify_same_origin_request # :doc: if marked_for_same_origin_verification? && non_xhr_javascript_response? if logger && log_warning_on_csrf_failure logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING end raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING end end
def xor_byte_strings(s1, s2) # :doc:
def xor_byte_strings(s1, s2) # :doc: s2 = s2.dup size = s1.bytesize i = 0 while i < size s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i)) i += 1 end s2 end