62 changes: 47 additions & 15 deletions lib/omniauth/strategy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,18 +180,44 @@ def call!(env) # rubocop:disable CyclomaticComplexity, PerceivedComplexity
raise(error)
end

warn_if_using_get

@env = env
@env['omniauth.strategy'] = self if on_auth_path?

return mock_call!(env) if OmniAuth.config.test_mode
return options_call if on_auth_path? && options_request?
return request_call if on_request_path? && OmniAuth.config.allowed_request_methods.include?(request.request_method.downcase.to_sym)
return callback_call if on_callback_path?
return other_phase if respond_to?(:other_phase)

begin
return options_call if on_auth_path? && options_request?
return request_call if on_request_path? && OmniAuth.config.allowed_request_methods.include?(request.request_method.downcase.to_sym)
return callback_call if on_callback_path?
return other_phase if respond_to?(:other_phase)
rescue StandardError => e
return fail!(e.message, e)
end

@app.call(env)
end

def warn_if_using_get
return unless OmniAuth.config.allowed_request_methods.include?(:get)
return if OmniAuth.config.silence_get_warning

log :warn, <<-WARN
You are using GET as an allowed request method for OmniAuth. This may leave
you open to CSRF attacks. As of v2.0.0, OmniAuth by default allows only POST
to its own routes. You should review the following resources to guide your
mitigation:
https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284
https://github.com/omniauth/omniauth/issues/960
https://nvd.nist.gov/vuln/detail/CVE-2015-9284
https://github.com/omniauth/omniauth/pull/809
You can ignore this warning by setting:
OmniAuth.config.silence_get_warning = true
WARN
end

# Responds to an OPTIONS request.
def options_call
OmniAuth.config.before_options_phase.call(env) if OmniAuth.config.before_options_phase
Expand All @@ -202,17 +228,19 @@ def options_call
# Performs the steps necessary to run the request phase of a strategy.
def request_call # rubocop:disable CyclomaticComplexity, MethodLength, PerceivedComplexity
setup_phase
log :info, 'Request phase initiated.'
log :debug, 'Request phase initiated.'

# store query params from the request url, extracted in the callback_phase
session['omniauth.params'] = request.GET

OmniAuth.config.request_validation_phase.call(env) if OmniAuth.config.request_validation_phase
OmniAuth.config.before_request_phase.call(env) if OmniAuth.config.before_request_phase

if options.form.respond_to?(:call)
log :info, 'Rendering form from supplied Rack endpoint.'
log :debug, 'Rendering form from supplied Rack endpoint.'
options.form.call(env)
elsif options.form
log :info, 'Rendering form from underlying application.'
log :debug, 'Rendering form from underlying application.'
call_app!
elsif !options.origin_param
request_phase
Expand All @@ -225,12 +253,14 @@ def request_call # rubocop:disable CyclomaticComplexity, MethodLength, Perceived

request_phase
end
rescue OmniAuth::AuthenticityError => e
fail!(:authenticity_error, e)
end

# Performs the steps necessary to run the callback phase of a strategy.
def callback_call
setup_phase
log :info, 'Callback phase initiated.'
log :debug, 'Callback phase initiated.'
@env['omniauth.origin'] = session.delete('omniauth.origin')
@env['omniauth.origin'] = nil if env['omniauth.origin'] == ''
@env['omniauth.params'] = session.delete('omniauth.params') || {}
Expand Down Expand Up @@ -312,10 +342,10 @@ def mock_callback_call
# underlying application. This will default to `/auth/:provider/setup`.
def setup_phase
if options[:setup].respond_to?(:call)
log :info, 'Setup endpoint detected, running now.'
log :debug, 'Setup endpoint detected, running now.'
options[:setup].call(env)
elsif options[:setup]
log :info, 'Calling through to underlying application for setup.'
log :debug, 'Calling through to underlying application for setup.'
setup_env = env.merge('PATH_INFO' => setup_path, 'REQUEST_METHOD' => 'GET')
call_app!(setup_env)
end
Expand Down Expand Up @@ -345,11 +375,13 @@ def extra
end

def auth_hash
hash = AuthHash.new(:provider => name, :uid => uid)
hash.info = info unless skip_info?
hash.credentials = credentials if credentials
hash.extra = extra if extra
hash
credentials_data = credentials
extra_data = extra
AuthHash.new(:provider => name, :uid => uid).tap do |auth|
auth.info = info unless skip_info?
auth.credentials = credentials_data if credentials_data
auth.extra = extra_data if extra_data
end
end

# Determines whether or not user info should be retrieved. This
Expand Down
2 changes: 1 addition & 1 deletion lib/omniauth/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module OmniAuth
VERSION = '1.9.0'.freeze
VERSION = '1.9.1'.freeze
end
3 changes: 2 additions & 1 deletion omniauth.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ require 'omniauth/version'
Gem::Specification.new do |spec|
spec.add_dependency 'hashie', ['>= 3.4.6']
spec.add_dependency 'rack', ['>= 1.6.2', '< 3']
spec.add_development_dependency 'bundler', '~> 1.14'
spec.add_development_dependency 'bundler', '~> 2.0'
spec.add_dependency 'rack-protection'
spec.add_development_dependency 'rake', '~> 12.0'
spec.authors = ['Michael Bleigh', 'Erik Michaels-Ober', 'Tom Milewski']
spec.description = 'A generalized Rack framework for multiple-provider authentication.'
Expand Down
11 changes: 8 additions & 3 deletions spec/helper.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
if RUBY_VERSION >= '1.9'
require 'simplecov'
require 'coveralls'
require 'simplecov-lcov'

SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true

SimpleCov.formatters = [
SimpleCov::Formatter::HTMLFormatter,
SimpleCov::Formatter::LcovFormatter,
Coveralls::SimpleCov::Formatter
]

SimpleCov.start do
add_filter ['/spec/', '/vendor/', 'strategy_macros.rb']
minimum_coverage(92.5)
maximum_coverage_drop(0.01)
maximum_coverage_drop(0.05)
end
end

Expand All @@ -20,6 +24,7 @@
require 'omniauth/test'

OmniAuth.config.logger = Logger.new('/dev/null')
OmniAuth.config.request_validation_phase = nil

RSpec.configure do |config|
config.include Rack::Test::Methods
Expand All @@ -45,7 +50,7 @@ def initialize(*args, &block)

def request_phase
options[:mutate_on_request].call(options) if options[:mutate_on_request]
@fail = fail!(options[:failure]) if options[:failure]
@fail = fail!(options[:failure], options[:failure_exception]) if options[:failure]
@last_env = env
return @fail if @fail

Expand All @@ -54,7 +59,7 @@ def request_phase

def callback_phase
options[:mutate_on_callback].call(options) if options[:mutate_on_callback]
@fail = fail!(options[:failure]) if options[:failure]
@fail = fail!(options[:failure], options[:failure_exception]) if options[:failure]
@last_env = env
return @fail if @fail

Expand Down
8 changes: 8 additions & 0 deletions spec/omniauth/auth_hash_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
expect(subject.weird_field.info).to eq 'string'
end

it 'has a subkey_class' do
expect(OmniAuth::AuthHash.subkey_class).to eq Hashie::Mash
end

describe '#valid?' do
subject { OmniAuth::AuthHash.new(:uid => '123', :provider => 'example', :info => {:name => 'Steven'}) }

Expand Down Expand Up @@ -111,6 +115,10 @@
end
end

it 'has a subkey_class' do
expect(OmniAuth::AuthHash::InfoHash.subkey_class).to eq Hashie::Mash
end

require 'hashie/version'
if Gem::Version.new(Hashie::VERSION) >= Gem::Version.new('3.5.1')
context 'with Hashie 3.5.1+' do
Expand Down
12 changes: 11 additions & 1 deletion spec/omniauth/builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
describe OmniAuth::Builder do
describe '#provider' do
it 'translates a symbol to a constant' do
expect(OmniAuth::Strategies).to receive(:const_get).with('MyStrategy').and_return(Class.new)
expect(OmniAuth::Strategies).to receive(:const_get).with('MyStrategy', false).and_return(Class.new)
OmniAuth::Builder.new(nil) do
provider :my_strategy
end
Expand All @@ -26,6 +26,16 @@ class ExampleClass; end
end
end.to raise_error(LoadError, 'Could not find matching strategy for :lorax. You may need to install an additional gem (such as omniauth-lorax).')
end

it "doesn't translate a symbol to a top-level constant" do
class MyStrategy; end

expect do
OmniAuth::Builder.new(nil) do
provider :my_strategy
end
end.to raise_error(LoadError, 'Could not find matching strategy for :my_strategy. You may need to install an additional gem (such as omniauth-my_strategy).')
end
end

describe '#options' do
Expand Down
5 changes: 5 additions & 0 deletions spec/omniauth/failure_endpoint_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,10 @@
_, head, = *subject.call(env)
expect(head['Location']).to be_include('&origin=%2Forigin-example')
end

it 'escapes the message key' do
_, head = *subject.call(env.merge('omniauth.error.type' => 'Connection refused!'))
expect(head['Location']).to be_include('message=Connection+refused%21')
end
end
end
33 changes: 32 additions & 1 deletion spec/omniauth/form_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
end

it 'evaluates in the instance when called with a block and no argument' do
OmniAuth::Form.build { |f| expect(f.class).to eq(OmniAuth::Form) }
f = OmniAuth::Form.build { @html = '<h1>OmniAuth</h1>' }
expect(f.instance_variable_get(:@html)).to eq('<h1>OmniAuth</h1>')
end
end

Expand All @@ -20,4 +21,34 @@
expect(OmniAuth::Form.new(:title => 'Something Cool').to_html).to be_include('<h1>Something Cool</h1>')
end
end

describe '#password_field' do
it 'adds a labeled input field' do
form = OmniAuth::Form.new.password_field('pass', 'password')
form_html = form.to_html
expect(form_html).to include('<label for=\'password\'>pass:</label>')
expect(form_html).to include('<input type=\'password\' id=\'password\' name=\'password\'/>')
end
end

describe '#html' do
it 'appends to the html body' do
form = OmniAuth::Form.build { @html = '<p></p>' }
form.html('<h1></h1>')

expect(form.instance_variable_get(:@html)).to eq '<p></p><h1></h1>'
end
end

describe 'fieldset' do
it 'creates a fieldset with options' do
form = OmniAuth::Form.new
options = {:style => 'color: red', :id => 'fieldSetId'}
expected = "<fieldset style='color: red' id='fieldSetId'>\n <legend>legendary</legend>\n\n</fieldset>"

form.fieldset('legendary', options) {}

expect(form.to_html).to include expected
end
end
end
1 change: 1 addition & 0 deletions spec/omniauth/key_store_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

it 'does not log anything to the console' do
stub_const('Hashie::VERSION', version)
allow(OmniAuth::KeyStore).to receive(:respond_to?).with(:disable_warnings).and_return(false)
OmniAuth::KeyStore.override_logging
expect(logger).not_to receive(:info)
OmniAuth::KeyStore.new(:id => 1234)
Expand Down
2 changes: 1 addition & 1 deletion spec/omniauth/strategies/developer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
end

context 'request phase' do
before(:each) { get '/auth/developer' }
before(:each) { post '/auth/developer' }

it 'displays a form' do
expect(last_response.status).to eq(200)
Expand Down
211 changes: 171 additions & 40 deletions spec/omniauth/strategy_spec.rb

Large diffs are not rendered by default.

47 changes: 37 additions & 10 deletions spec/omniauth_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,22 @@ class ExampleStrategy
end

before do
@old_path_prefix = OmniAuth.config.path_prefix
@old_on_failure = OmniAuth.config.on_failure
@old_before_callback_phase = OmniAuth.config.before_callback_phase
@old_before_options_phase = OmniAuth.config.before_options_phase
@old_before_request_phase = OmniAuth.config.before_request_phase
@old_path_prefix = OmniAuth.config.path_prefix
@old_on_failure = OmniAuth.config.on_failure
@old_before_callback_phase = OmniAuth.config.before_callback_phase
@old_before_options_phase = OmniAuth.config.before_options_phase
@old_before_request_phase = OmniAuth.config.before_request_phase
@old_request_validation_phase = OmniAuth.config.request_validation_phase
end

after do
OmniAuth.configure do |config|
config.path_prefix = @old_path_prefix
config.on_failure = @old_on_failure
config.before_callback_phase = @old_before_callback_phase
config.before_options_phase = @old_before_options_phase
config.before_request_phase = @old_before_request_phase
config.path_prefix = @old_path_prefix
config.on_failure = @old_on_failure
config.before_callback_phase = @old_before_callback_phase
config.before_options_phase = @old_before_options_phase
config.before_request_phase = @old_before_request_phase
config.request_validation_phase = @old_request_validation_phase
end
end

Expand Down Expand Up @@ -88,6 +90,15 @@ class ExampleStrategy
expect(OmniAuth.config.before_callback_phase.call).to eq('heyhey')
end

it 'is able to set request_validation_phase' do
OmniAuth.configure do |config|
config.request_validation_phase do
'validated'
end
end
expect(OmniAuth.config.request_validation_phase.call).to eq('validated')
end

describe 'mock auth' do
before do
@auth_hash = {:uid => '12345', :info => {:name => 'Joe', :email => 'joe@example.com'}}
Expand Down Expand Up @@ -128,6 +139,13 @@ class ExampleStrategy
end

describe '::Utils' do
describe 'form_css' do
it 'returns a style tag with the configured form_css' do
allow(OmniAuth).to receive(:config).and_return(double(:form_css => 'css.css'))
expect(OmniAuth::Utils.form_css).to eq "<style type='text/css'>css.css</style>"
end
end

describe '.deep_merge' do
it 'combines hashes' do
expect(OmniAuth::Utils.deep_merge({'abc' => {'def' => 123}}, 'abc' => {'foo' => 'bar'})).to eq('abc' => {'def' => 123, 'foo' => 'bar'})
Expand All @@ -148,6 +166,15 @@ class ExampleStrategy
OmniAuth.config.add_camelization('oauth', 'OAuth')
expect(OmniAuth::Utils.camelize(:oauth)).to eq('OAuth')
end

it 'doesn\'t uppercase the first letter when passed false' do
expect(OmniAuth::Utils.camelize('apple_jack', false)).to eq('appleJack')
end

it 'replaces / with ::' do
expect(OmniAuth::Utils.camelize('apple_jack/cereal')).to eq('AppleJack::Cereal')
expect(OmniAuth::Utils.camelize('apple_jack/cereal', false)).to eq('appleJack::Cereal')
end
end
end
end