Rails cancan ability.rb - ruby-on-rails

and start to learn cancan+devise
i have 'users' table ( devise )
i have 'posts' table ( with 'user_id' field )
i have 'roles' table ( with 'name' field )
1 - admin
2 - user
i have users_roles ( with 'user_id' and 'role_id' )
i create 2 users with 'user' role
and create 1 user with 'admin' role
user.rb
has_many :posts
has_many :users_roles
has_many :roles, :through => :users_roles
role.rb
has_many :users_roles
has_many :users, :through => :users_roles
users_role.rb
belongs_to :user
belongs_to :role
and there is a question:
i create ability.rb
with
def initialize(user)
user ||= User.new
if user.persisted?
#loged in
can :read, Post
can :create, Post
can :update, Post , :user_id => user.id
can :destroy, Post , :user_id => user.id
else
#not logged
can :read, Post
end
in my views/posts/index.html.erb
<% #posts.each do |post| %>
<tr>
<td><%= post.user.email %></td>
<td><%= post.title %></td>
<td><%= post.text %></td>
<td><%= link_to 'Show', post %></td>
<% if can? :update, Post %>
<% if current_user.id == post.user.id %>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<% end %>
<% end %>
<% if can? :destroy, Post %>
<% if current_user.id == post.user.id %>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% end %>
<% end %>
</tr>
<% end %>
</table>
<br />
<!-- ???почему Post ? -->
<% if can? :create, Post %>
<%= link_to 'New Post', new_post_path %>
<% end %>
and in this case i check, if user login - he can read and create,update,destroy ( if he is autor ), if user not logged(guest) - can only read
but i don't know how change my ability.rb to do that:
i have guest ( only read )
i have user ( can read and create,update,destroy ( if he is autor ) )
i have one admin ( can read, create, update, destroy )
note i already have role table ( with 2 roles ), and 3 users ( 1 with admin role, 2 with user role )

This is how I implemented cancan in my application to manage roles and a guest user.
Simply use a if user.role == role to verify that the user has the right role.
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.role == "author"
#
can :read, Post
can :create, Post
can :update, Post , :user_id => user.id
can :destroy, Post , :user_id => user.id
# a simple way to realize read create update and destroy is :manage
# can :manage, Post, :user_id => user.id
else
if user.role == "admin"
# User with role admin can manage all on all models
can :manage, :all
else
# Guest user can only read something
can :read, Post
# or
# can :read, [SomeModel1, Somemodel2]
# can :read, :all # this means he can read all models
end

Related

Setup cancancan that user couldn't destroy itself

I'm setting up cancancan to implement this function:
if the user is admin, it can destroy every user but himself.
This is my ability.rb
class Ability
include CanCan::Ability
def initialize(user)
if user.role == 'admin'
cannot :destroy, User, id: user.id
end
end
end
And here's my view
<h1>Listing Users</h1>
<table>
<thead>
<tr>
<th>E-Mail</th>
<th>Name</th>
<th>Role</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% #users.each do |user| %>
<tr>
<td><%= user.email %></td>
<td><%= user.name %></td>
<td><%= user.role %></td>
<% if can? :destroy, #user %>
<%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
</tr>
<% end %>
</tbody>
</table>
And even with this setup there's no destroy link at the end of every user now. What I want is that there's a destroy link behind every users but the admin himself. What should I do? Thanks!
Your main issue is that you're calling if can? :destroy, #user when #user doesn't exist:
<% #users.each do |user| %>
<% if can? :destroy, user %>
<% end %>
If the original answer does not work, perhaps you'd be better using a block to evaluate the user object:
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
case user.role
when "admin"
cannot :destroy, User do |x|
x.id == user.id
end
end
end
end
end
This will allow you to use:
<%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } if can? :destroy, user %>
... although fixing the first issue, I think, will resolve your issue.
Just because an admin cannot destroy itself, does not give it permission to destroy other users. You could try giving it destroy permissions.
class Ability
include CanCan::Ability
def initialize(user)
if user.role == 'admin'
cannot :destroy, User, id: user.id
else
can :destroy, User
end
end
end

RoR: CanCanCan authorize only user created items

What is wrong with this code? A normal user still being able to see all relatos, when he should only see his own.
My view code:
<% if can? :read, Relato %>
<td><%= relato.id %></td>
<td><%= relato.cliente.name %></td>
<td><%= relato.projeto.name %></td>
<td><%= relato.local.logra %></td>
<td><%= relato.time %></td>
<td><%= relato.comment %></td>
<% end %>
My Ability class:
can :manage, :all if user.role == "admin"
if user.role == "normal"
can :read, Relato , :user_id => user.id
can :manage, Relato, :user_id => user.id
end
You need to authorize the user for a particular instance:
<%= if can? :read, relato %>
When you attempt to authorize a user for an entire class, as you do above, CanCanCan ignores any conditions defined in the Ability because it can't determine a user_id field for the entire Relato model; it can only do so for a single relato instance.

CanCan: User not being able to view own stuff

I made a simple role based auth with Sorcery and Cancan, which database holds a column named ROLE that when registering a user can be admin or normal,
Relato is a scaffold where you can create "Reports" i wanted to normal user can only see those created by himself and do other stuff(update,destroy) as well.
my ability.rb
def initialize(user)
if user.role == 'admin'
can :manage, :all
elsif user.role == 'normal'
can :create, Relato
can :manage, Relato, :user_id => user.id
can [:read, :update, :destroy], User, :id => user.id
end
no need for control protection
in my view index.html.erb where it lists all "reports" i put
<% if can? :index, Relato %>
<tbody>
<% #relatos.each do |relato| %>
<tr class="alt">
<td><%= relato.cliente.name %></td>
<td><%= relato.projeto.name %></td>
<td><%= relato.local_id %></td>
<td><%= relato.task_id %></td>
<td><%= relato.time %></td>
<td><%= relato.comment %></td>
<td><%= relato.isdoe %></td>
<td><%= link_to 'Editar', edit_relato_path(relato) %></td>
<td><%= link_to 'Deletar', relato, method: :delete, data: { confirm: 'Are you sure?' } %>
</tr>
<% end %>
<% end %>
But it doesn't work, the user can't see his reports, using admin account everything is fine.
Since you have a collection of #relatos, you should not rely on a instance of Relato to check the ability.
Consider using something like can?(:index, Relato). Notice that I'm using the class.
Now you can setup the ability, but since the class is being used, you cannot check check attributes like user_id.
Since you have can :manage, :all for admin, they should be able to read the #relatos.
Let me know if you were trying to achive something else.
<% if can? :read, Relato %>
Not #Relatio.
Also you might want to consider using cascading abilities. Simply put an admin gets all the abilities of a normal user. Plus he gets some special admin abilities. To show why this is a good idea imagine if realize that you also need a editor role:
def initialize(user)
if user.role == 'admin'
can :manage, :all
elsif user.role == 'normal'
can :create, Relato
can :manage, Relato, :user_id => user.id
can [:show, :update, :destroy], User, :id => user.id
elsif user.role == 'editor'
can :manage, Relato
can [:show, :update, :destroy], User, :id => user.id
end
end
Thats alot of duplication. Instead:
def initialize(user)
# Anybody can change their profile
can [:show, :update, :destroy], User, :id => user.id
# Anybody can edit Relatos they own.
can :manage, Relato, :user_id => user.id
if user.role == 'admin'
can :manage, :all
end
if user.role == 'editor'
can :manage, Relato
end
end

CanCan: Can do RUD but not C

I'm trying to allow a user to read, update, or destroy a form that they themselves have created. I'm having some trouble.
In my ability.rb, I have
can [:update, :read, :destroy], Form.where(:user => user)
given that user is an attribute of the form. I can get the forms (and the responses to the forms) to display when I don't have this line in my ability class, but I obviously do not want people to be able to delete the forms of other users. I have already called
load_and_authorize_resource
to invoke CanCan in the controller. Currently I have this error:
The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for :index Form(id: integer, user_id: integer, policy: boolean, two_adult: boolean, training: boolean, attribute_name: boolean, attribute: boolean, agree_to_form: boolean, user_signature: string, signature_date: date, printed_date: datetime, created_at: datetime, updated_at: datetime)
(Changed some names for privacy. I'm also using Devise, by the way, if this changes anything)
Any help would be greatly appreciated. Thanks!
Edit: Would a solution such as...
can :read, Form do |form|
form.user == user
end
be a possibility?
Edit2: My view...
<% #forms.each do |form| %>
<tr>
<td><%= form.user %></td>
<td><%= form.policy %></td>
<td><%= form.two_adult %></td>
<td><%= form.training %></td>
<td><%= form.other_attribute %></td>
<td><%= form.attribute %></td>
<td><%= form.agree_to_covenant %></td>
<td><%= form.user_signature %></td>
<td><%= form.signature_date %></td>
<td><%= form.printed_date %></td>
<% if can? :read, #form %>
<td><%= link_to 'Show', form %></td>
<% end %>
<% if can? :update, #form %>
<td><%= link_to 'Edit', edit_form_path(form) %></td>
<% end %>
<% if can? :destroy, #form %>
<td><%= link_to 'Destroy', form, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% end %>
</tr>
<% end %>
I also noticed upon refreshing the page that the user hash changes each time. This is problematic.
Edit3: Pertinent Ability.rb info...
if user.is_camper?
can :read, Camp
can :read, Payment, Payment.where(:user => user)
can :create, Payment
can :read, Form, :user => user.id
Here is a visual of what happens too.
Your last variant (defined through block) should work.
See examples here.
In your case you can define through hash - it is simpler
can :read, Form, :user_id => user.id
And according to this point you can separately define :read, :create, :update and :destroy
Code on the page:
You iterate by form, so you should check ability on each of them (NOT for one #form)
<% #forms.each do |form| %>
...
<% if can? :read, form %>
...
<% end %>
<% if can? :update, form %>
...
<% end %>
<% end %>
Changes in ability:
can :read, Form # user can read all forms (not only own)
can [:create, :update, :destroy], Form, :user_id => user.id # it is better to use `user_id` instead of `user`
It is just one variant that should work properly but it is not tested by me :)

CanCan authorization issue

I am using cancan for my app
my ability.rb class is
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.role? :admin
can :manage, :all
elsif user.role? :operations
can :manage, :all
elsif user.role? :customer_support
can :read, :all
else
user.role? :marketing
can :read, :all
end
end
end
and i add method in user.rb
def role?(role)
self.roles.include? role.to_s
end
I also add
load_and_authorize_resource in my controller say products_controller which can authorise user and allow him to do certain action in this controller,
but my problem is when user gets logged in with admin as role he can't be able to add new product, it gives access denied error of cancan.
my view is
<% if can? :create, Product %>
<td class="action"><%= link_to 'Show', product %></td>
<td class="action"><%= link_to 'Edit', edit_product_path( product) %></td>
<td class="action"><%= link_to 'Destroy', product, :confirm => 'Are you sure?', :method => :delete %></td>
<% end %>
it also not showing this link to admin as there are all access to admin but still he can't access this action?
what else am I missing?
plz help?
Have you followed instructions in the cancan wiki? https://github.com/ryanb/cancan/wiki/Role-Based-Authorization.
Cancan default strategy for storing roles for each user is using a bitmask, but the wiki mentions about a different solution here: https://github.com/ryanb/cancan/wiki/Separate-Role-Model.

Resources