Do not reproduce warden behavior, simply rely on the controller calling it

This commit is contained in:
José Valim 2012-05-06 12:09:53 +02:00
parent a9b7a4a1de
commit 5e845ee265
3 changed files with 57 additions and 92 deletions

View File

@ -74,50 +74,48 @@ module Devise
result ||= {}
# set the response. In production, the rack result is returned from Warden::Manager#call, which
# the following is modelled on.
# Set the response. In production, the rack result is returned
# from Warden::Manager#call, which the following is modelled on.
case result
when Array
if result.first == 401 && intercept_401?(env) # does this happen during testing?
process_unauthenticated(env)
else
result # result is already the array rack response
end
when Hash
process_unauthenticated(env, result)
when Array
if result.first == 401 && intercept_401?(env) # does this happen during testing?
_process_unauthenticated(env)
else
# any other type, eg: ActionController::TestResponse pass through unchanged.
result
end
when Hash
_process_unauthenticated(env, result)
else
result
end
end
def process_unauthenticated(env, options = {})
def _process_unauthenticated(env, options = {})
options[:action] ||= :unauthenticated
proxy = env['warden']
result = options[:result] || proxy.result
# set the controller response as well as return our rack response array
ret = case result
when :redirect
body = proxy.message || "You are being redirected to #{proxy.headers['Location']}"
[proxy.status, proxy.headers, [body]]
when :custom
proxy.custom_response
else
env["PATH_INFO"] = "/#{options[:action]}"
env["warden.options"] = options
Warden::Manager._run_callbacks(:before_failure, env, options)
when :redirect
body = proxy.message || "You are being redirected to #{proxy.headers['Location']}"
[proxy.status, proxy.headers, [body]]
when :custom
proxy.custom_response
else
env["PATH_INFO"] = "/#{options[:action]}"
env["warden.options"] = options
Warden::Manager._run_callbacks(:before_failure, env, options)
status, headers, body = Devise.warden_config[:failure_app].call(env).to_a
@controller.send :render, :status => status, :text => body,
:content_type => headers["Content-Type"], :location => headers["Location"]
nil # causes process return @response
end
status, headers, body = Devise.warden_config[:failure_app].call(env).to_a
@controller.send :render, :status => status, :text => body,
:content_type => headers["Content-Type"], :location => headers["Location"]
nil # causes process return @response
end
# ensure that the controller response is set up. In production, this is not necessary since warden returns
# the results to rack. However, at testing time, we want the response to be available to the testing framework
# to verify what would be returned to rack.
# ensure that the controller response is set up. In production, this is
# not necessary since warden returns the results to rack. However, at
# testing time, we want the response to be available to the testing
# framework to verify what would be returned to rack.
if ret.is_a?(Array)
# ensure the controller response is set to our response.
@controller.response ||= @response
@ -127,7 +125,6 @@ module Devise
end
ret
end
end
end

View File

@ -3,74 +3,42 @@ require 'ostruct'
require 'warden/strategies/base'
require 'devise/test_helpers'
class MyController < DeviseController
class CustomStrategyController < ActionController::Base
def new
warden.authenticate!(:custom_strategy)
end
end
# These tests are to prove that a warden strategy can successfully
# return a custom response, including a specific status code and
# custom http response headers. This does work in production,
# however, at the time of writing this, the Devise test helpers do
# not recognise the custom response and proceed to calling the
# Failure App. This makes it impossible to write tests for a
# strategy that return a custom response with Devise.
class CustomStrategy < Warden::Strategies::Base
def authenticate!
custom_headers = { "X-FOO" => "BAR" }
response = Rack::Response.new("BAD REQUEST", 400, custom_headers)
custom! response.finish
end
end
class CustomStrategyTest < ActionController::TestCase
tests MyController
tests CustomStrategyController
include Devise::TestHelpers
# These tests are to prove that a warden strategy can successfully return a custom response, including a specific
# status code and custom http response headers. This does work in production, however, at the time of writing this,
# the Devise test helpers do not recognise the custom response and proceed to calling the Failure App. This makes
# it impossible to write tests for a strategy that return a custom response with Devise.
#
# The code this test needs to verify is in Devise::TestHelpers#_catch_warden (which appears to have no other test
# coverage at this point.) The functionality of this function should mirror Warden::Manager#call(env) and
# Warden::Manager#process_unauthenticated which correctly detects the custom response when set by a strategy.
#
class CustomStrategy < Warden::Strategies::Base
def authenticate!
custom_headers = { "X-FOO" => "BAR" }
response = Rack::Response.new("BAD REQUEST", 400, custom_headers)
custom! response.finish
end
setup do
Warden::Strategies.add(:custom_strategy, CustomStrategy)
end
# call the custom strategy, returning the rack result array
def call_custom(env_params={})
request.env ||= {}
request.env.merge! ({
'REQUEST_URI' => 'http://test.host/',
'HTTP_HOST' => 'test.host',
'REQUEST_METHOD' => 'GET',
'warden.options' => { :scope => :user },
'rack.session' => {},
'action_dispatch.request.formats' => Array(env_params.delete('formats') || Mime::HTML),
'rack.input' => "",
})
request.env.merge!(env_params)
env = request.env
# create a strategy instance
strategy = CustomStrategy.new env, :user
# processing a test request eventually calls _catch_warden:
ret = _catch_warden do
# when a controller action is triggered, its before filter would require authentication. Devise uses
# warden to execute the strategies.
# simulate its selection as the winning strategy (the custom response is read from .winning_strategy)
warden.winning_strategy = strategy
# And then the strategy is executed:
strategy.authenticate!
# after the strategy executes, halt! has been called (from in custom!) above which eventually results in
# the :warden symbol being thrown, which is caught in Devise::TestHelpers#_catch_warden
throw :warden
end
# after this point, @response should be set to the custom response when triggered by a custom strategy, or
# the response of the FailureApp as normal.
ret
teardown do
Warden::Strategies._strategies.delete(:custom_strategy)
end
test "custom strategy can return its own status code" do
ret = call_custom
ret = get :new
# check the returned rack array
assert ret.is_a?(Array)
@ -82,7 +50,7 @@ class CustomStrategyTest < ActionController::TestCase
end
test "custom strategy can return custom headers" do
ret = call_custom
ret = get :new
# check the returned rack array
assert ret.is_a?(Array)
@ -91,5 +59,4 @@ class CustomStrategyTest < ActionController::TestCase
# check the saved response headers as well.
assert_equal response.headers['X-FOO'], 'BAR'
end
end

View File

@ -84,6 +84,7 @@ Rails.application.routes.draw do
match "/set", :to => "home#set"
match "/unauthenticated", :to => "home#unauthenticated"
match "/custom_strategy/new"
root :to => "home#index"
end