I want to disable sessions completely for a controller action, because I want this single controller action (it's an autocomplete action on thousands of values, so speed matters) to be blazingly fast.
I tried using session_off, but it just sets the session variable to nil, an still looks up the users session in the database.
Is it possible to completely disable the Rails::SessionStore middleware, but only for a single controller action or URL?
I am running rails 3.2.17.
The answer is: handle this endpoint in a Rack middleware of your own, and insert it into the stack as early as possible!
You can achieve this in config/routes.rb just by routing to the middleware object:
match 'my_autocomplete_endpoint', to: AutocompleteMiddleware
then just return a response from the middleware and don't go up the stack.
You can put this wherever you want in the stack in config/application.rb with:
config.middleware.insert_before(SomeOtherMiddleware, AutocompleteMiddleware)
e.g., perhaps insert it before Rails::SessionStore.
Rails 5+ solution (maybe before, not sure when this became available).
Add this to your controller. You can specify which actions should not touch/update the session using the only: option.
after_action -> { request.session_options[:skip] = true }, only: :my_action_name
This will make the response not include the set_cookie response header. I found this particularly useful when dealing with a race condition in multiple AJAX requests, whereas one contained a very important session (cookie data) update and the other the session was not used, but Rails still sent back an updated cookie for the session. The race condition could cause the updated session data from the important action to be overwritten from the one I didn't care about.
Related
Is there a way to not save session ids & the session cookie in web2py on a per-controller level? global_settings.web2py_disable_session = True will do it for the whole site, but I want some pages to retain sessions.
If you don't need to use the parameter-based rewrite system, you can disable sessions based on routing using the pattern-based rewrite system. In the routes.py file, you would do something like this:
routes_in = [
('/myapp/default/$anything', '/myapp/default/$anything',
dict(web2py_disable_session=True))
]
The optional third element of a routes_in tuple is a dictionary, which will be used to update request.env. The above will add web2py_disable_session=True to request.env only for routes starting with /myapp/default/ (setting global_settings.web2py_disable_session=True, on the other hand, will add web2py_disable_session=True to request.env for all requests).
Alternatively, you can simply call session.forget(response) in any controller or action that does not need the session (or conditionally in a model file depending on the requested path). If no session cookie or file have yet been created, this will prevent their creation. Although simpler, this method is slightly less efficient than the above, as it will still result in the session initialization code running on every request.
One final alternative would be to create a custom WSGI application function in the WSGI handler file that conditionally adds web2py_disable_session=True to the WSGI environment dictionary depending on the requested route. Then pass the modified environment dictionary to gluon.main.wsgibase.
I am using Ruby on Rails 4.1.1 and I am thinking to accept parameters (through URL query strings) that are passed directly to the url_for method, this way:
# URL in the browser
http://www.myapp.com?redirect_to[controller]=users&redirect_to[action]=show&redirect_to[id]=1
# Controller
...
redirect_to url_for(params[:redirect_to].merge(:only_path => true))
Adopting the above approach users can be redirected after performing an action. However, I think people can enter arbitraryparams that can lead to security issues...
Is it safe to accept URL parameters for populating the url_for method? What are pitfalls? What can happen in the worst case?
By logging params during requests to my application I noted Rails adds always :controller and action parameters. Maybe that confirms url_for can be used the above way since it is protected internally and works as-like Rails is intended to.
This it is safe internally as Ruby On Rails will only be issuing a HTTP redirect response.
As you are using only_path this will protect you from an Open redirect vulnerability. This is where an email is sent by an attacker containing a link in the following format (say your site is example.com).
https://example.com?foo=bar&bar=foo&redirect=http://evil.com
As the user checks the URL and sees it is on the example.com domain they beleive it is safe so click the link. However, if there's an open redirect then the user ends up on evil.com which could ask for their example.com password without the user noticing.
Redirecting to a relative path only on your site fixes any vulnerability.
In your case you are giving users control of your controller, action and parameters. As long as your GET methods are safe (i.e. no side-effects), an attacker could not use this by creating a crafted link that the user opens.
In summary, from the information provided I don't see any risk from phishing URLs to your application.
Rails redirect_to sets the HTTP status code to 302 Found which tells the browser to GET the new path as you defined it by url_for. GET is a considered a safe method in contrast to
... methods such as POST, PUT, DELETE and PATCH [which] are intended for
actions that may cause side effects either on the server, or external
side effects ...
The only problem would have been if someone could gain access to methods such as create and destroy. Since these methods use HTTP methods other than GET (respectively POST and DELETE) it should be no problem.
Another danger here is if you go beyond CRUD methods of REST and have a custom method which responses to GET and changes the database state:
routes.rb
resources something do
member do
get :my_action
end
end
SomethingController
def my_action
# delte some records
end
For future ref:
Rails has a number of security measurements which may also interest you.
It's not exactly an answer, just wanted to point out that you shouldn't use something like
url_for(params)
because one could pass host and port as params and thus the url could lead to another site and it can get worse if it gets cached or something.
Don't know if it threatens anything, but hey, it's worth pointing out
I have Resque queue that processes a particular model to do some post processing. Once this model is updated I would like to expire the cached view action.
I have a Sweeper setup and working, but it only observes changes made to the models in Controller Actions.
I know it is not very MVC to be expiring cached items from my model, but all my post processing logic is contained in my models.
Ideally I would like to not double up on my cache busting code, so if I could get my existing sweeper to watch model changes that would be ideal.
Alternatively I would settle for expriing the action cache from inside my model OR a model observer.
p.s.: I can expire cached fragments from within a model observer I have setup, but not actions.
I'd really like to know what the best practice is here. I'm sure I am not the only one who has this requirment.
Thanks.
I'm wondering if something like this is the way to go:
http://dev.mensfeld.pl/2011/07/rails-wykorzystywanie-sweeperow-poza-kontrolerami-na-samych-modelach/
Since I wasn't able to add my comment on that site you linked to. I'll put it here:
In Rails 3.1.3, if you instantiate the controller and then try calling expire_fragment, you'll get errors about trying to call host on NilClass.
After some experimenting, I remembered that functional tests can instantiate your controller. So I changed the instantiation code to:
#controller ||= ApplicationController.new
if #controller.request.nil?
#controller.request = ActionDispatch::TestRequest.new
end
This seems to work, even in production, even using rails console.
In the end I came up with the following solution:
Added my existing sweeper to the list of object observers in application.rb:
config.active_record.observers = :app_sweeper
Added code to the sweeper methods to instantiate the #controller object if it was missing in the sweeper (which is what happens when coming via a object instead of a controller).
#controller ||= ActionController::Base.new
I could then use the expire_fragment method to expire the cached action, with the following tidbit.
expire_fragment("#{ActionMailer::Base.default_url_options[:host]}/items/#{item.id}")
That said, mj1531's answer might prove to be a nicer solution if it means that I can use the expire_action method instead of faking it with the expire fragment. I will report back when I have test it out and select the best answer.
This will probably be easiest if I explain what I'm trying to do. I have three actions in my Rails app controller, each rendering a different page. The page-render is done with a single partial which uses variables that were set in the controller action code. For example, each page has a list on it, but on one page the list is sortable. Up to now I've been handling this by setting a #sortable flag to true or false in the code for my actions.
This works fine when an action is initially run. The problem is that I have AJAX stuff going on (e.g. adding a new element to the list) and when this happens, I need to know the value of the #sortable variable again. It seems to have gone, even though I'm still technically on the same page. What I want is a variable store that is linked to the page you are on.
What are your recommendations for doing this? (Storing it in the Rails session hash seems like overkill - too much chance that the wrong value will get left in there by some yet-to-be-implemented action.)
Ben
In rails I've only managed to set page scoped variables for initial setup too.
I think the only solution would be to pass the sortable flag from the page on the ajax request. You can store it either with a javascript variable, in a hidden field, custom attribute on your list or anyway you wish and then in the ajax you simply add that to the request so you can treat that on the server side persistently.
Why do you don't want use session? As for me before_filter works fine for such tasks
in ApplicationController
before_filter :init_actions
def init_actions
session[:action] = action_name
session[:controller] = controller_name
end
Say I want to store some variable in my controller. I want to initialize it in one action, increment it in another, and read it in yet another. Just declaring this variable with #foo doesn't work because #foo dies after the action that created it is rendered.
I do not want this variable to be stored in a model.
Is there a way to preserve this variable besides storing it in a session?
It seems like I've run into this simple problem a few times, and I want to know the best way to go about solving it.
Not really. Each call to a controller action is stateless. Nothing is available after the controller action finishes. A new controller instance is created for each request, and then discarded at the end of the request.
If you don't want to store it in the session, or database model, you don't have many options if you're wanting that variable to be specific to a particular session.
If it is global across all sessions, you could put it in a ##class_variable rather than an #instance_variable, but that can get messy once you start having multiple Rails processes (each which will have their own copy of it), or if you're running in threadsafe mode, you can end up with nasty concurrency bugs.
I guess you could look at something like memcached, but you'd still need to key that to some user_id or other session marker (unless it's global)
I too am wondering why you are against using session? If you don't like working with session directly in your actions, you could emulate a surviving #foo instance variable with filters. Something like this maybe?
class FooController < ApplicationController
before_filter :load_foo
after_filter :save_foo
private
def load_foo
#foo = session[:foo] || 0
end
def save_foo
session[:foo] = #foo
end
end
Your actions will the be able to manipulate the value through the #count instance variable and this will be automatically persisted to session.
You could make use of the built in Rails.cache mechanism to store the value but as mentioned in the first answer you'd have to key it off something like the user_id. This is a nice way to go since you can back it with different storage mechanisms.
Rails.cache.write(:foo)
# in later action
Rails.cache.read(:foo)
One other thing you could look at is the flash hash, which provides a keep method to make the flash value last more than one subsequent request.
So in action 1 you could create the value:
flash[:foo] = some_value
flash.keep(:foo)
In action 2 you can access it, and call keep again if you want it to stay alive for more subsequent actions.
flash[:foo] #use it for something
flash.keep(:foo) # keep it for another request
It's a bit of a tricky thing to do cleanly within the context of http requests.
If it's a simple count or string, I think the best solution is to store it in the session. That way it will be there if you are using multiple web servers.
Why are you against using a session for this?
Don't worry, sessions won't bite.
Also, the session is probably the best way to do this.