Devise+CanCan AccessDenied 重定向在开发环境和测试环境之间有所不同
我有一个带有 Devise 和 CanCan 的 Rails 3.1 (RC5) 应用程序。两者都配置良好并且按预期工作,只是当我运行集成测试以确保 AccessDenied 根据需要重定向时,重定向会转到 Devise 的登录而不是应用程序根目录。我可以在测试中验证用户是否仍然登录并且仍然可以访问应用程序的适用部分。
重定向是在这个短控制器中定义的,其他受限控制器继承该控制器(而不是直接继承 ApplicationController)。
class AuthorizedController < ApplicationController
before_filter :authenticate_user!
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.message
end
end
受限控制器如下所示:
class Admin::UsersController < AuthorizedController
load_and_authorize_resource
def index
@users = User.all.order('name')
end
...
end
我正在使用默认的 (ActionDispatch::IntegrationTest) 集成测试;我唯一拥有的额外测试宝石是 Capybara、Machinist 和 Faker(没有 RSpec、Cucumber 等)。
我的测试如下所示:
def test_user_permissions
sign_in users(:user)
get admin_users_path
assert_response :redirect
assert_redirected_to root_url
end
测试失败并显示:
Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/users/sign_in>
当我通过在开发环境中以受限用户身份登录来测试此功能时,我按预期重定向到“/”,但在集成测试中使用相同类型的用户失败。
在集成测试中,用户实际上并未注销,尽管重定向使其看起来像是正在发生。当我将测试更改为不测试重定向目标并继续尝试其他 URL 时,用户仍然登录并且测试通过。
附录&解决方案:
我最初没有包含掌握关键线索的sign_in方法。如下:
module ActionController
class IntegrationTest
include Capybara::DSL
def sign_in (user, password = 'Passw0rd')
sign_out
visit root_path
fill_in 'Email', :with => user.email
fill_in 'Password', :with => password
click_button 'Sign in'
signed_in? user
end
...
end
end
我在 sign_in
中混合了 Capybara 访问方法(visit
、click_button
等)和普通集成测试访问方法(>get
等)在测试中。当我使用 Webrat(在 Capybara 之前)时,这种混合按我的预期工作,但显然 Capybara 的会话状态是单独处理的,因此通过 Capybara 方法的访问经过身份验证,但通过普通集成测试方法的访问则不然。
I have a Rails 3.1 (RC5) app with Devise and CanCan. Both are configured well and working as expected except that when I run integration tests to ensure that AccessDenied is being redirected as desired, the redirect goes to Devise's sign in instead of the application root. I can verify in my test that the user is still logged in and can still access applicable parts of the app.
The redirect is defined in this short controller, which the other restricted controllers inherit (instead of directly inheriting ApplicationController).
class AuthorizedController < ApplicationController
before_filter :authenticate_user!
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.message
end
end
The restricted controllers look like this:
class Admin::UsersController < AuthorizedController
load_and_authorize_resource
def index
@users = User.all.order('name')
end
...
end
I am using the default (ActionDispatch::IntegrationTest) integration test; the only additional testing gems I have are Capybara, Machinist, and Faker (no RSpec, Cucumber, etc.).
My test looks like:
def test_user_permissions
sign_in users(:user)
get admin_users_path
assert_response :redirect
assert_redirected_to root_url
end
The test fails with:
Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/users/sign_in>
When I test this by logging in as a restricted user in my dev environment, I am redirected to '/' as expected, but using the same type of user in the integration tests fails.
In the integration tests, the user is not actually being logged out, although the redirect makes it look like that is happening. When I change the test to not test the redirection target and continue trying other URLs, the user is still logged in and the test passes.
Addendum & Solution:
I originally did not include the sign_in method that held the key clue. Here it is:
module ActionController
class IntegrationTest
include Capybara::DSL
def sign_in (user, password = 'Passw0rd')
sign_out
visit root_path
fill_in 'Email', :with => user.email
fill_in 'Password', :with => password
click_button 'Sign in'
signed_in? user
end
...
end
end
I was mixing Capybara access methods (visit
, click_button
, etc.) in sign_in
and vanilla integration test access methods (get
, etc.) in the test. When I used Webrat (before Capybara) this mixing worked as I expected, but evidently Capybara's session state is handled separately, so access via the Capybara methods was authenticated, but access via the vanilla integration test methods was not.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您没有在 ApplicationController 中发布您的设备配置,但看起来设备身份/登录检查是在 CanCan 授权检查之前加载的(这是有道理的)。
您的测试登录设置似乎无法正常工作,因为当没有有效的用户会话时,“/users/sign_in”是 devise 的默认重定向。
由于设备身份检查失败,因此它永远不会达到您的 CanCan 授权检查的要求。如果还没有用户,为什么要问用户可以做什么。
before_filters 将按照定义的顺序首先从基础 ApplicationController 执行,因此子类过滤器在基类过滤器之后执行。这就是为什么我认为你在基础 ApplicationController 中进行了 Devise 配置,导致它不会命中 CanCan。
如果您发布您的 Devise 配置/代码,我们也许能够帮助您进一步调试。
编辑/TLDR:
您看到登录重定向,因为 Devise 认为不存在有效的用户会话。您的测试“sign_in()”助手未按您想象的方式工作。这就是为什么它可以在开发模式下通过手动登录进行实时用户会话。
You didn't post your devise config in ApplicationController, but it looks like the devise Identity/Sign-in checks are loading up before the CanCan Authorization checks (which makes sense).
It looks like your test sign in setup isn't working correctly, because '/users/sign_in' is the default redirect for devise when there isn't a valid user session.
Because the devise identity check is failing, it's never hitting your CanCan Authorization check. Why ask what the user can do if there isn't a user yet.
The before_filters will execute from the base ApplicationController first on up the chain in the order they are defined, so subclass filters after base class filters. This is why I think you've got Devise config in your base ApplicationController causing this not to hit CanCan.
If you post your Devise config/code we may be able to help you debug it further.
EDIT / TLDR:
You are seeing a redirect to login because Devise doesn't think a valid user session exists. Your test "sign_in()" helper isn't working the way you think it is. That's why this works in dev mode with a live user session via manual login.