Testing behavior of post request for non-logged in user - ruby-on-rails

I'm working on a project where users login and out, a complete shocker I know, and we're ensuring that people can logout when done. One thing we've noticed is that for various reasons a user can try to click the logout button when the system have already logged them out. (Session timeout, logged in from another browser, or other reasons. The site only allows for one concurrent login per user.)
So what happens when the user tries to POST to the logout URL is that they fail a CSRF validation, since they don't have a valid session. We can't remove the CSRF validation because of security concerns.
As such I'm trying to add a test that allows me to test this behavior and so that instead of blowing up with an invalid CSRF token it'll silently accept that the user is already logged out and continue on with its business.
What I've tried is basically this:
context 'user is not logged in' do
before do
Rails.application.config.action_controller.allow_forgery_protection = true
end
it 'does not blow up' do
expect { post :destroy, {}, { some_key: 'val' } }.not_to raise_error
end
it 'redirects to logout path' do
expect(response).to redirect_to(successful_logout_path)
end
end
Which goes green despite not having had any logic changes in my controller, and manual testing confirms it's still not working.
Any suggestion for how to enable the CSRF validation for this context, or another way of testing this in the unit tests?

Devise was having a similar problem up until last year. I submitted an issue on Github here: https://github.com/plataformatec/devise/issues/2934 and it was eventually fixed. This other question should give you a hand: InvalidAuthenticityToken in Devise::SessionsController#destroy (sign out after already having signed out)

Related

omniauth - session empty after auth token returned

I'm hoping someone can help me figure out what's going wrong with an auth I'm adding into an app. The app itself uses normal authentication (username/password) using devise. Once a user is logged in, they are supposed to be able to connect to their email provider using OAuth (currently working on Microsoft365) which is what I'm working on right now.
I'm using the omniauth-oauth2 gem to implement the authentication, and as far as I can tell everything is working - the user is presented the MS login page, a POST request is returned with the token. All looks good, however when the callback comes in, the original user session is completely empty, and when I then redirect the user back to another page, they are kicked back to the login screen.
My callback is really simple at the moment
def auth_callback
# data = request.env['omniauth.auth']
if has_permission?
flash[:notice] = 'YAY!'
redirect_to root_url
else
flash[:alert] = "Could not authenticate with your mailbox"
redirect_to new_mailbox_path
end
end
I'm not doing anything with the data, and I know in a normal authentication system this is where I'd be getting/creating the user and signing in - but the user should already be signed in. And since they weren't created by oauth, I can't really use the MS user ID to find them anyway.
The only solution I can think of, is to send the user ID in the state when requesting auth from MS, so that when it is returned I can match that up and the "sign in" the user again but that feels wrong.
I am already bypassing CSRF so it shouldn't be that wiping it out
skip_before_action :verify_authenticity_token, only: %i[auth_callback]
skip_before_action :authenticate_user!, only: %i[auth_callback]
Is there anything else that would cause the session to be lost like this?

Rails multi-tenant app, losing session when visiting another subdomain on some servers. How to debug?

rails 3.17, devise 3.1.1 (recent upgrade from 2x, didn't affect anything), sessions stored in mongoid
Multi-tenant app, each account has its own subdomain. User can be a member of different accounts. When we open account1.app.com, we check if current user is a member of account1 and then let him in. No sign in required. Session stays the same.
Everything works fine with rspec/capybara feature tests. Also works with Pow on my Mac. As a logged in user i can visit different accounts, session stays the same, a new cookie for each subdomain is created with the same session_id. I don't have to signin again when visiting other subdomains, session id stays the same.
This is the rspec test for that features. It passes, and this is how is supposed to work
scenario "User can switch between activated accounts he is a member of" do
account1 = Fabricate(:account, subdomain: 'account1', company_name: 'account1')
account2 = Fabricate(:account, subdomain: 'account2', company_name: 'account2', owner: account1.owner)
visit new_user_session_url subdomain: account1.subdomain
fill_in 'user[email]', :with => account1.owner.email
fill_in 'user[password]', :with => account1.owner.password
click_button 'Sign in'
# user inside his account
expect(current_url).to eq home_url(subdomain: account1.subdomain)
#user inside his other account
within "ul.account_menu" do
click_link "account2"
end
expect(current_url).to eq home_url(subdomain: account2.subdomain)
end
Doesn't work if i use webrick or thin. When i (as logged in user from account1.app.com) try to visit another account like account2.app.com it loses my session.
I'm getting redirected to sign in form
session not loaded inside controller: Rack::Session::Abstract::SessionHash:xxx not yet loaded
empty session with only csrf_token is created before signin form rendered.
Also cookie with session_id for account2.app.com is not created. "This site has no cookies" says google chrome.
If i try to sign in again - i get WARNING: Can't verify CSRF token authenticity in log and redirected back to sign in form. 'csrf_token' in session equals csrf-token in header and authenticity_token in form of signin page.
If i return to account1.app.com everything keeps working working there. My session is loaded and everything proceeds as intended.
Partially works under Passenger Stand alone in production.
Again, i get redirected to sigin page once i try to visit another subdomain
But this time cookie with NEW session id is created for that subdomain
After signin form is submitted a NEW session is created and i'm finally inside.
Would greatly appreciate help, run out of ideas how to debug this.
The solution was to add tld_length: 2 attribute to config.session_store at initializers/session_store.rb
Now it looks like that, and sessions started working across subdomains properly. I wish it was more obvious, spent quite some time researching it. Got misguided by inconsistent results from different webservers.
MyApp::Application.config.session_store :mongoid_store, domain: :all, tld_length: 2

Resetting basic-authenticate-with

I am trying to do a very basic authentication. I've already tried the one ryan bates used in his screencast with bcrypt-ruby and user authentication.
For a really small project I want to use something else:
http_basic_authenticate_with :name => 'user', :password => 'secret'
I've got a global called $admin and I've got a method to set its value to false (similar to logout).
Is there a way to reset this authentication so that the user (admin) has to fill in the "login credentials" again?
Kind regards
Unfortunately in case of basic_auth the user stays logged in until the browser window is closed. If user logs in with basic_auth, the browser stores the authentication information, and sends the authentication parameters through the http headers with every request.
There is a small catch to this though:
After logging in with basic_auth, when user goes browsing though your app and goes from one link to another (e.g. from http://appdomain.com/link1 to http://appdomain.com/link2 he is really going from http://username:password#appdomain.com/link1 to http://username:password#appdomain.com/link2. The browser hides the username:password# part in you addressbar, so you do not know about it.
A dirty way to logout a user that has authenticated through basic_auth would be to create a link or redirect to http://invaliduser#appdomain.com/ that the browser does not hold authentication credentials to...
EDIT: or as an alternative redirect AND login a user into a no-privilege account that cannot view or do anything within your app through http://guest:password#appdomain.com
Hope it helped.

Rails: sign out logged in user on event

I'm using Rail3 with Devise gem. It does a great job when you need to lock user from signing in.
But it works just for new login attempts.
If he is already logged in - it won't sign him out immediately.
Here's is the typical use case:
Given admin user
when detects suspicious activity of certain user he locks it with malicious_user.lock('locking-reason')
% can config/initializers/session_store.rb
AppFoo::Application.config.session_store :cookie_store, :key => '_foo_session'
Given HTTP's statelessness, you can't immediately log out a user because you will need to wait until they make another request to your server. You could get around this via a push service I suppose, but that would be overkill.
My solution would be to add that person to a blacklist and then check if they're on the blacklist whenever they try to access a section intended for logged-on users only. This will render them unable to log on until you decide whether or not their activity is suspicious.
Example:
User is suspected of intolerable activity
Admin wants to check this out, so they temporarily add the user to the blacklist.
User clicks on an area of the page they were currently on when added to the blacklist.
Code checks for loggin status and blacklisted users.
Since the user is blacklisted, they are informed that they need to sign in to access the content
Once the user tries to sign in again you can inform them that their account has been temporarily disabled (or you can do this in the previous step).
perhaps the easiest way would be to redirect the user to the logout action when you lock them so:
malicious_user.lock('locking-reason')
redirect_to '/logout' and return
I'm not familiar with Devise so this may not be the best solution or even possible but it's how I would approach the problem
Use a before_filter in the ApplicationController that will do the following
before_filter :kick_out_blocked_user
protected
def kick_out_blocked_user
unless current_user.try(:active?)
redirect_to destroy_user_session_path
end
end

Check FB Connect session expire using facebooker

how to check whether FB Connect session is still valid or not using rails facebooker plugin ? Are there any helper or module that can check the session ? I figure out that if I open up 2 tab in browser, one login with facebook, another is with my site and login using FB Connect. When user trying to logout in my site, facebook will erase both cookie, but when user logout through facebook, it will erase cookie in facebook site only, so the cookie in my site still left behind and I need to check whether the cookie still valid or not...
Using Facebooker, you'll get an exception when you try to use the exception, which can be rescue_from'd in application.rb
rescue_from Facebooker::Session::SessionExpired, :with => :facebook_session_expired
def facebook_session_expired
clear_fb_cookies!
clear_facebook_session_information
reset_session # remove your cookies!
flash[:error] = "Your facebook session has expired."
redirect_to root_url
end
I can't upvote things yet, but the answer that adds the line:
page.redirect_to url
is correct. I'd recommend adding it to Facebooker if Mike is reading.
The fb_logout_link method does not redirect when Facebook session is invalid. Add a redirect callback to your logout_path and it will do the job for you.
def fb_logout_link(text,url,*args)
js = update_page do |page|
page.call "FB.Connect.logoutAndRedirect",url
# When session is valid, this call is meaningless, since we already redirect
# When session is invalid, it will log the user out of the system.
page.redirect_to url # You can use any *string* based path here
end
link_to_function text, js, *args
end

Resources