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
Related
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
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.
For some reason, for a non logged in user, this code displays the html but once delete is clicked CanCan does not allow the action.
<% if can? :destroy, #boat %>
<%= link_to "", boat, method: :delete, data: { confirm: "You sure?" } %>
<% end %>
How do I prevent the HTML from displaying???
def initialize(user)
user ||= User.new
if user.admin? || user.email = 'test#test.io'
can :manage, :all
elsif user.manager?
can :read, Boat
can [:create, :read, :update], User
else
can :read, Boat
can :create, User
end
end
Some how the app was still detecting my email, even while logged out and therefore giving me admin privileges!
Not sure how this is happening...
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
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.