How can I use Cucumber to test Devise's Rememberable functionality? - ruby-on-rails

I'd like to have a Cucumber feature testing the rememberable functionality of devise (a remember me cookie).
It is easy to check the remember me check box using capybara but how should I simulate a user returning to the site after closing their window?

I came up with the following rack-test hack, and slightly cleaner selenium api use, to test Devise remember-me functionality in cucumber/capybara. It just tells the driver to manually erase the session cookie. Not all drivers are supported, I only implemented the two I've used:
http://gist.github.com/484787
This assumes cookie storage of the session. Remove #announce tag from the scenario to get rid of the verbosity.
Another option, suggested by Matt Wynne in the mailing list discussion, may be looking at other cookie stores, and deleting them by query or file deletion:
lifted from agile rails book:
config.action_controller.session_store = CGI::Session::PStore (or just :p_store)
config.action_controller.session_options[:tmpdir] = "/Users/dave/tmp"
config.action_controller.session_options[:prefix] = "myapp_session_"
or
rake db:sessions:create
config.action_controller.session_store = :active_record_store
Rails also has a reset session method, but I believe we don't have access to this because we can't hook into the rails session when testing with capybara.
Hope this helps,
Nick

nruth's gist was really helpful but I felt like deleting the cookie by name was cheating. I created a step that deletes the cookies a browser would delete when it was closed and restarted (any cookie without an expiry date set and set in the future).
You can see it in this commit (though I've only done it for the RackTest driver as I don't have Selenium setup). You can also see my login/remember_me feature in this commit. And I refactored the classes to separate files in this commit.
I hope that's helpful.

I used email-spec to accomplish this. My Scenario looks like the following:
#allow-rescue
Scenario: Create New Account (Everything cool)
Given I am not authenticated
When I go to register
And I fill in "Name" with "bill"
And I fill in "Email" with "bill#example.com"
And I fill in "Password" with "please"
And I fill in "Password Confirmation" with "please"
And I press "Sign up"
Then "bill#example.com" should receive an email
And I open the email
And I should see "Confirm my account" in the email body
When I follow "Confirm my account" in the email
Then I should see "Your account was successfully confirmed. You are now signed in."
Note the #allow-rescue decoration above the scenario that is necessary when using Devise.
Hope this helps.

I guess you could log out with capybara, and then log back in something like
Given I am on the login screen
And I select 'Remember Me'
And I click 'login'
Then I should be 'logged in'
When I click 'log out'
Then I should be 'logged out' #=> potentially destroy a session here?
When I click log in
Then I should be logged in
And I should not be directed to the login form.
This should use cabybara's current cookie state to model this flow.

You can use show_me_the_cookies for this as shown below:
And(/^I leave the site$/) do
expire_cookies
end

Related

I want to delete a particular cookie using Capybara within Cucumber test and don't want to use Webkit / Poltergeist

I have tried a couple of ways like using browser.manage.delete_cookie and also setting cookie to empty by doing something like this : page.driver.browser.manage.set_cookie("#{'cookie_name'}:") but none of them helped.
This is my Step Defn file
Given('I am on Learn Page') do
page.driver.browser.manage.window.resize_to(1800,990)
#tried this to click on the radio button from chrome settings to disable all cookies, but failed
visit "chrome://settings/cookies"
find('.//*[#id="label"]/text()').click
#find('#radioCollapse').click
#failed with this too
visit "https://mylearnwebsite.com/"
browser = Capybara.current_session.driver.browser
browser.manage.delete_cookie 'my_cookie'
#and with this as well
page.driver.browser.manage.set_cookie("{'my_cookie'}:")
end
All of them either says, unable to locate element or Selenium::WebDriver::Error::NoSuchElementError
I am a newbie to Capybara and trying to automate a couple of web application features using the same.It would be a great help if anyone from the group suggest how to proceed here.
You need to be on a page where the cookie would be valid in order to interact with the cookies (WebDriver spec limitation). Assuming that https://mylearnwebsite.com/ is the site where the cookie is valid and you're using the selenium driver then
visit "https://mylearnwebsite.com/"
page.driver.browser.manage.delete_cookie 'my_cookie' # this is driver specific
page.refresh # to reload the page without the cookie
...
would delete the cookie. Since you haven't shown what you're actually checking after removing the cookie it's hard to guess what behavior you're looking to actually test, so the code above is just calling refresh to see the page state without the cookie.
Noe: From a general system/feature testing purpose it's usually not valuable to manually interact with cookies, you're generally better off just testing the user behaviors which have a side-effect of modifying cookies.

Using Devise, how can I check status of cookie for settings like remember_me?

Running Windows 8 with Rubystack, Ruby 2.0.0p353, Rails 4.0.3. Built own application using Rails Composer including Devise, CanCan and Rolify. Trying to configure remember_me to be set on every user except admins and have those users remain logged in basically forever. Seems to be working in the code, but I'd like to see what the cookie is saying. I haven't yet worked with cookies and am looking for a thread to pull to unravel that mystery.
I add this code to app/models/user.rb to set remember_me:
def remember_me
true unless self.admin?
end
In config/initializers/devise.rb, I set the following variables:
config.remember_for = 10.years
config.extend_remember_period = true
Users with standard user role have the PostgreSQL column remember_create_at set as long as they are logged in. It is cleared when they logout. Users with admin role never have this column set. That seems right.
I just cannot get my head around checking the cookie out. I believe it should have remember_me information in it so that the standard role user remains logged in across browser sessions? How can I dig that information out and verify it? Thanks...
Are you just trying to inspect the cookies on the browser side? Firebug has a cookies tab for that, and if you're using Chrome, you can view them under Resources > Cookies in the web development tool.

Test stay signed in with Rspec

I'm a bit stumped on how (and where) to write some rspec tests for the "stay signed in" functionality you see all over, including on the google login.
The examples I found on the web weren't much help. Specifically I want to test these two scenarios.
1
a) user signs in with valid credentials without having clicked "stay
signed in"
b) user closes the browser, re-opens it and requests a protect page.
The user should not see the protected page.
The user should see the page asking them to signed in.
2
a) user signs in with valid credentials and having clicked "stay
signed in"
b) user closes the browser, re-opens it and requests a protected page.
The user should not see the page asking them to sign in.
The user should be taken to the protected page.
My first attempt at solving the problem involved simulating a browser close by deleting the user_id I stored in the session (since it gets deleted on browser close). However, these tests failed because I was working in the request spec folder and have no access there to the session variables. My earlier related question: session available in some rspec files and not others. how come?
What is the best way to do these test with rspec?
I think you should try standard rails method for integration tests - open_session.
Personally I never did that and can't give you tested code.
See multiple sessions example on rails guides.
There are two problems here, that I think belong into different tests:
User cannot access protected page when not logged in. That's a controller test.
User gets logged in automatically even after the session has been destroyed, so long the "remember" me flag was set in the cookie.
For #1, you can try something like:
describe UsersController do
context "when not logged in" do
context "GET users/edit" do
it "redirects to login" do
get :edit, :id => 123
response.should redirect_to login_path
end
end
end
end
You can make a more general test case that asserts all actions which aren't explicitly listed, so that you don't have test gaps if the access code later becomes more permissive by accident. But that's a more subtle point.
For #2 you can write a request spec that sets the "remember me flag", then logs out, then logs in again and checks that you get to the page you expected. Drive all this from the browser by filling out credentials, checking the remember me box, clicking buttons.
The question is: Why? Why do you want to test this? Are you using a home-grown login system? Highly discouraged, unless you're a top-notch security expert. If you're not using a home-grown system, but instead Devise which comes tested, then don't re-test the library functionality. Just only test your application code, such as access rights to certain pages, which is covered by #1. You can also take a look at the tests that come with Devise how they test for this condition.
Hope this helps.
Update To clarify the request spec for #2. As mentioned in the other answer by #cutalion (who deserves the credit for the right answer), the mechanism for verifying that login can persist across session closing is built into the ActionDispatch IntegrationTest framework with open_session.
See Rails docs IntegrationTest API which includes examples. A blog post expanding on the use of a custom DSL.
This still seems to be a recurring problem in 2018. Some basic documentation is clearly missing. I finally found a solution: "Show me the cookies" gem.
Switching the Capybara driver clears the session, so one possibility would be changing the driver in the middle of a test to simulate browser close/open. But getting an alternative driver (for example selenium, or selenium_chrome) to work is not trivial, and besides this method would probably delete all cookies.
Also this simple command resets the session: Capybara.reset_sessions! But the problem again is that it not only destroys the session cookie but also permanent cookies. So it's useless for testing "Remember me" functionality.
I finally settled on Show me the cookies gem. It was very simple to install and implement. I just followed the provided directions for rspec. The command expire_cookies provides a satisfying simulation of quitting and opening a browser.

Destroy cookies in rails, facebook connect, facebooker plugin

So I have sort of a weird situation going on. I am using the Facebooker plugin for rails where I want users to be able to login and logout with their facebook profiles. However, once users logout, if I refresh the page, it logs them back in. This is only when users log in with facebook connect.
I think the problem is that a rogue cookie is just re-instantiating the session and thus my best guess is to manually destroy the cookies but to be honest I'm not entirely sure of how to do this. I printed out my cookie list (from request.cookies) both before and after I click logout. After logout is clicked, I still have this cookie lingering...but don't know how to delete it.
fbsetting_0b78c8f2c95ce671470bdcb1c19e5070 {"connectState":1,"oneLineStorySetting":1,"shortStorySetting":1,"inFacebook":false}
After playing around with it a little more, that cookie isn't even there...but upon refreshing the page I am logged in again.
I'm doing this all on localhost...not sure if that should cause a problem or not.
Any ideas?
This is what I have in users controller
def logout_facebook
clear_facebook_session_information
redirect_to root_url
end
and this is how it is triggered
<%= fb_logout_link("Logout out", "#{root_url}users/logout_facebook")%>
This is how I got it to remove the cookies on the local side.
Note: don't forget to add a route to the logout_facebook method in your routes.
Destroying local cookies isn't enough to terminate a Facebook Connect session. The connect JS library will recreate destroyed cookies as long as you still have an active session on the facebook.com domain -- and those cookies are inaccessible to you.
All log-outs must be handled by calling the logout function in the connect library.
e.g.,
<script>$H.fbconnect.logout();</script>
To delete a cookie, you need to set the cookie again with an expiration date in the past.
a million thanks for the clean and full-proof solution to such a frazzled functionality. I've been literally going Blank over last 2 weeks since I came across the fact true to I believe almost all NEW-facebooker users, "facebook connect logging out completely". Or maybe just it was just a lack of documentation or some code excerpt I wasn't able to find till date in facebooker cover docs.
Anyway away from all that, moving to the solution which made me achieve it... Following to #James B method above.
Obviously I am assuming you all have read facebooker installation, configuration & usage instructions http://github.com/mmangino/facebooker.
I also have used a plugin "authlogic_facebook_connect" which you can find at github.com page of kalasjocke/authlogic_facebook_connect.
Now I assume you already configured your application to work with facebook connect by using fb_login_button or authlogic_facebook_login_button. Clicking it you'd see a popup to log yourself into both your facebook account and into the local account of your app. Once you login you'd be getting a facebook_session to handle saving a new user in your DB (only if you wish to).
By default you'd not be getting birthday and email address of users in the facebook_session as response from facebook. To get them you need something like this in your initialization javascript in the body tag of the rhtml page...
<%= fb_connect_javascript_tag %> <%=
init_fb_connect "XFBML",{
:app_settings=>" {
permsToRequestOnConnect :
'email,user_birthday' }"} %>
<%= authlogic_facebook_login_button %>
Now finally what I did for getting logged out of both Facebook and my site.
Inside the header of your application or wherever the LOGIN, LOGOUT, loggedin users's name etc will display...
:delete
%>
The two logout buttons are for different kind of users.. a. who registered directly on your site, b. who registerd to your site via facebook
Route for logout_both in routes.rb
map.connect "/logout_both",
:controller=>"users",
:action=>"logout_both"
The action for logout_both inside users_controller.rb
def logout_both
current_user_session.destroy
#clear_facebook_session_information
flash[:notice] = "Logout successful!"
redirect_to root_path end #End of method logout_both
You need to make sure that there are no filters defined in the controller which could restrict logout_both action to be executed without a session.
Oh yes and if you're wondering why that "clear_facebook_session_information" is commented in the action. Then don't worry, you're already logged out of facebook before entering this action, this line is no longer needed. Boss we're using "fb_logout_link" which first logs you out and then redirects you to this new action.
Well, that's about it. If this doesn't do it........ get your hands dirty like I am doing.. dig in.. solve it yourself. Facebooker is deep but has a definite END!!!
Again many thanks to mangino & kalasjocke for making facebook connect almost no painful for rails.
One clarification people... in my Answer above.. There is a hick, which is.. After logout, if you refresh the page.. it would again show you logged in on your site.. however you're successfully logged out of Facebook.
I was wrong, ONE BIG CLARIFICATION...... As a matter of fact you DO NEED THESE TWO LINES IN YOUR "logout_both" action
def logout_both <br/>
current_user_session.destroy<br/>
clear_facebook_session_information #MANDATORY TO COMPLETELY CLEAR COOKIES<br/>
reset_session # TO BE 100% sure you can use it optionally<br/>
flash[:notice] = "Logout successful!"<br/>
redirect_to root_path <br/>
end

Implementation of "Remember me" in a Rails application

My Rails-app has a sign in box with a "remember me" checkbox. Users who check that box should remain logged in even after closing their browser. I'm keeping track of whether users are logged in by storing their id in the user's session.
But sessions are implemented in Rails as session cookies, which are not persistent. I can make them persistent:
class ApplicationController < ActionController::Base
before_filter :update_session_expiration_date
private
def update_session_expiration_date
options = ActionController::Base.session_options
unless options[:session_expires]
options[:session_expires] = 1.year.from_now
end
end
end
But that seems like a hack, which is surprising for such common functionality. Is there any better way?
Edit
Gareth's answer is pretty good, but I would still like an answer from someone familiar with Rails 2 (because of it's unique CookieSessionStore).
You should almost certainly not be extending the session cookie to be long lived.
Although not dealing specifically with rails this article goes to some length to explain 'remember me' best practices.
In summary though you should:
Add an extra column to the user table to accept a large random value
Set a long lived cookie on the client which combines the user id and the random value
When a new session starts, check for the existence of the id/value cookie and authenticate the new user if they match.
The author also recommends invalidating the random value and resetting the cookie at every login. Personally I don't like that as you then can't stay logged into a site on two computers. I would tend to make sure my password changing function also reset the random value thus locking out sessions on other machines.
As a final note, the advice he gives on making certain functions (password change/email change etc) unavailable to auto authenticated sessions is well worth following but rarely seen in the real world.
I have spent a while thinking about this and came to some conclusions. Rails session cookies are tamper-proof by default, so you really don't have to worry about a cookie being modified on the client end.
Here is what I've done:
Session cookie is set to be long-lived (6 months or so)
Inside the session store
An 'expires on' date that is set to login + 24 hours
user id
Authenticated = true so I can allow for anonymous user sesssions (not dangerous because of the cookie tamper protection)
I add a before_filter in the Application Controller that checks the 'expires on' part of the session.
When the user checks the "Remember Me" box, I just set the session[:expireson] date to be login + 2 weeks. No one can steal the cookie and stay logged in forever or masquerade as another user because the rails session cookie is tamper-proof.
I would suggest that you either take a look at the RESTful_Authentication plug in, which has an implementation of this, or just switch your implementation to use the RESTful Authentication_plugin. There is a good explanation about how to use this plug in at Railscasts:
railscasts #67 restful_authentication
Here is a link to the plugin itself
restful_authentication
The restful_authentication plugin has a good implementation of this:
http://agilewebdevelopment.com/plugins/restful_authentication
Note that you don't want to persist their session, just their identity. You'll create a fresh session for them when they return to your site. Generally you just assign a GUID to the user, write that to their cookie, then use it to look them up when they come back. Don't use their login name or user ID for the token as it could easily be guessed and allow crafty visitors to hijack other users' accounts.
This worked like a charm for me:
http://squarewheel.wordpress.com/2007/11/03/session-cookie-expiration-time-in-rails/
Now my CookieStore sessions expire after two weeks, whereby the user must submit their login credentials again in order to be persistently logged-in for another two weeks.
Bascially, it's as simple as:
including one file in vendor/plugins directory
set session expiry value in application controller using just one line
I would go for Devise for a brilliant authentication solution for rails.

Resources