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.
Related
I have a Rails 3.2.22 app running in production for +1 year which uses Devise to authenticate users.
I'm trying to implement token authentication, so I can send transactional e-mails with URL params that can log in the user automatically, using a Gem named Simple Token Authentication https://github.com/gonzalo-bulnes/simple_token_authentication
After following all the instructions, I replaced before_filter :authenticate_user! in my controllers with acts_as_token_authentication_handler_for User.
The gem has integration with and a default fallback to Devise, so devise doesn't need to be called in the controllers anymore; if the token is missing from the params (or wrong), Devise will take over.
In my tests, if I add this line to ApplicationController, everything works fine and I can log in users using the authentication_token= secret the gem generates.
But I don't need auth for ApplicationController, I need it for other controllers (like DashboardController), url being /dashboard
If I put acts_as_token_authentication_handler_for User in that controller (replacing Devise's call), I get the most bizarre of situations.
Using binding.pry, I can confirm that current_user is correctly set during the loading of the template.
But there comes a point in the template where it uses #last_emails, which is defined inside a method in ApplicationController.
Using binding.pry, I can confirm current_user is nil there.
This is the code:
class DashboardController < ApplicationController
layout 'material'
acts_as_token_authentication_handler_for User
And in ApplicationController:
class ApplicationController < ActionController::Base
layout 'omega'
before_filter :populate_last_contacts_for_menu
private
def populate_last_contacts_for_menu
if current_user
#last_contacts = Contact.where("user_id" => current_user.id).where('blocked != ? or blocked is null', true).last(10).reverse
end
end
Funny thing is: using binding.pry, like I said, I can check that current_user is defined in the template (which means sign_in was a success). It even is defined in the better errors console. But, if I go to homepage, I see that user is not logged in ...
I've looked all over the web for this: read all the issues inside the Gem's github and all posts in SO about current_user being nil, but no light at all.
My devise_for :users is not inside any scope in routes.rb and, as I said, I have many calls to current_user all over the app and this is the first time I have issues with Devise.
When you call the acts_as_token_authentication_handler_for directive in the DashboardController it declares some before_filters for the controller to authenticate a user.
But the problem is that when you inherit rails controllers, at first, filters of a parent controller are executed, then filters of a child controller.
The parent controller is ApplicationController. At the moment when it's populate_last_contacts_for_menu filter is called, the user is not authentacated, because the authenticating filters given by the acts_as_token_authentication_handler_for directive have not called yet, they are declared in the child controller.
Possible solutions:
1) Try to append the populate_last_contacts_for_menu filter:
append_before_filter :populate_last_contacts_for_menu
I am not sure it will work in your case, but you can try and find it out.
2) Call the acts_as_token_authentication_handler_for directive in the ApplicationControoler and somehow skip it for the controllers that don't need it. (I don't like this way, but it may help if the first one will not work. )
3) Move the populate_last_contacts_for_menu filter logic into helpers. I think it is the best solution. This logic doesn't belong to a controller. When requests are not 'get', this filter executes for nothing, because you don't need to render views in that case.
module ApplicationHelper
def last_contacts
#last_contacts ||= if signed_in?
Contact.where("user_id" => current_user.id).where('blocked != ? or blocked is null', true).last(10).reverse
else
[]
end
end
...
end
# View:
<% if last_contacts.present? %>
....
<% end %>
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.
One thing I noticed when working with nested resource routes in Rails is that it is technically possible for a user to visit a route where the child resource exists (and is therefore displayed correctly), but the id for the parent resource represents an object that is not actually related to the child resource.
For example, in the route users/:user_id/post/:id, the user could type in a route where :user_id represents a user who did not make the post corresponding to :id.
What would be the best way to fix this so that if the user visits an invalid URL, the server redirects the user to the correct URL?
I have already put some code in my controllers to handle this, but it's kind of awkward having to check the path in every controller action and then redirect the user to the appropriate url, especially since the URL helpers are different for every action.
(edit_user_post_path(#user, #post), new_user_post_path(#user, #post))
There has to be a better way, right?
You should have a before_filter running on all requests that makes sure the user is valid. If not, it will throw ActiveRecord::RecordNotFound and show the friendly 404 page.
Then grab the post based on the user however you need, whether in another before_filter or directly in the action. Base your post search on the user. My example below demonstrates doing this with another before_filter.
before_filter :find_user_by_user_id
before_filter :find_post
def show
# Use #post variable here however you need
end
private
def find_user_by_user_id
#user = User.find(params[:user_id])
end
def find_post
# This assumes you have an association set up as needed
#post = #user.posts.where(id: params[:id]).first
if #post.nil?
# Do whatever you need here
end
end
First of all you should know that the error wich is raised by ROR will display the message 'Sorry but the page you are looking for does not exist' on a production environment.
Therefor I would not be concerned about that. if you want to 'capture' the failure and quickly redirect to a safe area you might be interested in using the rescue method.
http://www.simonecarletti.com/blog/2009/12/inside-ruby-on-rails-rescuable-and-rescue_from/
have fun
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