Reputation: 1064
Ruby-2.2.0 Rails-4.2 Devise-3.4.1 Declarative_Authorization-0.5.7
I am converting a Rails-3.2 app to Rails-4. The app employs both Devise authentication and Declarative Authorization. I am using the default Devise controller.
At this juncture I have most of the authentication scenarios passing. What I have run into however is that I cannot logout through Cucumber but I can in the browser. The step in question looks like this:
When /select end the current session/ do
selector_type = "#"
selector_value = link = "session_end_action_id"
selector = selector_type + selector_value
click_button( selector_value )
end
The routes look like this:
. . .
new_user_session GET /users/sign_in(.:format) devise/sessions#new
user_session POST /users/sign_in(.:format) devise/sessions#create
destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
user_password POST /users/password(.:format) devise/passwords#create
new_user_password GET /users/password/new(.:format) devise/passwords#new
edit_user_password GET /users/password/edit(.:format) devise/passwords#edit
PATCH /users/password(.:format) devise/passwords#update
PUT /users/password(.:format) devise/passwords#update
user_unlock POST /users/unlock(.:format) devise/unlocks#create
new_user_unlock GET /users/unlock/new(.:format) devise/unlocks#new
GET /users/unlock(.:format) devise/unlocks#show
account POST /account(.:format) users#create
new_account GET /account/new(.:format) users#new
edit_account GET /account/edit(.:format) users#edit
GET /account(.:format) users#show
PATCH /account(.:format) users#update
PUT /account(.:format) users#update
DELETE /account(.:format) users#destroy
authenticate GET /authenticate(.:format) devise/sessions#new
. . .
users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
. . .
The code in the view looks like this:
<%-if current_user-%>
<span class="authenticated_session" id="authenticated_session">
<%=button_to( I18n.t( :session_end ).strip.titleize,
:destroy_user_session,
:class => "button logout",
:confirm => I18n.t( :session_end_confirm ).strip.titleize,
:id => :session_end_action_id,
:method => :delete,
:title => I18n.t( :session_end_logout ).strip.titleize )-%>
</span
<%-else-%>
<%=button_to( I18n.t( :session_start ).strip.titleize,
:new_user_session,
:class => "button login",
:id => :session_start_action_id_top,
:method => :get,
:title => I18n.t( :session_start_login ).strip.titleize )-%>
<%-end-%>
When I am authenticated and press the logout button then I am logged out. When I run the cucumber step I get an authorisation error from Declarative Authorization.
<p class='security classified' style='color: orangered'>
you are not authorised to access the requested resource</p>
<br/>
<!-- End of header section from layouts/application.html.erb -->
This is generated here in the application controller:
def permission_denied
if current_user
flash[:security_classified] = I18n.t( :security_classified ).strip
else
flash[:security_restricted] = I18n.t( :security_restricted ).strip
end
redirect_back_or_default( welcome_url )
end
and that is apparently being called from the Users controller:
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 21]]
Rendered public/hll_authorisation_notice.html (0.1ms)
Rendered welcome/show.html.erb within layouts/application (3.6ms)
Completed 200 OK in 105ms (Views: 100.4ms | ActiveRecord: 0.6ms)
Started DELETE "/users/sign_out" for 127.0.0.1 at 2015-01-26 15:19:48 -0500
Processing by UsersController#destroy as HTML
Parameters: {"id"=>"sign_out"}
(0.2ms) SELECT COUNT(*) FROM "users"
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 21]]
CACHE (0.0ms) SELECT COUNT(*) FROM "users"
Role Load (0.4ms) SELECT "roles".* FROM "roles" INNER JOIN "clearances" ON "roles"."id" = "clearances"."role_id" WHERE "clearances"."user_id" = ? [["user_id", 21]]
CACHE (0.0ms) SELECT COUNT(*) FROM "users"
Redirected to http://www.example.com/welcome
Filter chain halted as :filter_access_filter rendered or redirected
Completed 302 Found in 23ms (ActiveRecord: 1.0ms)
Started GET "/welcome" for 127.0.0.1 at 2015-01-26 15:19:48 -0500
Processing by WelcomeController#show as HTML
(0.2ms) SELECT COUNT(*) FROM "users"
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 21]]
Rendered public/hll_authorisation_notice.html (0.1ms)
Rendered welcome/show.html.erb within layouts/application (1.5ms)
Completed 200 OK in 61ms (Views: 57.2ms | ActiveRecord: 0.4ms)
(0.4ms) rollback transaction
Inspecting (reformatted for this purpose) the object in the permission_denied method reveals this:
#<UsersController:0x00000006ba52d8 @_action_has_layout=true,
@_routes=nil, @_headers={"Content-Type"=>"text/html"}, @_status=200,
@_request=#<ActionDispatch::Request:0x00000006ba51c0
@env={"rack.version"=>[1, 3], "rack.input"=>#<StringIO:0x00000006c378e0>,
"rack.errors"=>#<StringIO:0x00000006c379a8>, "rack.multithread"=>false,
"rack.multiprocess"=>true, "rack.run_once"=>false,
"REQUEST_METHOD"=>"DELETE", "SERVER_NAME"=>"www.example.com",
"SERVER_PORT"=>"80", "QUERY_STRING"=>"", "PATH_INFO"=>"/users/sign_out",
"rack.url_scheme"=>"http", "HTTPS"=>"off", "SCRIPT_NAME"=>"",
"CONTENT_LENGTH"=>"14", "rack.test"=>true, "REMOTE_ADDR"=>"127.0.0.1",
"HTTP_REFERER"=>"http://www.example.com/",
"HTTP_HOST"=>"www.example.com",
"CONTENT_TYPE"=>"application/x-www-form-urlencoded",
"HTTP_COOKIE"=>"_proforma_session=. . .
So, this is definitely an issue with the Users Controller. But error is only encountered during a Cucumber run. When I serve the application with Rails server and access it from a browser, sign in and sign off then I am logged out without any error as shown in log extract below.
Started DELETE "/users/sign_out" for ::1 at 2015-01-26 15:45:50 -0500
Processing by Devise::SessionsController#destroy as HTML
Parameters: {"authenticity_token"=>"Xl9Ui1a6jt8gyjZOuh0lsefUqFI1eEunaXivaEdfwWhMofYhYbRumnZlsRQjwmjWiC1C7sI7O3FwDgEf9lJzJw=="}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
(0.2ms) SELECT COUNT(*) FROM "users"
(0.1ms) begin transaction
(0.4ms) UPDATE "users" SET "accessed_at" = '2015-01-26 20:45:50.179843', "changed_at" = '2015-01-26 20:45:50.180748', "lock_version" = 20 WHERE ("users"."id" = 1 AND "users"."lock_version" = 19)
(103.2ms) commit transaction
Redirected to http://localhost:3000/
Has anyone any ideas as to what might be happening here? Is the token value found in the browser sign_out significant?
P.S. If I just do this:
visit('/users/sign_out')
then the step passes. I am taking a wild guess here but is there anything about the javscript used with html buttons in RoR that might be causing what I am seeing?
Upvotes: 0
Views: 369
Reputation: 1064
I think that I have discovered what was wrong; accidentally whilst researching a different and unrelated problem.
The issue I believe is the default testing method used by Capybara:
https://github.com/jnicklas/capybara#selecting-the-driver
By default, Capybara uses the :rack_test driver, which is fast but limited: it does not support JavaScript, nor is it able to access HTTP resources outside of your Rack application, such as remote APIs and OAuth services. To get around these limitations, you can set up a different default driver for your features. For example if you'd prefer to run everything in Selenium, you could do:
Capybara.default_driver = :selenium
However, if you are using RSpec or Cucumber, you may instead want to consider leaving the faster
:rack_test
as the default_driver, and marking only those tests that require a JavaScript-capable driver using:js => true
or@javascript
, respectively. By default, JavaScript tests are run using the:selenium driver
. You can change this by settingCapybara.javascript_driver
.You can also change the driver temporarily (typically in the Before/setup and After/teardown blocks):
Capybara.current_driver = :webkit # temporarily select different driver
... tests ...
Capybara.use_default_driver # switch back to default driver
Note: switching the driver creates a new session, so you may not be able to switch in the middle of a test.
Upvotes: 0