Model-level authorization in Rails - ruby-on-rails

I want to implement authorization in my Rails application on a model level (not controller), in a similar way that validation on models is done. What is the best way to do this?
If it is implemented in the models itself, the main problem is that the models don't have access to the current user. I've seen solutions like: Thread.current[:user_id] = session[:user_id], but that doesn't seem like a good idea.
I've seen a different approach where variants of the methods like create, find and new are created, accepting an additional parameter for the current user.
Another approach would be to implement all the methods in the User/role class, so for example user.posts.create or user.readable_posts.find would be used instead of Post.create or Post.find.
Which of these approaches would be suggested? Are there any better ways to implement the authorization? Are there any plugins that makes this easier? I need an approach that scales well for multiple roles and models.

I would recommend you to look at declarative authorization. It works with both models and controllers.
The way it do what you are asking is having a before_filter in the applicationController that sets Authorization.current_user = current_user where Authorization is a module.
I think that approach is the best one, it keeps the models clean and you don't have to remember to include the user everywhere, but can filter it in the models callback functions instead.

Why would you do this? Isn't controller level identification enough?
if #user.is_able_to_do_this?
do_this
else
blah!
end
... and in your model
def is_able_to_do_this
if self.rights > Constant::Admin_level_whatever
return true
end
return false
end

Related

How to you only allow admins to set admin privileges?

If I set up a basic User model in Rails, and give it an is_admin:boolean, default: false attribute, what's the best way to prevent non-admin users from changing this?
It seems like the sort of logic that should really go into the model, but what's the best way to construct it? ActiveRecord callback functions?
I know I could put this into the controller's #update method, but that doesn't seem to match MVC best practices. (And seems less portable.)
What's the best approach here?
Denying access with a redirect is a job perfect for a controller, so doing it in private before_filter method would be sufficient and justified in my opinion.
Why do you say that the logic should go in the model?
It depends on your implementation. If the logic is dependent on the current_user (i.e. can a current admin set another user to be an admin as well), then the logic should go in the controller. Since you tagged the question as Rails 4, the logic will go in the permitted params in your controller:
def user_params
if current_user.is_admin
params.require(:user).permit(...your attributes here with is_admin...)
else
params.require(:user).permit(...your attributes here without is_admin...)
end
end
I would set admin privileges only in the console like that:
a = User.find_by(email: "user#example.com")
a.toggle!(:admin)
You would prevent somebody cracking into your site to gain admin privileges.

Determining if an association between two models has been created within one model?

I've been researching this topic for a day now, and I haven't seen a solution that could adequately allow this. I would have even give up and said that it's not possible, but I see large companies achieving this in their apps!
I need to know if the current user is following another user. I need to know this many times (for the current_user) without polling the DB again
The solution needs to be friendly for reuse. A solution (that's not friendly for reuse) I had come up with is as follows:
module UsersHelper
def is_following?(user)
return false if current_user == user
user.is_following = Relationship.find_by(followed_id: user.id, follower_id: current_user.id)
end
end
is_following?(#user) can now be called in any controller
Notice that I'm able to access current_user because this helper method will be called in a controller, not a model.
This implementation is cool for one model, maybe two... except I need to do this in almost every many-many relationship I have in the app. So it has to be scalable.
I'm referencing exactly what Twitter does with their following.

Routing for Multiple User Roles

I have a user model that differentiates into 6 roles, and I am defining different variables and directing them to different view files (similar format, but different tables of information), but within the same controller action, because they all have similar pages "overview", "profile", "messages", etc.
Now the controller is really messy, and has multiple if/else statements. I believe I should be changing the routes so that each user has its own controller, eliminating the use of the if/else monstrosity that currently invades the controller.
def index
if current_user.admin?
....
end
if current_user.moderator?
....
end
end
Question: How do I perform the routing such that url will be
www.website.com/1/schedule, where 1 = current_user.id, while having different view files rendered from the different controllers?
I am thinking of doing a AdminController and a ModeratorController to handle this, but am not sure how to do the routing, or if indeed this is the best way to do it. Thanks for advice in advance!
I think you are doing it the wrong way , If you have similar pages for different role then I think you are accessing same model for handling different roles.
If this is the case then you should use Gem like Cancan (authorization library for Ruby on Rails).
To address your second concern, I don't think this would be the best approach. Try this - move your logic away from multiple controllers, and keep the logic in the ApplicationController. I do not think that you should separate the different roles to controllers. Instead, look to your User model and put in a method that checks the privilege level for the different users. You could create methods in your User model that you could call in your controllers to see if the user is allowed access to the action. before_action would be recommended here.
If you decide to keep multiple controllers, I recommend a gem like Authority. Makes it much easier to keep track of different privileges, even across different controllers - I think routing is addressed as well: https://github.com/nathanl/authority

Rails global variable

Im using bootstrap & rails and have a user model and post model..users create posts (collections)..
with bootstrap in the navbar i want the user to be able to click a dropdown which displays the name's of their posts..i did this on one controller with a private method and a before_action but i don't want to do this for all the controllers and it didn't work for the application controller...
is there a better way to do this??
I was doing this
def list
#user = User.find_by_username(params[:id])
#collections = #user.collections
end
and a
before_action :list
at the top of the controller
What's the most semantic way to accomplish this??
If you could move both to your application controller, then it would be available to any controller. More generally, I'm not sure if this is the best approach to solve your problem.
These tips might also be useful.
Are you using devise? Or some other authentication plugin? If so you're likely going to have a current_user helper. This would allow you to simply do #collections = current_user.collections
To the extent possible, I recommend using more descriptive names for your actions and parameters. def fetch_list_collections might be a better name or instead of passing a param named id, perhaps your param should be named username. These naming conventions become extremely important both for others who might look at your code as well as for yourself if you return to it and are trying to remember what you wrote N months ago.
Your list action is generating a N+1 queries. Meaning that you're hitting the database multiple times when you should do so just once. See the rails guide on this. You might also look at ways to avoid this w/ devise. Devise is pretty well documented and I'll bet there is something in the wiki discussing this.
You may want to consider limiting when you call this action - at a minimum - a post request to an update action? What about before they've logged in? current_user might be nil and you'd have an error attempting to call a collections method on nil.
Take your time learning this stuff. You don't have to learn it all at once, but I thought the above might be helpful.
I got it to work with this in the application controller
before_action :list
private
def list
#collections = current_user.collections
end
thanks #arieljuod

Authorization for actions (not models!) with roles in rails

If I've got a simple rails user model that has an array of roles, is it sufficient enough to control access to actions by simply checking the model's role attribute for that role and blocking/proceeding accordingly?
Is there an advanced system that I ought to leverage due to unforeseen complexity?
Important: I'm not looking to authorize users/roles to models (I am already aware of CanCan). I'm looking to do security at the controller level so that I can break out the functionality in finer detail.
Even more important: Seriously, I'm not necessarily asking about CanCan, please read the question carefully and pay attention! :)
Question 1: YES, Question 2: NO.
I just keep this simple
If you check the models attribute in the controller, the controller will restrict all users that do not have this attribute set.
ex:
def create
#user.find(params[:user_id])
if #user.admin?
#post.new(params[:post])
#post.create!
end
end
make a method in the user model
def admin?
role == "Admin"
end
You should make better code than this. To much logic in the controller, but this will keep all, except admins out.

Resources