diff --git a/lib/omniauth-rails.rb b/lib/omniauth-rails.rb new file mode 100644 index 0000000..512e0dd --- /dev/null +++ b/lib/omniauth-rails.rb @@ -0,0 +1 @@ +require 'omniauth/rails' diff --git a/lib/omniauth-rails/version.rb b/lib/omniauth-rails/version.rb deleted file mode 100644 index d29f060..0000000 --- a/lib/omniauth-rails/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module OmniAuthRails - VERSION = "1.0.0" -end diff --git a/lib/omniauth/rails.rb b/lib/omniauth/rails.rb new file mode 100644 index 0000000..73cd1dc --- /dev/null +++ b/lib/omniauth/rails.rb @@ -0,0 +1,5 @@ +require 'omniauth' +require 'rails' + +require 'omniauth/rails/version' +require 'omniauth/rails/railtie' diff --git a/lib/omniauth/rails/railtie.rb b/lib/omniauth/rails/railtie.rb new file mode 100644 index 0000000..7cb65ef --- /dev/null +++ b/lib/omniauth/rails/railtie.rb @@ -0,0 +1,18 @@ +require 'omniauth/rails/request_forgery_protection' + +module OmniAuth + module Rails + class Railtie < ::Rails::Railtie + initializer 'logger' do + OmniAuth.config.logger = ::Rails.logger + end + + initializer 'request_forgery_protection' do + OmniAuth.config.allowed_request_methods = [:post] + OmniAuth.config.before_request_phase do |env| + RequestForgeryProtection::Current.new(env).call + end + end + end + end +end diff --git a/lib/omniauth/rails/request_forgery_protection.rb b/lib/omniauth/rails/request_forgery_protection.rb new file mode 100644 index 0000000..ce42663 --- /dev/null +++ b/lib/omniauth/rails/request_forgery_protection.rb @@ -0,0 +1,23 @@ +module OmniAuth + module Rails + module RequestForgeryProtection + if ::Rails::VERSION::MAJOR == 3 + require "omniauth/rails/request_forgery_protection/rails_3_2" + + Current = Rails32 + elsif ::Rails::VERSION::MAJOR == 4 && ::Rails::VERSION::MINOR == 0 + require "omniauth/rails/request_forgery_protection/rails_4_0" + + Current = Rails40 + elsif ::Rails::VERSION::MAJOR == 4 && ::Rails::VERSION::MINOR == 1 + require "omniauth/rails/request_forgery_protection/rails_4_1" + + Current = Rails41 + else + require "omniauth/rails/request_forgery_protection/rails_4_2" + + Current = Rails42 + end + end + end +end diff --git a/lib/omniauth/rails/request_forgery_protection/base.rb b/lib/omniauth/rails/request_forgery_protection/base.rb new file mode 100644 index 0000000..e072a64 --- /dev/null +++ b/lib/omniauth/rails/request_forgery_protection/base.rb @@ -0,0 +1,73 @@ +module OmniAuth + module Rails + module RequestForgeryProtection + class Base + def initialize(env) + @env = env + end + + def request + @_request ||= ActionDispatch::Request.new(@env) + end + + def session + request.session + end + + def reset_session + request.reset_session + end + + def params + @_params ||= request.parameters + end + + def call + verify_authenticity_token + end + + def verify_authenticity_token + return if verified_request? + + if OmniAuth.logger && log_warning_on_csrf_failure + OmniAuth.logger.warn "Can't verify CSRF token authenticity" + end + + handle_unverified_request + end + + private + + def handle_unverified_request + # Implemented by subclass. + end + + def verified_request? + !protect_against_forgery? || request.get? || request.head? || + valid_authenticity_token?(session, form_authenticity_param) || + valid_authenticity_token?(session, request.headers['X-CSRF-Token']) + end + + def valid_authenticity_token? + # Implemented by subclass. + end + + def protect_against_forgery? + ::ApplicationController.allow_forgery_protection + end + + def request_forgery_protection_token + ::ApplicationController.request_forgery_protection_token + end + + def log_warning_on_csrf_failure + true + end + + def form_authenticity_param + params[request_forgery_protection_token] + end + end + end + end +end diff --git a/lib/omniauth/rails/request_forgery_protection/rails_3_2.rb b/lib/omniauth/rails/request_forgery_protection/rails_3_2.rb new file mode 100644 index 0000000..29e7935 --- /dev/null +++ b/lib/omniauth/rails/request_forgery_protection/rails_3_2.rb @@ -0,0 +1,28 @@ +require 'omniauth/rails/request_forgery_protection/base' + +module OmniAuth + module Rails + module RequestForgeryProtection + # Based on ActionController::RequestForgeryProtection in Rails 3.2.21. + class Rails32 < Base + + private + + # This is the method that defines the application behavior when a request is found to be unverified. + # By default, \Rails resets the session when it finds an unverified request. + def handle_unverified_request + reset_session + end + + def valid_authenticity_token?(session, param) + form_authenticity_token == param + end + + # Sets the token value for the current session. + def form_authenticity_token + session[:_csrf_token] ||= SecureRandom.base64(32) + end + end + end + end +end diff --git a/lib/omniauth/rails/request_forgery_protection/rails_4_0.rb b/lib/omniauth/rails/request_forgery_protection/rails_4_0.rb new file mode 100644 index 0000000..7105c71 --- /dev/null +++ b/lib/omniauth/rails/request_forgery_protection/rails_4_0.rb @@ -0,0 +1,21 @@ +require 'omniauth/rails/request_forgery_protection/rails_3_2' + +module OmniAuth + module Rails + module RequestForgeryProtection + # Based on ActionController::RequestForgeryProtection in Rails 4.0.13. + class Rails40 < Rails32 + + private + + def forgery_protection_strategy + ::ApplicationController.forgery_protection_strategy + end + + def handle_unverified_request + forgery_protection_strategy.new(self).handle_unverified_request + end + end + end + end +end diff --git a/lib/omniauth/rails/request_forgery_protection/rails_4_1.rb b/lib/omniauth/rails/request_forgery_protection/rails_4_1.rb new file mode 100644 index 0000000..59f51e0 --- /dev/null +++ b/lib/omniauth/rails/request_forgery_protection/rails_4_1.rb @@ -0,0 +1,12 @@ +require 'omniauth/rails/request_forgery_protection/rails_4_0' + +module OmniAuth + module Rails + module RequestForgeryProtection + # Based on ActionController::RequestForgeryProtection in Rails 4.1.10. + class Rails41 < Rails40 + + end + end + end +end diff --git a/lib/omniauth/rails/request_forgery_protection/rails_4_2.rb b/lib/omniauth/rails/request_forgery_protection/rails_4_2.rb new file mode 100644 index 0000000..2c8c46c --- /dev/null +++ b/lib/omniauth/rails/request_forgery_protection/rails_4_2.rb @@ -0,0 +1,88 @@ +require 'omniauth/rails/request_forgery_protection/rails_4_1' + +require 'action_controller' +require 'active_support/security_utils' + +module OmniAuth + module Rails + module RequestForgeryProtection + # Based on ActionController::RequestForgeryProtection in Rails 4.2.1. + class Rails42 < Rails41 + + private + + def log_warning_on_csrf_failure + ::ApplicationController.log_warning_on_csrf_failure + end + + AUTHENTICITY_TOKEN_LENGTH = ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH + + # Sets the token value for the current session. + def form_authenticity_token + masked_authenticity_token(session) + end + + # Creates a masked version of the authenticity token that varies + # on each request. The masking is used to mitigate SSL attacks + # like BREACH. + 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 + + # Checks the client's masked token to see if it matches the + # session token. Essentially the inverse of + # +masked_authenticity_token+. + 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 + + def compare_with_real_token(token, session) + ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session)) + end + + def real_csrf_token(session) + session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH) + Base64.strict_decode64(session[:_csrf_token]) + end + + def xor_byte_strings(s1, s2) + s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*') + end + end + end + end +end diff --git a/lib/omniauth/rails/version.rb b/lib/omniauth/rails/version.rb new file mode 100644 index 0000000..5eb0caa --- /dev/null +++ b/lib/omniauth/rails/version.rb @@ -0,0 +1,5 @@ +module OmniAuth + module Rails + VERSION = '0.0.1' + end +end diff --git a/omniauth-rails.gemspec b/omniauth-rails.gemspec index eb43f80..9961719 100644 --- a/omniauth-rails.gemspec +++ b/omniauth-rails.gemspec @@ -1,11 +1,11 @@ # coding: utf-8 lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require "omniauth-rails/version" +require "omniauth/rails/version" Gem::Specification.new do |spec| spec.name = "omniauth-rails" - spec.version = OmniAuthRails::VERSION + spec.version = OmniAuth::Rails::VERSION spec.authors = ["Erik Michaels-Ober", "Douwe Maan"] spec.email = ["sferik@gmail.com", "douwe@gitlab.com"] @@ -17,7 +17,7 @@ Gem::Specification.new do |spec| spec.files = `git ls-files -z`.split("\x0") spec.require_paths = ["lib"] - spec.add_dependency "omniauth" - spec.add_dependency "rails" + spec.add_dependency "omniauth", "~> 1.2.2" + spec.add_dependency "rails", ">= 3.2, < 5" spec.add_development_dependency "bundler", "~> 1.9" end