I've been doing a ton of reading on the way that flash[]= works in rails and understand that when you redirect_to, to use the flash[...]=..., and if you are rendering the action after assignment, then flash.now[...]...; however, my application's flash is persisting indefinitely, never actually going away during the session.
This question is a hypothetical. If I have a controller,
class MyController < ApplicationController
def index
if my_cond
flash.now[:notice] = "Your condition is true"
else
flash[:notice] = "Your condition isn't true"
redirect_to some_other_rendering_action
end
end
end
If I redirect this time, then the next time I redirect, let's say via clicking a link that connects to some action that redirects to another render, but leaves flash unmodified, then it renders the flash. The problem is this happens indefinitely, doesn't even end after one redirect.
How would you suggest troubleshooting this issue? The application is probably 35k lines, and already has been washed to follow the flash and flash.now solution recommendations posted everywhere, so where do we look?
rails 2.3.5 high volume site, apache/mongrel
ADDING INFORMATION:
The flash is persisting in the session as well.
flash: !map:ActionController::Flash::FlashHash
:notice: You must be signed in to view this page.
I suggest looking for flash.keep calls and if you have no luck and want to get rid of the behavior, add an after_filter that calls flash.discard to your ApplicationController
class ApplicationController < ActionController::Base
after_filter :discard_flash
private
def discard_flash
flash.discard
end
end
Related
I'm currently using pundit to authorise my controller methods.
after_action :verify_authorized
I am also currently use Bugsnag to catch unhandled errors.
I have an application with relatively ephemeral records used by a large audience and often I receive multiple unhandled errors where the record being looked up is no longer present (typically by users unintentionally navigating back to a browser auto-completed URL).
Currently my solution is doing this for each method in my controller:
def method_name
begin
authorize #record
rescue
if #record.nil?
redirect_to missing_record_path
else
redirect_to :back, notice: 'You are not authorised to view this record.'
end
end
# rest of code goes here
end
There's a lot of code repetition and I'm sure there's a smarter way to handle this whilst also support as an after_action command. What do you think?
I'm wondering what would be the best way to handle the following:
I have an authentication method (used as a before_action) as follows that checks if a user_id is in the session when the login page is requested. If the user_id is detected, then it redirects the user to dashboard path.
def already_validated
if session[:uid] == user.id
redirect_to dash_path
end
end
This is leading to a too many redirect errors which I understand. I can see in pry that it's just evaluating that before_action filter every-time the page loads. That's what leads to too many redirects.
My question is what is the best way to handle this type of setup. Is there a way in rails to only evaluate on the first redirect? I thought of using a temp flag to tell if the redirect happened before. That doesn't seem very elegant though. I'm sure there is an easier/better way to manage it.
Thanks for any advice you can provide.
There has to be an exception on your before_action: you don't want to call it on the dash_path. If a user enters there and is validated, it should stay there (as what the redirect would do) and if it is not validated it should just stay there (as with any other url that fails this validation process).
There is no point on checking if it is validated as the result will always be to stay on the same page.
Then in your controller you have to specify that you want an exception on the before_action:
class SomeController < ApplicationController
before_action: :already_validated, except: [:dash_action]
def is_validated_action # the method that causes the redirect
end
def dash_action # action of dash_path url
end
def already_validated
if session[:uid] == user.id
redirect_to dash_path
end
end
end
If you want some validation before the hypothetical dash_action then create a new method for it. Be sure that you don't have circular references or it will be pretty difficult to debug on the long run.
You can just tell Rails to skip the before filter in the controller that handles the dash_path:
# in the controller
skip_before_action :already_validated
Read about Filters in the Rails Guides.
I have a redirect in an ApplicationController-method and want to send a notice through:
class ApplicationController < ActionController::Base
def redirect_if_no_user
if current_user.nil?
redirect_to root_path, notice: t('errors.session_expired')
end
end
end
I'm calling redirect_if_no_user in some other controller actions.
Unfortunately I cannot see a notice until I do another reload on the homepage manually (After I already got redirected to it via the method).
Is this behaviour intended? Anyone got an idea?
From rails documentation, ( http://guides.rubyonrails.org/action_controller_overview.html#the-flash )
By default, adding values to the flash will make them available to the next request, but sometimes you may want to access those values in the same request. For example, if the create action fails to save a resource and you render the new template directly, that's not going to result in a new request, but you may still want to display a message using the flash. To do this, you can use flash.now in the same way you use the normal flash:
flash.now[:notice] = t('errors.session_expired')
redirect_to root_path
It looks like you might benefit from flash.now.
I've had issues like that which you mentioned (no flash on redirect), and I haven't solved it entirely yet. One thing I did find was using flash.now to help someone else solve their issue:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def redirect_if_no_user
if !user_signed_in? #-> assuming you're using devise
flash.now[:notice] = t('errors.session_expired')
redirect_to root_path
end
end
end
This may not work. If it doesn't, I'll delete.
I've had this problem before...
The flash notice you're seeing (eventually) is likely NOT the one you think it is. You have
redirect_to root_path
But I would wager that your root_path has it's own redirect, and that redirect means you've lost the flash notice. When you then submit on root_path you're doing another call on redirect_if_no_user and this time you see the flash message.
You can usually work around this (a redirect following a redirect losing flash message)
with the flash.keep method.
def my_root_path_action
flash.keep
...
end
... or it might be needed in one of your before_action or before_filter methods if they're redirecting before your action is called.
flash[:notice] = t('errors.session_expired')
redirect_to root_path
Try that.
I am still fairly new to rails.
In my jobs/index.html.erb file I currently have a conditional as follows:
<% if current_user.admin? %>
<td><%= link_to 'Edit', edit_job_path(job) %></td>
<%end%>
Now although the editing is forbidden on the employee side, because it clearly states "if current_user_admin" however if an employee were to login and type localhost:3000/jobs/1/edit they are somehow granted access to change the file.
How can I block the user who is NOT an admin from having the ability to do this?
Any suggestions would be greatly appreciated.
You're preventing them from seeing the link, you need to prevent them from accessing the feature. Add code to your controller that checks their status and prevents unauthorized users from doing things they shouldn't.
class JobsController
def edit
if !current_user.admin?
redirect_to '/'
return
end
// Old code here
end
end
If you are going to have a complex set of permissions, consider using a gem like Devise or Cancan. I don't have experience with those, but they seem to be the standards for authorization in rails.
If you want to forbid access to some parts of your application the right place to do it is in controller, not in views (hiding links doesn't work as you see). The common solution is to define before_filter (http://apidock.com/rails/ActionController/Filters/ClassMethods/before_filter) in your controller.
In your particular case this should work
class JobsController do
before_filter :authorize!, only: [:edit,:update]
#CRUD below
def authorize!
redirect_to(:back) unless current_user.admin?
end
end
You could also add message to flash before redirecting to let user know what's going on.
def authorize!
redirect_to(:back, alert: "YOu are not allowed to do it") unless current_user.admin?
end
also adding status: :403 would be nice in case building api (403 is knows as forbidden response)
I would suggest you to use cancancan gem to define abilities for different types of users.
Another option is to make a redirecton in edit view for unauthorized users
Don't do this in the view - it's just UI. You can put a test directly in the controller:
class JobsController < ApplicationController
def edit
raise ActionController::RoutingError.new('Not Found') unless current_user.admin?
end
def update
raise ActionController::RoutingError.new('Not Found') unless current_user.admin?
end
end
I use a 404 (not found) but you may prefer a 403 (not authorized), depending if you want the user to know there is something there that he has no access to.
You have protected the display of the link, which is good, so the benign user won't be surprised when it doesn't work. But as you point out, for the amlignant user, this is trivial to bypass. You need to add a guard in the destination controller as well.
But you really should check out some gems that provide exactly this type of security. Cancan seems to be the most widely used.
I've got a Rails 3.2.8 app using Sorcery for authentication. Sorcery provides a current_user method, pretty standard stuff.
My app has subscriptions, they work pretty much in the standard resourceful way. Here's the abridged version of the controller:
class SubscriptionsController < ApplicationController
before_filter :require_login
force_ssl
def show
#subscription = SubscriptionPresenter.new( current_user )
end
def create
handler = StripeHandler.new( current_user )
...
end
def destroy
handler = StripeHandler.new( current_user )
...
end
end
The #show action works fine, current_user loads. However, right now #create does not work, because current_user ends up being nil in that action.
So, why is current_user nil when a logged in user posts to this action? My guess is something about the way sessions work over SSL, but I don't know what I'm missing here...
I figured this out. It turns out that I was actually getting a silent exception in a 3rd-party library that I was interacting with, and that exception was causing an 'unauthorized' request which logged the user out. After patching that it turns out there was nothing wrong with my controller specifically. Thanks for the pointers, all.