Skip to content
This repository has been archived by the owner on Dec 12, 2021. It is now read-only.

Custom Permissions Not Working As Expected #594

Closed
partydrone opened this issue Mar 28, 2012 · 2 comments
Closed

Custom Permissions Not Working As Expected #594

partydrone opened this issue Mar 28, 2012 · 2 comments

Comments

@partydrone
Copy link

I am having trouble getting custom actions to work.

What I expect to happen:
A user should only be able to read, create, update, destroy, and submit their own claims. The "Approve" and "Deny" buttons should only show up if the user is an approver.

What's actually happening:
The "Approve" and "Deny" buttons do display for the user, but they do not display for the approver. This is the opposite of what I'm expecting.

I've read through the documentation and various existing issues. I've tried several things, including spelling out which permissions each user has, explicitly (i.e., [:read, :create, :update, :destroy, :submit] instead of :manage). It makes the buttons go away for the user, but they still don't show up for an approver. What am I missing?

models/ability.rb:

class Ability
  include CanCan::Ability

  def initialize(user)
    can :read, Category
    can :manage, Claim, user_id: user.id
    cannot [:approve, :deny], Claim

    if user.has_role? :approver
      can [:read, :approve, :deny], Claim
    end
  end
end

controllers/claims_controller.rb:

class ClaimsController < ApplicationController
  # Restrict access
  load_and_authorize_resource
  skip_authorize_resource only: :new

  def index
    
  end

  def show
    
  end

  def new
    
  end

  def edit
    
  end

  def create
    
  end

  def update
    
  end

  def destroy
    
  end

  ## STATE MACHINE EVENTS

  def submit
    
  end

  def approve
    
  end

  def deny
    
  end
end

views/claims/show.html.erb

<% if can? [:approve, :deny], @claim %>
<%= link_to 'Approve this claim', approve_claim_path(@claim), class: "button gradient" if @claim.can_approve? %>
<%= link_to 'Deny this claim', deny_claim_path(@claim), class: "button red gradient" if @claim.can_deny? %>
<% end %>
@ryanb
Copy link
Owner

ryanb commented Mar 29, 2012

Have you tried testing the Ability class in the console? This will tell you if the problem lies there or elsewhere. See this the Debugging Abilities page for details.

@partydrone
Copy link
Author

After testing the Ability class in the console, I have confirmed that the class itself is behaving as expected, and that permissions are set up the way they should be, but the view is still acting weird. In the console, I grabbed the first user, who is an approver:

Loading development environment (Rails 3.2.2)
irb(main):001:0> user = User.first
  User Load (0.1ms)  SELECT "users".* FROM "users" LIMIT 1
=> #<User id: 1, login: "aporter", group_strings: "Wireless, VPN Users, VPN, Wave-HC, Marketing Group,...", name: "Andrew Porter", email: "andrew.porter@wavetronix.com", ou_strings: nil, auth_token: "CxKa5eF1xyEfU8IxhVdsKw", account_balance: #<BigDecimal:7fae4b7c7e18,'0.25E3',9(36)>, vision_balance: nil, created_at: "2012-03-19 21:56:43", updated_at: "2012-03-26 22:37:21">
irb(main):002:0> user.roles.map { |r| r.name }
  Role Load (0.2ms)  SELECT "roles".* FROM "roles" INNER JOIN "roles_users" ON "roles"."id" = "roles_users"."role_id" WHERE "roles_users"."user_id" = 1
=> ["admin", "approver"]

I grab a claim owned by this user:

irb(main):003:0> claim = Claim.find(10)
  Claim Load (4.8ms)  SELECT "claims".* FROM "claims" WHERE "claims"."id" = ? LIMIT 1  [["id", 10]]
=> #<Claim id: 10, state: "pending", total: #<BigDecimal:7fae4b1cf7e0,'0.105035E3',18(45)>, user_id: 1, batch_id: nil, created_at: "2012-03-28 20:35:03", updated_at: "2012-03-28 21:18:29">

I create an Ability instance for the user:

irb(main):004:0> ability = Ability.new(user)
=> #<Ability:0x007fae4b1a6228 @rules=[#<CanCan::Rule:0x007fae4fd1f538 @match_all=false, @base_behavior=true, @actions=[:read], @subjects=[Category(id: integer, name: string, description: text, percentage: decimal, created_at: datetime, updated_at: datetime)], @conditions={}, @block=nil>, #<CanCan::Rule:0x007fae4fd1f2e0 @match_all=false, @base_behavior=true, @actions=[:manage], @subjects=[Claim(id: integer, state: string, total: decimal, user_id: integer, batch_id: integer, created_at: datetime, updated_at: datetime)], @conditions={:user_id=>1}, @block=nil>, #<CanCan::Rule:0x007fae4fd1efe8 @match_all=false, @base_behavior=false, @actions=[:approve, :deny], @subjects=[Claim(id: integer, state: string, total: decimal, user_id: integer, batch_id: integer, created_at: datetime, updated_at: datetime)], @conditions={:user_id=>1}, @block=nil>, #<CanCan::Rule:0x007fae4fd1e598 @match_all=false, @base_behavior=true, @actions=[:read, :approve, :deny], @subjects=[Claim(id: integer, state: string, total: decimal, user_id: integer, batch_id: integer, created_at: datetime, updated_at: datetime)], @conditions={}, @block=nil>]>

Given the following permissions:

class Ability
  include CanCan::Ability

  def initialize(user)
    can :read, Category
    can :manage, Claim, user_id: user.id
    cannot [:approve, :deny], Claim, user_id: user.id

    if user.role? :approver
      can [:read, :approve, :deny], Claim
    end

    if user.role? :processor
      can :manage, Batch
    end

    # if user.role? :admin
    #   can :manage, :all
    # end
  end
end

Here is what this approver can do with his own claim:

irb(main):005:0> ability.can? :manage, claim
=> true
irb(main):006:0> ability.can? :approve, claim
=> true
irb(main):007:0> ability.can? :deny, claim
=> true

If I grab a claim that the approver doesn't own:

irb(main):008:0> claim = Claim.find(11)
  Claim Load (0.2ms)  SELECT "claims".* FROM "claims" WHERE "claims"."id" = ? LIMIT 1  [["id", 11]]
=> #<Claim id: 11, state: "pending", total: #<BigDecimal:7fae4fd435f0,'0.105E3',9(36)>, user_id: 3, batch_id: nil, created_at: "2012-03-28 22:17:39", updated_at: "2012-03-28 22:24:38">

The permissions still check out:

irb(main):009:0> ability.can? :manage, claim
=> false
irb(main):010:0> ability.can? :read, claim
=> true
irb(main):011:0> ability.can? :approve, claim
=> true
irb(main):012:0> ability.can? :deny, claim
=> true

Except when I combine actions like this:

irb(main):013:0> ability.can? [:approve, :deny], claim
=> false

So, I can put the individual checks on each button, and that gets me where I need to be:

<%= link_to 'Approve this claim', approve_claim_path(@claim), class: "button gradient" if @claim.can_approve? && can?(:approve, @claim) %>
<%= link_to 'Deny this claim', deny_claim_path(@claim), class: "button red gradient" if @claim.can_deny? && can?(:deny, @claim) %>

NOTE: I am coupling the display of the buttons with a state machine, so I'm checking to see if the claim is in a state from which it can be approved, and if the user has the ability to approve it.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants