Skip to content

Commit

Permalink
Rescues from strategy calls by calling fail!, properly URL encodes me…
Browse files Browse the repository at this point in the history
…ssage_key for FailureEndpoint handling

Do not rescue around mock_call!
  • Loading branch information
joshjordan committed Dec 7, 2020
1 parent 2e6140a commit ee2ef21
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 36 deletions.
2 changes: 1 addition & 1 deletion lib/omniauth/failure_endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def raise_out!

def redirect_to_failure
message_key = env['omniauth.error.type']
new_path = "#{env['SCRIPT_NAME']}#{OmniAuth.config.path_prefix}/failure?message=#{message_key}#{origin_query_param}#{strategy_name_query_param}"
new_path = "#{env['SCRIPT_NAME']}#{OmniAuth.config.path_prefix}/failure?message=#{Rack::Utils.escape(message_key)}#{origin_query_param}#{strategy_name_query_param}"
Rack::Response.new(['302 Moved'], 302, 'Location' => new_path).finish
end

Expand Down
13 changes: 9 additions & 4 deletions lib/omniauth/strategy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,15 @@ def call!(env) # rubocop:disable CyclomaticComplexity, PerceivedComplexity
@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 => ex
return fail! ex.message, ex
end

@app.call(env)
end
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 @@ -54,5 +54,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
112 changes: 81 additions & 31 deletions spec/omniauth/strategy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -312,32 +312,41 @@ def make_env(path = '/auth/test', props = {})
context 'disabled' do
it 'does not set omniauth.origin' do
@options = {:origin_param => false}
expect { strategy.call(make_env('/auth/test', 'QUERY_STRING' => 'return=/foo')) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))

strategy.call(make_env('/auth/test', 'QUERY_STRING' => 'return=/foo'))
expect(strategy.last_env['rack.session']['omniauth.origin']).to eq(nil)
end
end

context 'custom' do
it 'sets from a custom param' do
@options = {:origin_param => 'return'}
expect { strategy.call(make_env('/auth/test', 'QUERY_STRING' => 'return=/foo')) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))

strategy.call(make_env('/auth/test', 'QUERY_STRING' => 'return=/foo'))
expect(strategy.last_env['rack.session']['omniauth.origin']).to eq('/foo')
end
end

context 'default flow' do
it 'is set on the request phase' do
expect { strategy.call(make_env('/auth/test', 'HTTP_REFERER' => 'http://example.com/origin')) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with("Request Phase", kind_of(StandardError))
strategy.call(make_env('/auth/test', 'HTTP_REFERER' => 'http://example.com/origin'))

expect(strategy.last_env['rack.session']['omniauth.origin']).to eq('http://example.com/origin')
end

it 'is turned into an env variable on the callback phase' do
expect { strategy.call(make_env('/auth/test/callback', 'rack.session' => {'omniauth.origin' => 'http://example.com/origin'})) }.to raise_error('Callback Phase')
strategy.should_receive(:fail!).with("Callback Phase", kind_of(StandardError))
strategy.call(make_env('/auth/test/callback', 'rack.session' => {'omniauth.origin' => 'http://example.com/origin'}))

expect(strategy.last_env['omniauth.origin']).to eq('http://example.com/origin')
end

it 'sets from the params if provided' do
expect { strategy.call(make_env('/auth/test', 'QUERY_STRING' => 'origin=/foo')) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))
strategy.call(make_env('/auth/test', 'QUERY_STRING' => 'origin=/foo'))
expect(strategy.last_env['rack.session']['omniauth.origin']).to eq('/foo')
end

Expand All @@ -350,7 +359,9 @@ def make_env(path = '/auth/test', props = {})
context 'with script_name' do
it 'is set on the request phase, containing full path' do
env = {'HTTP_REFERER' => 'http://example.com/sub_uri/origin', 'SCRIPT_NAME' => '/sub_uri'}
expect { strategy.call(make_env('/auth/test', env)) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))

strategy.call(make_env('/auth/test', env))
expect(strategy.last_env['rack.session']['omniauth.origin']).to eq('http://example.com/sub_uri/origin')
end

Expand All @@ -359,8 +370,9 @@ def make_env(path = '/auth/test', props = {})
'rack.session' => {'omniauth.origin' => 'http://example.com/sub_uri/origin'},
'SCRIPT_NAME' => '/sub_uri'
}
strategy.should_receive(:fail!).with('Callback Phase', kind_of(StandardError))

expect { strategy.call(make_env('/auth/test/callback', env)) }.to raise_error('Callback Phase')
strategy.call(make_env('/auth/test/callback', env))
expect(strategy.last_env['omniauth.origin']).to eq('http://example.com/sub_uri/origin')
end
end
Expand All @@ -369,34 +381,41 @@ def make_env(path = '/auth/test', props = {})

context 'default paths' do
it 'uses the default request path' do
expect { strategy.call(make_env) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))
strategy.call(make_env)
end

it 'is case insensitive on request path' do
expect { strategy.call(make_env('/AUTH/Test')) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))
strategy.call(make_env('/AUTH/Test'))
end

it 'is case insensitive on callback path' do
expect { strategy.call(make_env('/AUTH/TeSt/CaLlBAck')) }.to raise_error('Callback Phase')
strategy.should_receive(:fail!).with('Callback Phase', kind_of(StandardError))
strategy.call(make_env('/AUTH/TeSt/CaLlBAck'))
end

it 'uses the default callback path' do
expect { strategy.call(make_env('/auth/test/callback')) }.to raise_error('Callback Phase')
strategy.should_receive(:fail!).with('Callback Phase', kind_of(StandardError))
strategy.call(make_env('/auth/test/callback'))
end

it 'strips trailing spaces on request' do
expect { strategy.call(make_env('/auth/test/')) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))
strategy.call(make_env('/auth/test/'))
end

it 'strips trailing spaces on callback' do
expect { strategy.call(make_env('/auth/test/callback/')) }.to raise_error('Callback Phase')
strategy.should_receive(:fail!).with('Callback Phase', kind_of(StandardError))
strategy.call(make_env('/auth/test/callback/'))
end

context 'callback_url' do
it 'uses the default callback_path' do
expect(strategy).to receive(:full_host).and_return('http://example.com')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))

expect { strategy.call(make_env) }.to raise_error('Request Phase')
strategy.call(make_env)

expect(strategy.callback_url).to eq('http://example.com/auth/test/callback')
end
Expand Down Expand Up @@ -436,12 +455,15 @@ def make_env(path = '/auth/test', props = {})
context 'dynamic paths' do
it 'runs the request phase if the custom request path evaluator is truthy' do
@options = {:request_path => lambda { |_env| true }}
expect { strategy.call(make_env('/asoufibasfi')) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))
strategy.call(make_env('/asoufibasfi'))
end

it 'runs the callback phase if the custom callback path evaluator is truthy' do
@options = {:callback_path => lambda { |_env| true }}
expect { strategy.call(make_env('/asoufiasod')) }.to raise_error('Callback Phase')
strategy.should_receive(:fail!).with('Callback Phase', kind_of(StandardError))

strategy.call(make_env('/asoufiasod'))
end

it 'provides a custom callback path if request_path evals to a string' do
Expand All @@ -451,29 +473,35 @@ def make_env(path = '/auth/test', props = {})

it 'correctly reports the callback path when the custom callback path evaluator is truthy' do
strategy_instance = ExampleStrategy.new(app, :callback_path => lambda { |env| env['PATH_INFO'] == '/auth/bish/bosh/callback' })
strategy_instance.should_receive(:fail!).with('Callback Phase', kind_of(StandardError))

expect { strategy_instance.call(make_env('/auth/bish/bosh/callback')) }.to raise_error('Callback Phase')
strategy_instance.call(make_env('/auth/bish/bosh/callback'))
expect(strategy_instance.callback_path).to eq('/auth/bish/bosh/callback')
end
end

context 'custom paths' do
it 'uses a custom request_path if one is provided' do
@options = {:request_path => '/awesome'}
expect { strategy.call(make_env('/awesome')) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))

strategy.call(make_env('/awesome'))
end

it 'uses a custom callback_path if one is provided' do
@options = {:callback_path => '/radical'}
expect { strategy.call(make_env('/radical')) }.to raise_error('Callback Phase')
strategy.should_receive(:fail!).with('Callback Phase', kind_of(StandardError))

strategy.call(make_env('/radical'))
end

context 'callback_url' do
it 'uses a custom callback_path if one is provided' do
@options = {:callback_path => '/radical'}
expect(strategy).to receive(:full_host).and_return('http://example.com')
strategy.should_receive(:fail!).with('Callback Phase', kind_of(StandardError))

expect { strategy.call(make_env('/radical')) }.to raise_error('Callback Phase')
strategy.call(make_env('/radical'))

expect(strategy.callback_url).to eq('http://example.com/radical')
end
Expand All @@ -496,18 +524,20 @@ def make_env(path = '/auth/test', props = {})
end

it 'uses a custom prefix for request' do
expect { strategy.call(make_env('/wowzers/test')) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))
strategy.call(make_env('/wowzers/test'))
end

it 'uses a custom prefix for callback' do
expect { strategy.call(make_env('/wowzers/test/callback')) }.to raise_error('Callback Phase')
strategy.should_receive(:fail!).with('Callback Phase', kind_of(StandardError))
strategy.call(make_env('/wowzers/test/callback'))
end

context 'callback_url' do
it 'uses a custom prefix' do
expect(strategy).to receive(:full_host).and_return('http://example.com')

expect { strategy.call(make_env('/wowzers/test')) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))
strategy.call(make_env('/wowzers/test'))

expect(strategy.callback_url).to eq('http://example.com/wowzers/test/callback')
end
Expand All @@ -533,7 +563,8 @@ def make_env(path = '/auth/test', props = {})
end

it 'allows a request method of the correct type' do
expect { strategy.call(make_env('/auth/test')) }.to raise_error('Request Phase')
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))
strategy.call(make_env('/auth/test'))
end

after(:context) do
Expand Down Expand Up @@ -579,14 +610,16 @@ def make_env(path = '/auth/test', props = {})
it 'does not affect original options' do
@options[:test_option] = true
@options[:mutate_on_request] = proc { |options| options.delete(:test_option) }
expect { strategy.call(make_env) }.to raise_error('Request Phase')

strategy.call(make_env)
expect(strategy.options).to have_key(:test_option)
end

it 'does not affect deep options' do
@options[:deep_option] = {:test_option => true}
@options[:mutate_on_request] = proc { |options| options[:deep_option].delete(:test_option) }
expect { strategy.call(make_env) }.to raise_error('Request Phase')

strategy.call(make_env)
expect(strategy.options[:deep_option]).to have_key(:test_option)
end
end
Expand All @@ -595,14 +628,16 @@ def make_env(path = '/auth/test', props = {})
it 'does not affect original options' do
@options[:test_option] = true
@options[:mutate_on_callback] = proc { |options| options.delete(:test_option) }
expect { strategy.call(make_env('/auth/test/callback', 'REQUEST_METHOD' => 'POST')) }.to raise_error('Callback Phase')

strategy.call(make_env('/auth/test/callback', 'REQUEST_METHOD' => 'POST'))
expect(strategy.options).to have_key(:test_option)
end

it 'does not affect deep options' do
@options[:deep_option] = {:test_option => true}
@options[:mutate_on_callback] = proc { |options| options[:deep_option].delete(:test_option) }
expect { strategy.call(make_env('/auth/test/callback', 'REQUEST_METHOD' => 'POST')) }.to raise_error('Callback Phase')

strategy.call(make_env('/auth/test/callback', 'REQUEST_METHOD' => 'POST'))
expect(strategy.options[:deep_option]).to have_key(:test_option)
end
end
Expand Down Expand Up @@ -822,8 +857,10 @@ def make_env(path = '/auth/test', props = {})
let(:escaped_token) { URI.encode_www_form_component(csrf_token, Encoding::UTF_8) }

it 'allows a request with matching authenticity_token' do
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))

post_env = make_env('/auth/test', 'rack.session' => {:csrf => csrf_token}, 'rack.input' => StringIO.new("authenticity_token=#{escaped_token}"))
expect { strategy.call(post_env) }.to raise_error('Request Phase')
strategy.call(post_env)
end

it 'does not allow a request without a matching authenticity token' do
Expand All @@ -840,8 +877,10 @@ def make_env(path = '/auth/test', props = {})
end

it 'allows a request without authenticity token' do
strategy.should_receive(:fail!).with('Request Phase', kind_of(StandardError))

get_env = make_env('/auth/test', 'REQUEST_METHOD' => 'GET')
expect { strategy.call(get_env) }.to raise_error('Request Phase')
strategy.call(get_env)
end

after(:context) do
Expand All @@ -853,6 +892,17 @@ def make_env(path = '/auth/test', props = {})
OmniAuth.config.request_validation_phase = nil
end
end

it 'calls fail! when encountering an unhandled exception' do
strategy.stub(:request_phase).and_raise(Errno::ECONNREFUSED)
strategy.should_receive(:fail!).with('Connection refused', kind_of(Errno::ECONNREFUSED))
strategy.call(make_env)
end

it 'redirects to the fail! result when encountering an unhandled exception' do
OmniAuth.config.test_mode = false
expect(strategy.call(make_env).first).to eq 302
end
end

context 'setup phase' do
Expand Down

0 comments on commit ee2ef21

Please sign in to comment.