Devise / Rolify / CanCan 2.0: запрет пользователям изменять роль пользователя

Я использую 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, вот мой вывод:

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"=>"[email protected]", "password"=>"", "password_confirmation"=>"", "approved"=>"1", "role_ids"=>["1", "2", ""]}

Я, должно быть, что-то упускаю из виду...


person Joseph Tura    schedule 23.01.2013    source источник
comment
Нам было бы полезно увидеть остальную часть формы, где находится этот флажок, и контроллер, которому он подчиняется. Без этого я не думаю, что эту проблему можно отладить.   -  person Ryan Bigg    schedule 23.01.2013
comment
Я добавил еще немного контекста к моему вопросу.   -  person Joseph Tura    schedule 23.01.2013
comment
у меня такое ощущение, что нет простого способа сделать это... и что Cancan на самом деле работает так, как задумано. Можете ли вы подтвердить, что if can? :update, @user, :roles правильно показывает/скрывает флажки? если да, то я думаю, что канкан сделал свое дело. Действие update может потребоваться разделить на update и admin_update, при этом 2-е действие может касаться ролей.   -  person tw airball    schedule 22.04.2013
comment
Да, это работает.   -  person Joseph Tura    schedule 23.04.2013
comment
Для чего предназначено ваше правило способности cannot :access, :users, [:role_ids]? Похоже, либо неправильный синтаксис, либо я что-то упускаю.   -  person zkcro    schedule 24.04.2013
comment
Он предназначен для ограничения доступа к атрибуту role_id в пользовательской модели. Должно быть синтаксически правильным CanCan 2.0 (github.com/ryanb/cancan/tree/2.0 ).   -  person Joseph Tura    schedule 24.04.2013
comment
@JosephTura Вы уже поняли, как это сделать с канканом 2.0?   -  person dropson    schedule 26.04.2013
comment
Я только что добавил ответ, который показывает суть того, что я сделал.   -  person Joseph Tura    schedule 26.04.2013


Ответы (4)


Во-первых, вы, вероятно, захотите почистить свои способности, так как на данный момент все кажется немного запутанным. Игнорируя все остальное, вероятно, было бы неплохо указать специальное действие для изменения паролей, для ясности. Например:

# 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

(Вам нужно будет дважды проверить, какой правильный ключ удалить из хэша параметров, я предполагаю, что это :role_ids на основе вашего attr_accessible, но я действительно не очень хорошо знаю simple_form.)

person zkcro    schedule 24.04.2013
comment
Кажется, что вставка происходит до того, как я могу манипулировать параметрами запроса. Я тоже это пробовал. - person Joseph Tura; 24.04.2013
comment
Я добавил выходные данные журнала к вопросу. - person Joseph Tura; 24.04.2013
comment
Вы пытались удалить условие if, чтобы убедиться, что проблема не в нем? - person zkcro; 25.04.2013

Оберните роли этим в файле 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 %>
person Tomanow    schedule 22.04.2013
comment
Я хочу защитить его на уровне контроллера или модели. Конечно, я могу и буду менять форму. Но это не безопасно. - person Joseph Tura; 23.04.2013

Я не уверен, понял ли я, что вы хотите, чтобы модераторы сделали, но вот базовая конфигурация, которую я использую. Я адаптировал его к вашему сценарию, насколько мог. Если модераторам не нужны отдельные права, просто удалите внутренний пункт.

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
person JJD    schedule 24.04.2013

В итоге я использовал 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:

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

Кроме того, я отказался от Rolify и управлял ролями самостоятельно.

person Joseph Tura    schedule 26.04.2013