CanCan: limiting a user's ability to set certain model attributes based on their role
Asked Answered
C

4

20

I have a Post model with a :published attribute (boolean) and a User model with a role attribute (string). There are three roles: ROLES = %w[admin publisher author]

I don't want users whose role is author to be capable of setting, or editing, the :published field on the Post model.

I'm using CanCan (and RailsAdmin gem) and my simplified Ability.rb file looks like this:

class Ability
  include CanCan::Ability
  def initialize(user)
    user ||= User.new

    if user.role? :admin
      can :manage, :all
    elsif user.role? :publisher
      can :manage, Post
    elsif user.role? :author
      # I want to prevent these guys from setting the :published attribute
    end

  end
end

Anyone got any tips for doing this sort of thing?

Cannibalize answered 7/5, 2011 at 14:11 Comment(0)
P
13

So far it is not possible. But according to this: https://github.com/ryanb/cancan/issues/326 this feature should be in cancan 2.0.

Update: you can see this on CanCan 2.0 branch here: https://github.com/ryanb/cancan/tree/2.0 in section "Resource Attributes"

Perjury answered 7/5, 2011 at 14:20 Comment(2)
ok, thanks for that, I will keep an eye on CanCan v2.0 then. ThanksCannibalize
I know it's not perfect, but I think you can still use cannot <<your attribute>>, :klass bee because they are still methods in ruby while waiting for 2.0.Roasting
E
3

Check out this post: How do I use CanCan with rails admin to check for ownership

It shows how to make a field not visible based off a users role.

UPDATE I was able to set options in rails admin with this code:

config.model User do
  edit do
    configure :organization do
      visible do
        bindings[:view]._current_user.max_role_name != 'admin' ? false : true
      end
    end

    configure :organization_id, :hidden do
      visible do
        true if bindings[:view]._current_user.max_role_name != 'admin'
      end
      default_value do
        bindings[:view]._current_user.organization_id if bindings[:view]._current_user.max_role_name != 'admin'
      end
    end

    include_all_fields
  end
end

This configuration will hide the organization field if the logged in user is not an admin. It will then show an organization_id field ( set to type='hidden' ) and set the default value.

Hope this helps someone.

Escent answered 2/3, 2012 at 21:34 Comment(0)
M
3

Until CanCan 2.0 comes out, I've solved this by creating a subclass of the model with restricted accessibility, something like:

class AuthorPost < Post
  attr_protected :published
end

And then give authors access to AuthorPosts: can :manage => AuthorPost

Then in your controller, you can set the resource you want in a before_filter:

before_filter :set_resource
...
  private
    def set_resource
      if current_user and current_user.author?
        @resource = AuthorPost
      else
        @resource = Post
      end
      params[:post] ||= params[:author_post]
    end

One last caveat: you won't be able to use load_and_authorize_resource in that controller. You'll have to do that manually, as detailed here: https://github.com/ryanb/cancan/wiki/Controller-Authorization-Example

You'll need to replace Project with @resource.

I'm on the fence as to whether this is more or less effective than the method described in the railscast. For my purposes, it left the original model totally intact, so my other code wasn't affected--and just allowed me to give some users fewer editable fields.

Mecklenburg answered 27/4, 2012 at 19:34 Comment(0)
H
0

There is a way, I did something like this in my project. But CanCan is not entirely the answer. What you need to do is make attr_accessible in your model dynamic based on user role, so if you're an admin, then you're allowed to update the published field. If not, then giving the field a new value simply won't take when the model saves.

Railscasts comes to the rescue once again: http://railscasts.com/episodes/237-dynamic-attr-accessible

Following getting the backend part of that implemented, then you can do something about the frontend form by wrapping the publish field in the View with a roles check or something to show or hide the field based on the user. Rough example of my implementation...

<% if current_user.roles.where(:name => ['Administrator','Editor']).present? %>
    <%= f.label :display_name %>
    <%= f.text_field :display_name %>
<% end %>
Hengelo answered 7/5, 2011 at 21:41 Comment(1)
thanks, I like this - going to watch that railscast now - thanks :)Cannibalize

© 2022 - 2024 — McMap. All rights reserved.