messaging from the model level to controller level for ajax purposes - ruby-on-rails

I have a page called /examples/1/duplicate
on there is an ajax form which kicks off /examples/1/run_duplicate
the controller method for run_duplicate runs Example.duplicate(session)
I pass in the session, which I understand is a holy no-no.
the duplicate model code is roughly:
def duplicate(session)
session[:duplicate] = 0
duplicate_foobars
session[:duplicate] = 1
duplicate_snafus
session[:duplicate] = 2
duplicate_widgets
session[:duplicate] = 3
end
I have another controller method on the duplicate page which is doing a long poll: /examples/1/check_dupe_status
The purpose was to get the updated status from session[:duplicate], tweak a progress report,
and report it back to the user via ajax so they could see the progress of Example.duplicate() .
sessions did not update the way I hoped. I can see the code run, but the sessions don't update, so /examples/1/check_dupe_status never knows that anything has kicked off.
Given that I did this wrong from the start, what is the correct way to notify a user about the state of Example.duplicate() ?

Sounds like you've got two separate requests, with one writing to the session and one trying to read from the session concurrently. Is that correct? Stop reading if it's not.
This can not work because the "session" is just a cookie - a header on the HTTP response that's downloaded to your browser as a cookie, then re-uploaded on the next request, then re-downloaded on the next response, ad nosium. In your code, here is the order of operations:
/examples/1/run_duplicate writes "0" to what is essentially a Ruby Hash, representing your session cookie
/examples/1/check_dupe_status reads the values from the session cookie that was just sent from your browser with this request. It probably didn't have anything at all in :duplicate, so it will appear as blank.
/examples/1/run_duplicate writes "1" to the Ruby session Hash
/examples/1/check_dupe_status reads, again, the session cookie which that request sent - nothing has changed
/examples/1/run_duplicate writes "2" to the Ruby session Hash
/examples/1/check_dupe_status reads from the session cookie it originally sent - no change
/examples/1/run_duplicate writes "3" to the Ruby session Hash and the request finishes, sending the session back as a cookie with a value of 3 at :duplicate.
/examples/1/check_dupe_status is still sitting there like a dufus, reading the blank session cookie it originally sent
At some point, /examples/1/check_dupe_status may timeout, and it may return with the session cookie. But guess what? Since :duplicate was never set in that session cookie, it will overwrite the one in your browser, and :duplicate will be blank in the next request you send.
Hopefully I expressed that clearly. Basically you're hitting a race condition in your cookies, and that's very difficult to overcome if you're sending concurrent requests from the same browser.
The best way to handle this would be to write your duplicate 0,1,2, etc. values to some database table. Your long-polling request could then just read out of the database. A little less efficient perhaps, but of course it has the advantage that it could work.

Related

Can I safely tell Rails to remember the result of an expensive instance method?

My current_user has a has_privilege? method which calls the database to check whether this user has a certain admin privilege or a superceding one (system is similar to Wordpress privileges). As has_privilege? gets called several times per page, can I tell Rails to remember the result for as long as the instance exists, using the method explained at How can I save the value of an instance method?, or would this make the app insecure? It's a bad idea to put this information into the session variable or cache, right?
as long as the instance exists
It depends what you mean by that.
Rails treat each HTTP requests in a separated process so you can cache the has_privilege? method output for the current request (so calling many times the method will query the DB only the first time), but on the next request, it will be executed again and then cached (and actually you want that as you may change the permissions and don't want the user to keep the old permissions).
In order to do so you can use this simple trick:
class User < ActiveRecord
def has_privilege?
#has_privilege ||= begin
# You code here. The returned value will be cached in #has_privilege.
end
end
end
So the first time the method is called, #has_privilege is nil for your instance of the user, so the || will its right side part which is the = begin ... end.
Excepted if the code return nil, the value will be assigned to #has_privilege.
On the next call, #has_privilege is no more nil, therefore || will no trigger its right side part and return immediately.
I need a cross request cache
In this case, you have to go with JWT and save the value in the token.
JWT is a token generated and signed by the server, and it must be sent back in to each requests, otherwise the server will reject it.
As only the server can sign the token, in the case the user tries to change it, the server will reject and sign out the user.
You can read more on their website, it's quite simple to use.

Rails 4 session is not updated between requests

Let's say we have a very simple controller's show action:
def show
session[:shown_counter] ||= 0
session[:shown_counter] += 1
puts "session id: #{session.id}"
puts "shown #{session[:shown_counter]} times"
end
When I hit the url that invokes this action from my browser(chrome), it works as expected and I see the shown_counter increment. But I have a flash that makes some (5-10) requests to the correct URL, invokes my show action, but I can see that the counter is not increased although the session id is always the same.
When I reload the flash, I'll see that the counter increased by 1, but get "stuck" until I refresh the flash again or I make a "regular" request (with the browser).
How does it work? Why the counter doesn't grow with the flash requests?
I believe your Flash app needs to get the session cookie as provided by your Rails app server in a Set-Cookie header, store the data provided, and pass this key-value pair as a Cookie header on its next request.
(Save the _myapp_session=Xyz(etc.) part from each response, in other words, and pass it as Cookie on each subsequent request.)
Otherwise, Rails won't know to which session your request belongs.
After talking to the flash creator, I think I got an answer.
The requests are asynchronous, that means a request won't wait for a response => the cookie won't be updated => the session (that uses the cookie store) won't be updated as well.
I guess this is the scenario, please tell if it makes sense to you and if you agree / disagree.

Detecting Rails 4 Session cookie tampering

Background
I'm an experienced web developer (mostly with Python and CherryPy) who has implemented secure session management from scratch before, and is now learning Rails. I'm investigating the behavior of Rails sessions as exposed by the session object that is available in the ActionController instance and view contexts.
Question/Problem
I have read that the default implementation of sessions in Rails 4 uses an encrypted and tamper-proof cookie. Cool, I guess that means I can use it to hold a user ID for user sessions without worrying about session forging (tamper-proof) or anyone being able to find out what their ID is (encrypted). I wanted to test this and see what rails would do if the session cookie was altered.
So, I went and altered the content of the session cookie attribute using a browser add-on, and when I reload the page with the new cookie value, Rails just happily gives me different new values for session_id and _csrf_token.
What happened to session cookie integrity!?
Shouldn't rails detect (via HMAC signature) that the cookie was altered and then tell me about it somehow?
I'm terrified that I'm missing something obscenely obvious, but I've been having no luck searching for an answer on the web, and the source code isn't giving it up easily either (I'm new to ruby). Thanks in advance.
The Experiment
I created a new app and generated a controller with an index action:
$ rails new my_app
$ cd my_app; rails g controller home index
Then I added these two lines to the app/views/layouts/application.html.erb file:
<%= session.keys %><br/>
<%= session.values %>
I started up the dev server and navigated my browser to "localhost:3000/home/index". As expected, the page has the following lines at the bottom:
["session_id", "_csrf_token"]
["8c1558cabe6c86cfb37d6191f2e03bf8", "S8i8/++8t6v8W8RMeyvnNu3Pjvj+KkMo2UEcm1oVVZg="]
Reloading the page gives me the same values, although the app sets a new value of the _my_app_session cookie attribute every time. That seems weird to me, but I'm getting the same session hash values, so I guess it's cool.
Then, I used a cookie editing add-on for Chrome to alter the value of the _my_app_session cookie attribute (replacing the first character of the attribute value). Reloading the page shows completely different values without anything happening. WAT?
I can't claim a really thorough understanding of the code here. But I can tell you this much:
I followed your steps exactly (using Ruby 2.0.0-p247 & Rails 4.0), with one exception -- I also added the 'byebug' gem to my Gemfile and inserted a debugging breakpoint in the HomeController#index action.
From the byebug console, at that breakpoint, I could see the unedited session cookie via:
(byebug) cookies["_my_app_session"]
"cmtWeEc3VG5hZ1BzUzRadW5ETTRSaytIQldiaTMyM0NtTU14c2RrcVVueWRQbncxTnJzVDk3OWU3N21PWWNzb1IrZDUxckdMNmZ0cGl3Mk0wUGUxU1ZWN3BmekFVQTFxNk55OTRwZStJSmtJZVkzVmlVaUI2c2c5cDRDWVVMZ0lJcENmWStESjhzRU81MHFhRTN4VlNWRlJKYTU3aFVLUDR5Y1lSVkplS0J1Wko3R2IxdkVYS3IxTHA2eC9kOW56LS1IbXlmelRlSWxiaG02Q3N2L0tUWHN3PT0=--b37c705a525ab2fb14feb5f2edf86d3ae1ab03c5"
And I could see the actual encrypted values with
(byebug) cookies.encrypted["_my_app_session"]
{"session_id"=>"13a95fb545a1e3a2d4e9b4c22debc260", "_csrf_token"=>"FXb8pZgmoK0ui0qCW8W75t3sN2KLRpkiFBmLbHSfnhc="}
Now, I edit the cookie by changing the first letter to "A" and refresh the page:
(byebug) cookies["_my_app_session"]
"AmtWeEc3VG5hZ1BzUzRadW5ETTRSaytIQldiaTMyM0NtTU14c2RrcVVueWRQbncxTnJzVDk3OWU3N21PWWNzb1IrZDUxckdMNmZ0cGl3Mk0wUGUxU1ZWN3BmekFVQTFxNk55OTRwZStJSmtJZVkzVmlVaUI2c2c5cDRDWVVMZ0lJcENmWStESjhzRU81MHFhRTN4VlNWRlJKYTU3aFVLUDR5Y1lSVkplS0J1Wko3R2IxdkVYS3IxTHA2eC9kOW56LS1IbXlmelRlSWxiaG02Q3N2L0tUWHN3PT0=--b37c705a525ab2fb14feb5f2edf86d3ae1ab03c5"
(byebug) cookies.encrypted["_my_app_session"]
nil
So the session is nil at this point in the request:
(byebug) session
#<ActionDispatch::Request::Session:0x7ff41ace4bc0 not yet loaded>
I can force loading the session with
(byebug) session.send(:load!)
and when I do, I see that the resulting session id is
"f6be13fd646962de676985ec9bb4a8d3"
and sure enough, when I let the request finish, that's what I see in the view:
["session_id", "_csrf_token"] ["f6be13fd646962de676985ec9bb4a8d3", "qJ/aHzovZYpbrelGpRFec/cNlJyWjonXDoOMlDHbWzg="]
I also have a new cookie value now, unrelated to the one I edited.
So from this I think we can conclude is that what's happening is that since the cookie signature could not be verified, the session was nullified and regenerated. I now have a new session, with a different csrf_token.
The relevant code appears at actionpack/lib/action_dispatch/middleware/cookies.rb:460-464, in the EncryptedCookieJar class:
def decrypt_and_verify(encrypted_message)
#encryptor.decrypt_and_verify(encrypted_message)
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
nil
end
Rather than decrypting a message with an invalid signature, we just treat it as nil. So the unverifiable cookie that stores the session id and csrf token is not used to load the session, and anything that depends on the values in the cookie will fail.
So why didn't we get an error rather than just a new session? That's because we didn't try anything that depends on the encrypted values. In particular, although we have
protect_from_forgery with: :exception
(as opposed to :null_session) in ApplicationController, Rails does not verify the csrf token on GET or HEAD requests -- it relies on the developer to implement these actions according to spec, so that they're non-destructive. If you tried the same thing on a POST request, you'd get an ActionController::InvalidAuthenticityToken error (as you can easily verify for yourself).

Is form data passed through session or params?

I am not understanding the difference between session and params in the following application.
A user submits a new movie form. How would the associated controller access the title of the movie?
session['title']
session.title
params['title']
params.title
All of the above
Based on the StackOverflow answer at Difference between session and params in Controller class:
params live in the url or in the post body of a form, so it vanishes as soon as the query is made.
Session persists between multiple requests (the info are often stored in cookies but this depends on your configuration).
To be short:
params: one request only (creation of one object, access to one particular page)
session: info to be persisted (cart, logged user..)
I chose (1) session ['title'] on the quiz and got the answer wrong. I chose (1) because I thought it involved accessing information that had to persist.
Am I misinterpreting the question and maybe this falls more under "one request only" so the answer should be (3) params['title']?
To attempt to answer your question in the context of this quiz instead of just in the context of code, consider where it says that:
params live in the url or in the post body of a form, so it vanishes
as soon as the query is made.
Now consider that the question itself says:
A user submits a new movie form. How would the associated controller
access the title of the movie?
So the question is saying that the user interacts by filling out a form which is then posted to the server. This is exactly the "post body of a form" mentioned in your notes.
So the correct answer is 3) params['title'].
Now, once this data is accessed, it CAN BE PLACED into the session, but that's for the developer to do or decide, and that's not really within the scope of what's being talked about here.
You also know that, in the context of this question, the session is not what's used because your question only refers to a single request: the sending of the form. If your question referred to data sent from the form that had to persist over a few more requests (such as a multi-page form), then the session might come into play.
Before access data you need to put it. In default rails generated forms all data is send in one request in params. If you have form that points to User#create action and have 'name' input, you will have params['name'] in User#create action.
Session is another thing. It's hard to find sessions in standart generated rails scaffold. You can access session as a hash. session['name'] will store name between requests. Read more
So, params are are generated for one request - to transfer data from user to server, sessions are used not for transfer data, but for store it.

Rails 3 Cookie Based Sessions Question

With Rails 3, the default session storage mechanism is cookie_store. I assume that this means that the contents within the session hash are serialized, encoded and stored within a cookie in the browser? Does this mean that nothing (or very little) of the session is stored in the server?
I've had a few issues where I had a cookie overflow error and I'm assuming because I kept on adding to my user instance (which was also linked/fetched from the cookie).
u = session[:user]
u.add_this lots_of_data
so eventually I got a cookie overflow error.
Am I correct about this? Are sessions fully stored within cookies in Rails 3 (by default)?
Yes, if you use the cookie store, the session data is stored in the cookie. If you'd like to store it on the server, you will need to use another session store.
However, if you are storing model objects or "lots of data" in the session, you are most likely doing it wrong in the first place. Your data should go to the database, and the session should only contain as much information as you need to retrieve it.
In you case, this would mean to store the user id int he session, and load the user from the db in a before_filter.
Yes, you are right. The problem might come up if you keep on adding data to session.
But there are some other things that affect it.
Once, I ended up with CookieOverflow error, and the reason was the flash[:notice] messages.
If you use flash[:notice] = "message" and then redirect, the text "message" will be stored in the cookie. If the size of the text u pass is more than 4KBs, you get "CookieOverflow" error

Resources