cancan - dealing with a HUGE ability.rb file - ruby-on-rails

I have a significant Roles aspect to my site and my ability.rb file has grown to a behemoth. It now conducts 25 queries to check 'role' records every time load_and_authorize_resource is called. Most of these queries are irrelevant because they govern lots of different permissions other than those relevant to the controller in use.
Let's say if on the companies controller, I only want to check permissions for Company and Project, and I want to ignore Task, Employee, and Assignment.
How can I send parameters to ability.rb so it only perform the relevant queries each time?

Hmm, one suggestion could be, that you could define your own ability models for Company and Project.
Something that quickly jumps into mind is the same concept that is being used here.
Perhaps you can override the current_ability method in the Company and Project controllers respetively. Something like:
def current_ability
#current_ability ||= CompanyAbility.new(current_user)
end
And in your CompanyAbility model, that is where you would define your custom permission settings solely for Company.
Hope that helps.

Related

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

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.

Dynamic roles and permissions system in Rails app

I need to create roles based permissions systems in my Rails app. I would be totally happy with CanCan, but the main problem - it has to be dynamic, so that Admin has to be able to assign permissions and to create new roles. The permissions can be simple controller/action restrictions, and can be data related, for example some users can edit only their own profiles, and some of them can edit the profiles of all the users in the particular group. And it would be really nice to allow Admin to create new permissions.
What I'm thinking about is to store in db a controller/action, and some data related restrictions (I'm really confused here about the way to define them). So could you please give me some advice, what would be the best way to organize permissions?
Any thoughts are much appreciated
If you like CanCan, then I think is best to use it. Here is a short tutorial about storing abilities in database so non-programmers can update them:
https://github.com/ryanb/cancan/wiki/Abilities-in-Database
If you really, really want to implement such system yourself. Depending on your needs, I will suggest for you to implement it as simple as possible.
In the case you need only users to have access to modules(certain controllers). You can do:
Store all users permissions in just like serialized fields -> http://apidock.com/rails/ActiveRecord/Base/serialize/class
class User
serialize :permissions, Array
def access_to?(module)
permissions.include? module.to_s
end
end
some check when setting this field would be nice.
Just make a check on top of every controller if the current user have access to this controller(section)
class ApplicationController
private
def self.require_access_to(module)
before_filter do |c|
unless c.send(:current_user).try :access_to?(module)
c.send :render_no_presmissions_page
end
end
end
end
class AdminNewsController
require_access_to :news
end
Of course this is just a start position, from where you can easily evolve.
[EDIT The link given by #RadoslavStankov is a better answer than this.]
You can do it with CanCan easily. Just make a model for permissions (which has_and_belongs_to_many :users), and in your Ability#initialize load the permissions appropriate to the user from the model and say that the user can do them. Then make appropriate user interface for administrating that model.
Something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
user.permissions.each do |permission|
can permission.symbol, permission.scope
end
user.prohibitions.each do |permission|
cannot permission.symbol, permission.scope
end
end
end
where scope returned something like :all for nil scope, or Object.const_get(#scope_name) otherwise... Play with it. Anyway, it's doable.
More complex CanCan things are likely to be more complex (duh) - for example conditional permissions - but that would be a bitch to administer as well, so I think this should be enough for most applications.

Model-level authorization in 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

Resources