Rails CanCan only show data that user created - ruby-on-rails

I am using Devise for registering accounts and signing in/out. The next functionality I want to add is to only show the data entered for a given user. For instance, if I create a new "something" (a client in my case, at /clients/new), I only want the person who created that something to be able to view it. Right now, if I log in and create a new client, then log out and back in as a different user, I'm able to see the client I created as the other user. This should be restricted so that the author is the only one who can read, update and destroy their own clients.
I've watched Ryan Bate's screencast on CanCan 3 times now, but it seems to only touch on setting it up for different roles, and not for limiting content based on the author.
How can I go about this with CanCan?
My current ability.rb has nothing in it but an empty initialize(user) method.
I have tried this inside that method:
can :update, Client do |client|
client.try(:user) == user
end
with
<% if can? :update, #client %> ... <% end %>
around the loop that displays the clients in the index view, but to no avail.

I think that filtering the results using CanCan is not an optimal solution.
If User 'has_many' Clients, then in your controller method just query for Users' clients:
#clients = current_user.clients

Give this a try
def initialize(user)
can :update, Client, :user_id => user.id
end
From cancan wiki

Related

Admin can't create items for self with CanCan?

I'm using CanCanCan for authorization. An admin can manage all, so they don't have per-user-id rules. The result is that they can't create items for self by default. It looks like I need to add a bunch of extra plumbing to make create in my controllers work the same for admins as it does for general users. The reason seems to be Ability#attributes_for doesn't provide the admin user with the user_id attribute.
How are other people getting around this? Are you writing code to specifically handle the admin use case in your view or controller?
Relevant parts of the Ability class
if user.admin?
can manage, :all
else
can manage, Purchase, user_id: user.id
end
Example interaction
2.6.2 :012 > Ability.new(User.find(3)).attributes_for(:create, Purchase)
=> {:user_id=>3}
2.6.2 :013 > Ability.new(User.find(4)).attributes_for(:create, Purchase)
=> {}
User 3 is general_user, User 4 is an admin
In the controller
# relying on load_and_authorize_resource
def create
puts #purchase.user_id # => nil for admin, 3 for general user
# have to add this for admin use case
#purchase.user = current_user
...
end
I wouldn't rely on the Ability object to assign user_ids to your new objects. I think it's better to explicitly write it in the controller. It seems more clear what's happening. So basically just initiate a new object in the create method and don't rely on load_and_auhtorize_resource
def create
#purchase = current_user.purchases.new(purchase_attributes)
end
If the user wasn't authorized, CanCanCan would already have interfered.
ps. I have been a CanCanCan user for years, but recently moved to Pundit. I think the way it was designed is way better and clearer than CanCanCan. Check it out if you have the time!
There's a bug in CanCanCan. Defining the ability can(:manage) needs an id. Creating a record doesn't. So the ability file doesn't allow you to create bc it's looking only for record that it can find with an id. if you define can :create, Purchase above where you define the :manage you should be good :D
if user.admin?
can :manage, :all
else
can :create, Purchase
can :manage, Purchase, user_id: user.id
end

Given a record retrieve users authorized to :read it via cancan

I have a rails project using cancan for authorization. When a record is updated I want to notify users who are authorized to read it that the record is updated. Cancan easily lets me check if a specific user is authorized to perform an action on a record or get retrieve records that the specific user is allowed to operate on. But can I do the reverse through cancan? I.e. given a specific record retrieve all users authorized to perform an action on it?
I am averagely familiar with Cancan. Having said that, this is what I could think of:
#myrecord = MyRecord.find(1)
authorized_user_ids = User.find_each do |user|
user.id if Ability.new(user).can? :read, #myrecord
end.compact
puts authorized_user_ids
# => [1, 2, 5, 10, 78]
Note however, this is extremely inefficient because it will loop through all users. You might want to perform caching this with a separate table/model that you would implement.
given a specific record retrieve all users authorized to perform an action on it?
No.
That's like asking "can Devise show me all the users who are currently logged in?" - it doesn't have the capacity to do it because the scope is not there.
There are several ways to achieve what you want.
The simplest is to push the notification (I'm not sure how you're doing this) to the front-end of your application (so that, potentially, anyone could read it).
Then, as CallmeSurge said, you'll be able to use can? read on it to make it so that only the users who were eligible could actually invoke the data:
#app/views/layouts/application.html.erb
<%= [[notification]] if can? :read, [[notification]] %>
--
The other way is to use ActiveRecord to return the users eligible to read the notification with the criteria you use already. This can be done using the following:
#app/models/user.rb
class User < ActiveRecord::Base
def self.notify
where(x: y)
end
end
Then you'd be able to set the notifications as follows:
User.notify.each do |user|
user.notification.create .....
end

how can we use cancan gem without being log-in?

I am trying to make a cancan permissions for visitor who doesn't have to login to get some services. To clarify this for example:-
I've a search controller which has new and search actions . In the related new viewer there's some links, some of them I don't want to be seen for non-login users and others must be seen for them!..
I've tried to write these lines in the controller search :-
before_filter :authenticate_user! # this forces the user to log-in ( I don't want this )
load_and_authorize_resource # this doesn't work without authenticate_user
before_filter :load_permissions
in the new view I wrote this
<%= link_to "Listing Managment", extras_path if can? :index, Role %>
but it raises an error for me because of ( can? )
hint: the error arises when I remove the controller's 3 before filter
above. But still not convenient when I put them.
In your cancan initializer you can use something like
user = current_user || User.new
That way cancan will pick up the current user (who may have roles) and if there is no current_user (you are not logged in) will return a dummy user record (anonymous user) with no active roles.

How can I know if a user has access to all or just some resources with Cancan?

I have a Rails 3 application that uses Cancan for authorization. I have the following logic in my application: An admin has access to manage all other users, and has can :manage, User set in the ability file. A company owner has access to manage all users under his company, and has can :manage, User, company_id: user.company_id set in the ability file.
In my UserController#index, i need to call one method if the user has access to manage all the other users, and another method if the user only can access users from his company. Is there any way to do this with CanCan?
As discussed in the question comments, this sounds like a case of roles vs abilities.
If there already is a role system in place, and the logic for choosing between methods maps directly to these roles, then going through CanCan abilities is unnecessarily complex. CanCan is good at checking for abilities on specific model objects, classes and collections but not at going back to the original logic behind why those abilities were awarded in the first place.
In this specific case, there would need to be a way to refer to the case "can manage all Users in Company X but NOT all Users". It might be possible to accomplish with some if-else structure, but I don't think it is what you actually want. Especially if your ability logic changes over time it may not make sense anymore. One example is the corner case where all users belong to the same company, would it be desirable that the "all Users" method is called even for non-admin company owners?
My suggestion therefore is to check the roles directly, much like you already do in your Abilities class. But I feel your pain. ;)
i'm using cancan with device, in my projet i used probably the same you want
if user_signed_in?
if current_user.has_role? :admin
#users = User.all
end
else
#users = User.where(current.user.company_id == company_id)
end
In controller action you should do something like:
#load #user variable
begin
authorize! :manage, #user
#code when access is granted
rescue CanCan::AccessDenied
#code when access is denied
end
I know this an old question but I also ran into this situation. I was also looking to do all role checking in ability.rb file in one place.
What I ended up doing was far from ideal, but just noting it down if anyone wants to go that road. I defined a new ability like this in the ability.rb:
can :manage, :all_users if user.admin?
Please note that :all_users is just a random name that I chose and not some magic method in cancan.
After defining this ability, I was able to do like this in the controller:
if can? :manage, :all_users
call_method_which_can_access_all_users
else
call_method_which_can_access_only_some_users
end
But, it would have been great if Cancan gave us something like can? :manage, User, :all depending on whether there is a hash for filtering users after the User parameter.

Dynamic Role Authorization in Rails using a database. Declarative Authorization the best choice?

I will need to provide dynamic role assignments (Roles/ Privileges) .More clearly, an end user should be able to create a role, assign permissions to a new user. So I was thinking of storing roles and privileges in a table for each user.
Is there a smart way to do this (any other plugin?),or or should I write code to do this with Declarative Authorization . Some light would help.Thanks!
Try answering these to get closer to a solution:
Are the roles themselves dynamic? i.e. Can the privileges assigned various to roles can be changed through the web interface by an Admin? If yes, then you should be storing this information into your database. For a system like a blog, where roles are pre-defined eg. Admin, Guest and Moderator, Declarative Authorization works like a charm.
How strong is the coupling of permissions to the UI? (Sometimes it just a couple of places you need to restrict, in other cases, like a social network, permissions are a lot more complex and coupled tightly with the UI). If its very tightly coupled, i.e. one action is available to all sorts of roles but the actions these roles perform are limited by their definition, then Declarative Authorization (or the likes) won't help much, you need a legacy system.
I've used CanCan recently in a project and think it was pretty cool. You create an Ability class and use it to decide if the user 'can' perform the action... You could check for existence of permissions in a table in the method, or if their ruleset permits the action.
I took all of this sample code from the github readme:
class Ability
include CanCan::Ability
def initialize(user)
if user.admin?
can :manage, :all
else
can :read, :all
end
end
end
Then in your views and your controller you can check authorization levels
<% if can? :update, #article %>
<%= link_to "Edit", edit_article_path(#article) %>
<% end %>
def show
#article = Article.find(params[:id])
authorize! :read, #article
end
Cancan is great for simple/starting projects but you should definitely wrap it if you have a monolithic app. Cancan should be a early solution but not a final one. If your looking at policy objects (pundit) it might be a code smell you need to build your own authorization model. Authorization like integration varies client to client and if your looking for more dynamic solutions or you have too many roles to speak of, cancan is not for you. You may need a more data-driven security model. For example if you can grant someone else access to an entity.

Resources