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.
Related
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.
In one of my controllers I have this method:
def method_name
if current_user
#model = Model.find(params[:id])
if #model.destroy
flash.alert = 'Model deleted successfully'
redirect_to models_path
end
end
end
I check if there is a current_user assigned by devise before giving the ability for the #model to be deleted. Is this safe and sufficient in terms of security?
What I really do is just checking if current_user exists. So is there a way that somebody can "trick" the system that current_user does exist and as a result be able to trigger the commands included in the method?
You will get a spectrum of answers in this. But if you want the user to be logged in then just do this at the top of your controller:
before_filter :authenticate_user!
That is provided by devise and ensures that there is a logged in user before allowing any controller actions.
If you have simple authorization then yes, most likely though you are going to want to make sure that the user has the authorization to delete the object. You can do that several ways. My favorite one right now is the Pundit gem.
You could also just check that the user owns the object in order to be able to delete it. That code would look something like this
#model = Model.find(params[:id)
if current_user.id == #model.user_id
# Rest of your destroy code
end
I'm using devise with my rails 4 app to handle the authentication, and no problems there.
However, I want to make sure a logged in user can only view / edit (via the show and update actions) the items that his user owns (that are linked to his user_id).
I think I could hack something to make this all work by checking the current_user.id, but many users in Stackoverflow and other places say to use cancan-- however it appears cancan is dead and gone, and there's a replacement called cancancan, which may be ok, but I don't know.
Is there a standard way to do this in Rails 4, or is the best route to still use a third party gem like cancancan? Is there a better gem?
I've been using Pundit instead of Cancan for the last few projects I've done. It is lightweight, flexible and easy to use. Here's the link: https://github.com/elabs/pundit
In regards to your question, you will create policies for each model. For each action you define a method. It's super simple and explained on the link I've attached. Here as an example you have update in your model (models/post.rb):
def update
#post = Post.find(params[:id])
authorize #post
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
Call authorize to define permissions.
In your policies/post.rb:
class PostPolicy < Struct.new(:user, :post)
def update?
user.admin? or not post.published?
end
end
That returns true or false. In your case if you want to check if the user is a owner you can place the following if statement:
if user.admin? || user.owner_of?(post)
You get the idea. You can also define scopes, etc.
I don't think there's a standard, per se, but rather it's based on what you need. For Rails 4, cancancan brings a lot to the table and is built off of a gem that has been used regularly by the Rails community.
The only other alternatives I'm familiar with are protector and pundit - maybe check those out.
However, if cancancan and protector don't fit your needs, you could always roll your own authorization solution, but to me, why reinvent the wheel if cancancan will satisfy your needs.
I'd recommend Action Access, it's much simpler and straightforward. It boils down to this:
class ArticlesController < ApplicationController
let :user, :all
let :guest, [:show, :index]
# ...
def edit
not_authorized! unless #article.user == current_user
# ...
end
# ...
end
First of this will automatically lock the controller, allowing only users to access every action, guests can only show or index articles. Then not_authorized! will reject and redirect with an alert any user other than the owner of the article.
What's good about this is that it makes controllers to be self contained, everything related to the controller is within the controller. This also makes it very modular and avoids leaving forgotten trash anywhere else when you refactor.
It's completely independent of the authentication system (so no problem with Devise) but it bundles a set of handy model additions that allow to do things like:
<% if current_user.can? :edit, :article %>
<%= link_to 'Edit article', edit_article_path(#article) %>
<% end %>
Here :article refers to ArticlesController, so the link will only be displayed if the current user is authorized to access the edit action in ArticlesController. It supports namespaces too.
You can lock controllers by default, customize the redirection path and the alert message, etc. Checkout the documentation for more.
I want to design an API in Rails that requires actions like Create, Update and Delete to be readonly for certain controllers, and open to the public for others (eg, comments on an article should be open but editing that article should require API authentication)
I know how to do the authentication part, what I don't know how to do is the "read only" part or the "you have permission to create a comment but not delete it" part.
Does any one have any resources, tips, tricks or github repositories that do this or something similar to this?
You are needing to do authorization. Look at Pundit for a scalable solution https://github.com/elabs/pundit
I had an app for a while that only needed a little bit of control as there were only a few methods on 2 controllers that were limited. For those i just created a before_filter and method to control the authorization.
The code below would allow everyone to do index and only allow users with a role attribute that has a value of "admin" to do any other action in the controller. You can also opt to raise an unauthorized error or raise an error message instead of redirecting. There are articles (probably books) written on the security side of the house for whether you should give users notice if they are not authorized to do something (which means they can infer that there is something there that someone can do at the uri)
SomeController < ApplicationController
before_filter check_authorized, except [:index]
def index
....stuff that everyone can do
end
def delete
....stuff only admin can do
end
private
def check_authorized
redirect_to root_path unless current_user.admin?
end
end
Of course you will need devise or a current_user method and a method on user that checks admin
class User < ActiveRecord::Base
def admin?
if self.role == "admin"
true
else
false
end
end
end
I was attempting to start a test to confirm that a user can only modify an object if current_user.id and model.user_id match.
I feel like this is a validation from the model. So I might write something like:
class UserLocked < ActiveModel::Validator
def validate(record)
unless record.user_id == current_user.id
record.errors[:name] << "Sorry you cannot modify something that is not your's"
end
end
end
Which might be ok... (is there a centralized place I can put this? do I need to do anything special to reference it then?)
Writing a test for that isn't too bad either; however, I also need to prevent the controller from displaying the form to edit form. Should I be creating a separate view or just make it part of the edit page? How can I write a test for this for this in rspec...
I might be over thinking this, but I am trying to figure out what everyone else is doing. An example would be great! I've done this before in other languages/frameworks, but I am trying to "do things the right way."
Thanks!
Authorization belongs in the controller and not in the model. So you could implement a before_filter like this:
class UsersController < ApplicationController
before_filter :correct_user, only: [:edit, :update]
...
private
def correct_user
#user = User.find(params[:id])
redirect_to root_path unless current_user? #user
end
end
Of course you would need some sort a method to detect who the current user is.
You could test this with a request spec, using RSpec & Capybara. The logic is simple: you login with a user and expect that when trying to edit the info of another user you get an error message displayed. Otherwise the relevant form fields should be displayed.
For an example see http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#code:edit_update_wrong_user_tests