CanCan Permissions - Do they have to be explicit? - ruby-on-rails

I'm new to RoR and am utilizing CanCan for authorization. I'm wondering if I have to be explicit with every single ability that a user may have.
I'm probably not being clear enough, so I'll post some code to help illustrate my question.
if user.is_admin?
can :manage, all
end
if user.is_director?
can :update, Camp
end
In this case, would the director only be able to update a camp? Or would I have to denote what he/she specifically cannot do as well?
Thanks in advance.

If you added check_authorization to your ApplicationController, then yes, it will default to be locked down for all controller actions, unless specifically overwritten by skip_authorization_check.
See this GitHub discussion around this issue.

If I remember correctly the moment you implement CanCan you have to explicit, as long as you call the authorize_resource (or load_and_authorize_resource) in the matching controller.
So yes (, if you call one of authorize methods in the CampController).
(Also the director role should not overlap with the admin role ;) If it does it just get all permissions. And isn't it :all?)

Related

How does cancancan set the model instance, and how can I inspect it?

I noticed users could access an action they shouldn't be able to access.
I debugged in the rails console with something like
user = User.first
physician = Physician.first
ability = Ability.new(user)
ability.can?(:send_message, physician)
# => false
The above says that user can't access the send_message action for that physician, which is the desired behaviour, yet I know they can in the app!
I think this narrows down the cause to a problem with cancancan loading the wrong model instance for some reason. And that's hinted to in the cancan docs too:
Note: this assumes that the model instance is being loaded properly.
But the problem is I'm not sure how to diagnose the problem from here, since the console says it should be working. I don't know how to view the model instance that cancancan has set, and I don't know what else to try.
Any ideas?
Update
I did manage to work around this by using authorize! :send_message, physician in the controller, but since I only stumbled upon this behaviour by chance, I think it's much more important to figure out why the wrong model instance was being loaded (especially so I can see if that was happening elsewhere too).
I figured out why it was probably happening (but I still don't know how to disagnose it)
I think this was happening because I had many custom actions, and some had #physician = Physician.find(current_user.physician.id) (i.e they're the current user), whereas others were more like #physician = Physician.find_by_id(physician_params[:id]). I'm not sure how cancan sets the instance model, but I do know it's not psychic, so it wouldn't know whether to set it to the current user's physician instance, or the physician instance for the physician id passed in.
What remains?
How does cancancan set the model instance for custom methods (I presume it tries something, and if that doesn't work, tries something else, etc etc)?
Small notes that help:
load_and_authorize_resource does attempt to load the model instance for non RESTful actions
Some useful info in the docs
This may have something to do with what I experienced:
When I returned slug it breaks this behaviour and I can edit all pokemons.
Leaving my notes here in case they are helpful to anyone else.
TL;DR, there are a lot of nuanced assumptions cancancan makes, which you won't know about from the outset. I discovered many of them by thoroughly reading the comments in the cancancan readme, code, and defining abilities docs
So here goes..
How cancancan works
if you call authorize! in the controller action itself, cancancan will look for an instance variable in each controller action.
if you instead simply add load_and_authorize_resource at the start of your controller, that will do two things:
Load an instance variable that cancancan thinks should be loaded, and
Checks for authorization on that model instance
Note that for custom actions, load_and_authorize_resource will still try to load a model instance, but how does it know what to load? It doesn't, it guesses, which, for me, I do not like, so be aware of that.
For me, I prefer to do the work of load_and_authorize_resource myself in two separate steps, so I know exactly what's going on.
Ensure #article is generated via a before action for each controller action (or #articles for index action)
Simply have a line at the top of the controller saying load_and_authorize_resource after the before action that sets the model instance
Note that the only difference is now the developer is responsible for loading the right model instance, and cancancan is not trying to guess it. I prefer this approach because it only takes one mistake to accidentally allow access where it shouldn't be granted.
Also remember that load_and_authorize_resource should always go after any before actions that set the model instance variable
Random notes that may also help
The name of the instance variable depends on the action. If we have an articles controller, then:
For the index action, authorize looks for #articles
For all other actions, authorize looks for #article
It then checks to see if the user is allowed access to that resource.
load_and_authorize_resource checks to see if the model instance exists, and if not, creates one. So if you have a before action that creates #article/#articles, then load_and_authorize_resource won't do it for you (i.e. it won't overwrite it), but if you didn't set one, cancan will try to set one. See here for more on that.
An ability rule will override a previous one. (see here for an example)
Just one last thing, never use current_user in ability.rb, it will error silently (!!), so be sure to use user instead :)
Here's what is happening: https://github.com/CanCanCommunity/cancancan/blob/585e5ea54c900c6afd536f143cde962ccdf68607/lib/cancan/controller_additions.rb#L342-L355
# Creates and returns the current user's ability and caches it. If you
# want to override how the Ability is defined then this is the place.
# Just define the method in the controller to change behavior.
#
# def current_ability
# # instead of Ability.new(current_user)
# #current_ability ||= UserAbility.new(current_account)
# end
#
# Notice it is important to cache the ability object so it is not
# recreated every time.
def current_ability
#current_ability ||= ::Ability.new(current_user)
end

Authorization of lit translation dashboard

I'm working on a project where I'm trying to use lit https://github.com/prograils/lit to organise my translation file. So I've installed the gem and done the it's working. The problem is that the dashboard is now available to any user.
We use devise for authentication and pundit for authorization, but I can't find any mechanism to restrict access to the dashboard depending on the users role.
Any help would be greatly appreciated.
dashboard depending on the users role. is something you need to achieve using(in your case) Pundit. As you mentioned you are already using it then you should be able to do something like below:
class DashboardPolicy
:
:
:
def show?
user.admin?
end
end
In you user.rb model you will need a method which would return a boolean value something like:
def admin?
self.role == "admin"
end
Update (in case of no access to controller method)
As you mentioned you dont have access to the controller method then in this case you may want to check for constraints at the routes level.
I wont be adding code snippets here since it has been documented really well on another question you can check out the answer here: https://stackoverflow.com/a/29136866/2545197 and also read more about the same here: http://guides.rubyonrails.org/routing.html#advanced-constraints

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.

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.

Resources