Devise / Rolify / CanCan 2.0:防止用户更改其他用户的角色

4

我正在使用Devise进行身份验证,Rolify进行角色管理,以及Cancan 2.0进行授权。

我想允许:admin角色更改用户的角色,但禁止其他所有用户访问。

以下是我尝试过但未成功的方法:

#ability.rb
class Ability
  include CanCan::Ability

  def initialize(user)
    if user.has_role? :admin
      can :access, :all
    elsif user.has_role? :moderator
      can [:index, :read, :update, :destroy], :users, :user_id => user.id
      cannot :access, :users, [:role_ids]
    end
end

#application_controller.rb
...
rescue_from CanCan::Unauthorized do |exception|
    redirect_to root_url, :alert => exception.message
  end

我故意在用户表单中保留了关联:

#_form.html.erb
<%= simple_form_for @user do |f| %>
  <%= f.association :roles, as: :check_boxes %>
  <%#= f.association :roles, as: :check_boxes if can? :update, @user, :roles %>
  <%= f.button :submit %>
<% end %>

控制器

#users_controller.rb
class UsersController < ApplicationController

  before_filter :authenticate_user!
  load_and_authorize_resource

  def index
    @users = User.accessible_by(current_ability)
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(params[:user])
  end

  def show
    @user = User.find(params[:id])
  end

  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])

    @user.update_without_password(params[:user])

    if successfully_updated
      redirect_to @user
    else
      render :action => "edit"
    end
  end
end

以及这个模型:

#user.rb
    class User < ActiveRecord::Base
      rolify

      attr_accessible :role_ids
    ...

现在,如果具有:moderator角色的用户尝试更改另一个用户(或自己)的角色,那么将发生以下情况:
  1. 抛出CanCan::Unauthorized异常并将用户重定向到root_url
  2. 用户的角色将被更改
我感到困惑。如果出现异常,为什么还会进行更改?我可能做错了什么 :)
我尝试在users_controller.rb中根据用户角色操作查询参数。如果我在def update之后放置一个日志记录语句,则输出如下:
2013-04-24 12:42:21 [4161] DEBUG    (0.1ms)  BEGIN
2013-04-24 12:42:21 [4161] DEBUG    (0.3ms)  INSERT INTO "users_roles" ("user_id", "role_id") VALUES (5, 1)
2013-04-24 12:42:21 [4161] DEBUG    (0.4ms)  COMMIT
2013-04-24 12:42:21 [4161] DEBUG   User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", "5"]]
2013-04-24 12:42:21 [4161] DEBUG {"username"=>"Blabla", "email"=>"bla@bla.com", "password"=>"", "password_confirmation"=>"", "approved"=>"1", "role_ids"=>["1", "2", ""]}

我一定是忽略了什么...

能否让我们看到包含该复选框的表单以及它提交的控制器?如果没有这些信息,我认为无法调试此问题。 - Ryan Bigg
我已经为我的问题添加了更多的背景信息。 - Joseph Tura
是的,那确实有效。 - Joseph Tura
你的 cannot :access, :users, [:role_ids] 能力规则是什么意思?它似乎要么是语法错误,要么是我漏掉了什么。 - zkcro
这是限制用户模型中属性 role_ids 访问的意图。在句法上应该符合 CanCan 2.0(https://github.com/ryanb/cancan/tree/2.0)。 - Joseph Tura
显示剩余3条评论
4个回答

1
首先,您可能想要整理一下您的能力,因为目前情况有些混乱。不考虑其他方面,在清晰明了的基础上,为修改密码指定一个自定义操作是一个好主意。例如:
# ability.rb

def initialize(user)
  ...

  cannot :manage_roles, :user
  if user.has_role? :admin
    can :manage_roles, :user
  else
end

你希望其他规则起到什么作用?当前似乎只允许管理员自己阅读、编辑和删除,这是你的意图吗?
你可能需要隐藏或禁用表单中与角色相关的部分,对于那些无法实际使用它的人来说。
#_form.html.erb
<%= simple_form_for @user do |f| %>
  <%= f.association :roles, as: :check_boxes if can? :manage_roles, @user %>
  <%= f.button :submit %>
<% end %>

(注意,can?只接受两个参数,即动作和对象/类。)
如果您想要更加安全,您还可以在控制器中使用以下检查:
# users_controller.rb
def update
  @user = User.find(params[:id])

  user_params = params[:user]
  if cannot? :manage_roles, @user
    user_params.delete_if { |k, v| k.to_sym == :role_ids }
  end

  if @user.update_without_password(user_params)
    redirect_to @user
  else
    render :action => "edit"
  end
end

你需要仔细检查从参数哈希中移除的正确键是什么,我假设基于你的 attr_accessible,它应该是 :role_ids,但是我对 simple_form 不是很了解。


插入似乎发生在我能够操作查询参数之前。我也尝试过这样做。 - Joseph Tura
我已经在问题中添加了日志输出。 - Joseph Tura
你尝试过移除if条件来确保问题不是出在那里吗? - zkcro

0
将角色包装在您的users/_form.html.erb文件中:
使用简单表单:
<% if can? :manage, User %>
  <%= f.association :roles, as: :check_boxes %>
<% end %>

没有简单表单:

<% if can? :manage, User %>
    <div class="control-group">
      <%= f.label :roles, class: "control-label" %>
      <div class="controls">
        <% Role.all.each do |role| %>
            <%= check_box_tag "user[role_ids][]", role.id, @user.role_ids.include?(role.id) %>
            <%= role.name %><br />
        <% end %>
      </div>
    </div>
<% end %>

我想在控制器或模型层面上进行安全保护。当然,我可以并且会更改表单。但那不是安全的。 - Joseph Tura

0

我不确定我是否理解了您希望版主做什么,但这是我使用的基本配置。我尽可能地根据您的情况进行了调整。如果版主不需要个人权限,请删除内部子句。

class Ability
  include CanCan::Ability

  def initialize(user)
    # Create guest user aka. anonymous (not logged-in) when user is nil.
    user ||= User.new

    if user.has_role? :admin
      can :manage, :all
    elsif user.has_role? :moderator
      can :manage, User, user_id: user.id
      can :create, User
      can :read, :all
    else
       # Guest user aka. anonymous
      can :read, :all
    end
  end
end

0
我最终使用了一个 before_filter,像这样:
before_filter :prevent_unauthorized_role_setting, :only => [ :create, :update ]

def prevent_unauthorized_role_setting
  if cannot? :manage_roles, current_user
    params[:user].delete_if { |k, v| k.to_sym == :role_ids }
  end
end

在 ability.rb 中遵循 Zaid 的建议:

cannot :manage_roles, :users
if user.has_role? :admin
  can :manage_roles, :users
end

另外,我放弃了Rolify并自己管理角色。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接