module Roda::RodaPlugins::Rails42::InstanceMethods

Rails 4.2 integration code, most code from Rails

Public Instance Methods

call() click to toggle source
Calls superclass method
# File lib/roda/plugins/rails42.rb, line 23
def call
  catch(:halt) do
    rails = self.class.opts[:rails]
    r = request
    if instance_exec(r, &rails[:check_csrf])
      unless valid_authenticity_token?(session, instance_exec(r, &rails[:csrf_token]))
        instance_exec(r, &rails[:invalid_csrf])
      end
    end

    super
  end
end
csrf_tag() click to toggle source
# File lib/roda/plugins/rails42.rb, line 41
def csrf_tag
  "<input type='hidden' name='authenticity_token' value=\"#{masked_authenticity_token(session)}\" />".html_safe
end
flash() click to toggle source
# File lib/roda/plugins/rails42.rb, line 37
def flash
  env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"])
end

Private Instance Methods

compare_with_real_token(token, session) click to toggle source
# File lib/roda/plugins/rails42.rb, line 95
def compare_with_real_token(token, session)
  ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
end
masked_authenticity_token(session) click to toggle source

Creates a masked version of the authenticity token that varies on each request. The masking is used to mitigate SSL attacks like BREACH.

# File lib/roda/plugins/rails42.rb, line 50
def masked_authenticity_token(session)
  one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
  encrypted_csrf_token = xor_byte_strings(one_time_pad, real_csrf_token(session))
  masked_token = one_time_pad + encrypted_csrf_token
  Base64.strict_encode64(masked_token)
end
real_csrf_token(session) click to toggle source
# File lib/roda/plugins/rails42.rb, line 99
def real_csrf_token(session)
  session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
  Base64.strict_decode64(session[:_csrf_token])
end
valid_authenticity_token?(session, encoded_masked_token) click to toggle source

Checks the client’s masked token to see if it matches the session token. Essentially the inverse of masked_authenticity_token.

# File lib/roda/plugins/rails42.rb, line 60
def valid_authenticity_token?(session, encoded_masked_token)
  if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
    return false
  end

  begin
    masked_token = Base64.strict_decode64(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
    # 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]
    csrf_token = xor_byte_strings(one_time_pad, encrypted_csrf_token)

    compare_with_real_token csrf_token, session

  else
    false # Token is malformed
  end
end
xor_byte_strings(s1, s2) click to toggle source
# File lib/roda/plugins/rails42.rb, line 104
def xor_byte_strings(s1, s2)
  s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
end