Accessing warden login information in Sinatra test response - ruby

Practically everything I am looking for is said in the title - I need to access warden user variable in test to check whether authentication worked. Another way is also possible for me, just trying to test authentication nicely :)
should "authenticate" do
post "/login", {:login => "test_login", :password => "password"}, {"HTTP_HOST" => "test.host"}
assert last_response.redirect?
assert_equal last_response.env["warden"].user.login, "test_login"
end

You can't get at the internal request environment in a rack-test-style full-stack test. If you want to verify that you've been logged in as a specific user you'll need to look at something like the redirect URL (if it redirects to a user-identifiable URL) or follow the redirect (easy with rack-test's follow_redirect! helper) and then look for evidence of the User ID in the HTML.
I would have thought that you don't really need to test Warden itself, but you will want to make sure you're providing the correct information to it, and aren't mangling it in the middleware stack.
You might find something like Cucumber handy for doing genuine form-filling and submission.
Finally, Warden has its own test helpers (which definitely work with rack-test) so you can set up a request to be logged in without having to actually run through the logging in request/redirect cycle in each test - https://github.com/hassox/warden/wiki/testing has more details.

Related

Simplest method of enforcing HTTPS for Heroku Ruby Sinatra app

I have an app I created on Heroku which is written in Ruby (not rails) and Sinatra.
It is hosted on the default herokuapp domain so I can address the app with both HTTP and HTTPS.
The app requests user credentials which I forward on to an HTTPS call so the forwarding part is secure.
I want to ensure my users always connect securely to my app so the credentials aren't passed in clear text.
Despite lots of research, I've not found a solution to this simple requirement.
Is there a simple solution without changing my app to Ruby rails or otherwise?
Thanks,
Alan
I use a helper that looks like this:
def https_required!
if settings.production? && request.scheme == 'http'
headers['Location'] = request.url.sub('http', 'https')
halt 301, "https required\n"
end
end
I can then add it to any single route I want to force to https, or use it in the before filter to force on a set of urls:
before "/admin/*" do
https_required!
end
Redirect in a Before Filter
This is untested, but it should work. If not, or if it needs additional refinement, it should at least give you a reasonable starting point.
before do
redirect request.url.sub('http', 'https') unless request.secure?
end
See Also
Filters
Request Object
RackSsl::Enforcer

Requesting An Access Token from Google API Returns 302

I'm trying to get an access token from Google API in my Ruby on Rails app, as part of an overall goal of setting up a raketask. I am able to get an Auth Code fine, but when I make a post request to get an access token, I am getting a 302 error. I'll describe my current code first, and afterward list how I've tried to solve the problem so far.
Current code:
#users_controller
def auth_access
client = Signet::OAuth2::Client.new(
:authorization_uri => 'https://accounts.google.com/o/oauth2/auth',
:token_endpoint_uri => 'https://accounts.google.com/o/oauth2/token',
:client_id => ENV['OAUTH_CLIENT_ID'],
:client_secret => ENV['OAUTH_CLIENT_SECRET'],
:scope => 'https://www.googleapis.com/auth/analytics.readonly',
:redirect_uri => 'http://localhost:3000/google/auth_callback'
)
redirect_to client.authorization_uri.to_s
end
This part works fine so far. It redirects to the consent page, and when the user agrees it then redirects them to the page with the auth code in the url parameters. Next I take that auth code and try to make a POST request to API for an access token:
#users_controller
def auth_callback
http = Net::HTTP.new('accounts.google.com')
path = '/o/oauth2/token'
data = "code=#{params['code']}&client_id=#{ENV['OAUTH_CLIENT_ID']}&client_secret=#{ENV['OAUTH_CLIENT_SECRET']}&redirect_uri=http://localhost:3000/auth_final&grant_type=authorization_code"
response = http.post(path, data)
end
This when I run into a problem. The Google API returns a 302, and includes a message saying something akin to "we moved to 'https://accounts.google.com/o/oauth2/token'".
Here's how I've tried to fix the problem so far:
I assumed that the problem was that the http.post method is making a call to an http and not https.
I've tried including
http.use_ssl = true
http.ssl_version = :SSLv3
This returns the error "SSL_connect returned=1 errno=0 state=SSLv3 read server hello A: wrong version number".
I can take a guess at what this means, but I am still unsure of what the actual problem is and how to solve it. Googling the error message has not been a help.
In a similar vein, I tried using gems to make the https call for me, in particular HTTParty and Typheous, although I was not able to make any progress with them (and am still not even sure that it's an http/https problem).
I've tried using the Signet-Rails gem. This was the most productive method by far, making a successful API call and returning the information. However, it either wasn't saving the refresh token or I cannot find where it is being saved. As I need access to that token to run the rake tasks, I gave up on Signet-Rails.
I tried using Legato, and was constantly running into various problems. Overall, Legato left me with the impression that it did not integrate getting the auth code, consent and tokens into the app, instead requiring the developer to set those up in advance outside of the app's scope. I want to be able to set up the auth code as part of the app. If I am understanding Legato properly, then it is not the gem I need.
I've also tried other various odds and ends but to no avail. The above solutions were the tactics I kept coming back to. Primarily I'm looking for an answer to what is going wrong in my code, and what is the best avenue to fix it (and if I was going down the right track with any of my attempted solutions above, which one?)
Thanks for taking the time to read this and answer!
(on a complete sidenote, those last three list items should be 2, 3, 4, but the stackoverflow text editor thinks it knows better than me...)
Specify the port:
http = Net::HTTP.new('accounts.google.com', 443)
Source: SSL Error on HTTP POST (Unknown Protocol)

Implementing Remember Me in Padrino 0.11 with new admin interface

I'm trying to implement a 'Remember Me' feature in the new Padrino 0.11 Admin interface, but having a little bit of trouble due to the differences between it and Rails. Basically, I'm following along with http://railscasts.com/episodes/274-remember-me-reset-password.
I've managed to get the Remember Me and auth_token working handily, and I can see the cookie in the Dev console when I go to look at it. I am having a lot of trouble figuring out how to get the application to do autologin on the cookie when it is present though. I'm sure it's something stupid, but this is where I'm up to.
For instance, I've got the actual Remember Me creating an auth_token and setting it fine to the cookie (I can see it on localhost) in the dev console on Chrome via this in the sessions controller.
admin/controllers/sessions
post :create do
if account = Account.authenticate(params[:email], params[:password])
set_current_account(account)
if params[:remember_me]
response.set_cookie('da_app', value: account.auth_token,
expires: (Time.now + 1.year + 1.day))
end
flash[:success] = "You've successfully logged in as #{account.name}."
redirect url(:base, :index)
else
params[:email], params[:password] = h(params[:email]), h(params[:password])
flash[:error] = pat('login.error')
redirect url(:sessions, :new)
end
end
However, due to my inexperience with padrino, a little stumped as to where I'd put the bit of logic which triggers before an incoming request, checks for the cookie and then logs the user in. I tried the following, which is not perfect but which is definitely not working (though not sure why... =< ) and in fact, the code block to detect the cookie does not even seem to be firing (which seems pretty basic.).
admin/app.rb (not sure this is the right place for it actually)
before '/*' do
if request.cookies['da_app'].exists?
set_current_account(Account.find_by_auth_token(request.cookies['da_app']))
redirect url(:base, :index)
end
end
So, I'm sure it's probably dead simple to solve but a bit stumped on this one (and also, am really trying to avoid using a gem plugin like padrino-warden or the like at the moment and implement this from scratch as an exercise.).
(Also, bonus karma points on helping solve this one as I'm implementing this as part of some pro bono work for a global conservation charity.)

Devise not working well with multiple subdomains on RoR3 application

I have seen a lot of questions about this topic, but a lot of them have contradictory information, and for some reason it didnt work for me.
I have:
a top level domain: i.e. lvh.me (development).
each user has subdomains: i.e. userdomain.lvh.me
The login form is in the top level domain: lvh.me
I want:
If an user logs in, the session needs to be shared between all the subdomains. I mean, the session needs to be active in lvh.me:3000/something and userdomain.lvh.me:3000
If an user logs out from lvh.me:3000/something it should work, and if the user logs out from userdomain.lvh.me:3000 it should work also.
I tried
Setting in an initializer the following:
MyApplication::Application.config.session_store :cookie_store, :key => '_mykey', :domain => :all
What happened?
I can login in lvh.me:3000, I am correctly redirected to lvh.me:3000/internalpage and if I go to subdomain.lvh.me:3000 it works great. I can also logout from lvh.me:3000/internalpage BUT if I try to logout from subdomain.lvh.me:3000 it doesn't work. The destroy action in Devise SessionsController is executed and everything, but the session doesn't die.
According to http://excid3.com/blog/sharing-a-devise-user-session-across-subdomains-with-rails-3/,
The trick here is the :domain option. What this does is sets the level
of the TLD (top level domain) and tells Rails how long the domain is.
The part you want to watch out for here is that if you set :domain =>
:all like is recommend in some places, it simply won’t work unless
you’re using localhost. :all defaults to a TLD length of 1, which
means if you’re testing with Pow (myapp.dev) it won’t work either
because that is a TLD of length 2.
So, after reading that I also tried
MyApplication::Application.config.session_store :cookie_store, :key => '_mykey', :domain => 'lvh.me'
What happened?
I can login in lvh.me:3000, I am correctly redirected to lvh.me:3000/internalpage and if I go to subdomain.lvh.me:3000 it doesn't work, i have no session there. If I go back to lvh.me:3000/internalpage my session has disappeared. What happened there?
What else?
Then, after reading rails 3.2 subdomains and devise I changed my initializer line to
MyApplication::Application.config.session_store :cookie_store, :key => '_mykey', :domain => '.lvh.me'
Note the "." before the domain name.
According to the post in SO:
This allows this cookie to be accessible across subdomains and the
application should maintain it's session across subdomains. May not be
100% what you are looking for but it should get you going in the right
direction.
What happened?
Nothing, it didn't work. Same behavior if compared with the last thing I tried.
I finally tried What does Rails 3 session_store domain :all really do? , creating a custom class to handle the cookies. But I had no luck.
Of course that I deleted all the cookies and temp files before each attempt. Also I changed the name of the cookie.
Any help? Thanks!
According to this guy here: Rails: how can I share permanent cookies across multiple subdomains? You need to set the domain manually? Googling around it looks like '.domainname.com' with the dot at the beginning really is the way to go.
If you inherit from Devise::SessionsController you can manually set it on create
class SessionsController < Devise::SessionsController
def create
# modify the cookie here
super
end
end
I am setting up a working example to test that out, I'll post back afterwards, cheers!
And here is my Edit
Forget tempering with the token on create. The problematic is this, you need to have the token domain set to '.lvh.me' that's all there is to it, but domain: '.lvh.me' just doesn't do anything. Here is my proof of concept and ultimately it boiled down to a single change inside a controller:
class HomeController < ApplicationController
def index
cookies[:_cookietest_session] = {domain: '.lvh.me'}
end
end
In Chrome the token would look like this
And that for subdomain.lvh.me, lvh.me and any other subdomain I tried. I can sign_in/sign_out from any and the session is created/destroyed accordingly.
Now I wouldn't advise doing it the way I did, I liked the middleware approach I think it would work just fine if setup properly. Let me know if you need further help on this.
Cheers!
Ok last thing
I went back and tried domain: :all because it really ought to work as you have expected. If I access lvh.me I get a cookie with .lvh.me but if I got to subdomain.lvh.me I get one that reads .subdomain.lvh.me
I think the issue is that :all adds a . to the subdomain.lvh.me so you would stay logged in with foo.subdomain.lvh.me which doesn't do you much good.
:all seems to work if your original login is from the root domain lvh.me and you then redirect to a subdomain. but you can't log in through a subdomain with it set that way.
MyApplication::Application.config.session_store :cookie_store, :key => '_mykey', :domain => '.lvh.me'
looks like the correct way to specify this.
Note:
Make sure you restart rails after making change.
Make sure you clear cookies out for your domain before testing again. You can leave remnant cookies behind that are confusing between tests.

Testing Sinatra's redirect back in rspec

I am running a sinatra app and have a testing suite setup using rspec 2.7.0 and webrat 0.7.3 (both the most recent versions). I have an extensive set of tests for all of my request actions and it seems to be working fine. Today I discovered Sinatra's redirect back request-level helper and implemented it in a couple of areas of my application that were rendering forms with get requests which were taking parameters.
The nice thing about the redirect back helper is that if I have an action say:
get '/login' do
#used_var = params[:var]
haml :login
end
Which renders a form, I can have validation on the post request receiving the form:
post '/login' do
# pretend User.authenticate pulls back a user entry from the database if there
# is a valid username/password combination
unless User.authenticate(params[:username], params[:password]).nil?
redirect '/content'
else
flash[:notice] = "Invalid username/password combo"
redirect back # will redirect back to the get '/login' request
end
end
And if the form doesn't validate properly, it will redirect back to the page with the from and retain any parameters that were passed in without me having to worry about storing it to a session variable. The only problem is that rspec doesn't seem to want to play nicely with the redirect back helper. i.e. if I have a spec action that does this:
it 'should redirect to login when invalid username/password combo is received.' do
get '/login', :var => 'value'
fill_in 'username', :with => 'invalid_username'
fill_in 'password', :with => 'invalid_password'
click_button 'Submit'
last_response.should be_redirect; follow_redirect!
last_request.url.should include("/login")
end
The spec fails to pass because for some reason it seems that rspec or webrat isn't picking up on the redirect back helper and is instead redirecting the request back to the root url for my application ('/').
What I want to know is whether there is a way to get rspec to redirect to the proper location in these instances? The actual application functions as expected when I test it with my browser (it redirects me to the first page with parameters), but the rspec tests don't pass properly.
try to pass :referer => '/login' to your requests, so redirect_back can know where the actually 'back' is
Apparently this was a bug in rack, and it appears to have been fixed with the release of rack 1.3.0. I tested this spec with rack 1.2.5 (most recent version prior to 1.3.0 release), and it failed, but upon upgrading to 1.3, it started passing.
After some digging around, pretty sure that this pull request (here is the commit) was the change that fixed it.
So it is no longer an issue in rack ~> 1.3.

Resources