6

我正在使用 Pundit gem(使用 Devise 和 Rolify)来限制对基于登录用户角色的信息的访问。

目前,我为我的用户模型定义了三个角色:管理员、客户管理员和客户管理员。

一个用户属于一个客户。客户 has_many 用户。

在索引Customer 模型时,我已成功实施 Pundit 策略。管理员和客户管理员可以查看所有客户。客户管理员只能看到他们自己的记录。

问题出在我试图限制Customer 控制器的show方法时。管理员和客户管理员可以查看所有客户。但是,客户管理员应该只能看到他自己的记录。 但就目前而言,客户管理员可以在 URL 中输入任何 id 并查看任何客户记录。

我对范围很模糊。据我了解,策略方法(即索引?和显示?)是限制谁可以执行这些操作,而范围方法限制可以获取哪些记录。我无法为上述场景构建正确的范围。

这是客户控制器:

class CustomersController < ApplicationController
  before_action :set_customer, only: [:show, :edit, :update, :destroy]
  after_action :verify_authorized

  # GET /customers
  # GET /customers.json
  def index
    @customers = policy_scope(Customer)
    authorize Customer
  end

  # GET /customers/1
  # GET /customers/1.json
  def show
    authorize @customer
  end

  # GET /customers/new
  def new
    @customer = Customer.new
    authorize @customer
  end

  # GET /customers/1/edit
  def edit
    authorize @customer
  end

  # POST /customers
  # POST /customers.json
  def create
    @customer = Customer.new(customer_params)
    authorize @customer

    respond_to do |format|
      if @customer.save
        format.html { redirect_to @customer, notice: 'Customer was successfully created.' }
        format.json { render :show, status: :created, location: @customer }
      else
        format.html { render :new }
        format.json { render json: @customer.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /customers/1
  # PATCH/PUT /customers/1.json
  def update
    authorize @customer
    respond_to do |format|
      if @customer.update(customer_params)
        format.html { redirect_to @customer, notice: 'Customer was successfully updated.' }
        format.json { render :show, status: :ok, location: @customer }
      else
        format.html { render :edit }
        format.json { render json: @customer.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /customers/1
  # DELETE /customers/1.json
  def destroy
    authorize @customer
    @customer.destroy
    respond_to do |format|
      format.html { redirect_to customers_url, notice: 'Customer was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_customer
      @customer = Customer.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def customer_params
      params.require(:customer).permit(:name, :parent_customer_id, :customer_type, :active, :currency)
    end
end

这是客户政策:

class CustomerPolicy < ApplicationPolicy

  def index?
    # Admins, ClientAdmins, and CustomerAdmins can index customers (see Scope class for filters)
    @user.has_role? :admin or @user.has_role? :client_admin or @user.has_role? :customer_admin
  end

  def show?
    # Admins, ClientAdmins, and CustomerAdmins can see any customer details
    @user.has_role? :admin or @user.has_role? :client_admin or @user.has_role? :customer_admin
  end

  def update?
    # Only Admins and ClientAdmins can update customer details
    @user.has_role? :admin  or @user.has_role? :client_admin
  end

  def destroy?
    @user.has_role? :admin or @user.has_role? :client_admin
  end

  class Scope < Struct.new(:user, :scope)
    def resolve
      if (user.has_role? :admin or user.has_role? :client_admin)
        # Admins and ClientAdmins can see all Customers
        scope.where(:parent_id => nil)
      elsif user.has_role? :customer_admin
        # Customer Admins can only see their own Customer
        scope.where(:id => user.customer) # THIS DOES NOT APPEAR TO GET INVOKED BY THE SHOW METHOD OF THE CONTROLLER
      end
    end    

    def show?
      # NOT SURE WHAT TO PUT IN HERE
    end
  end
end

成功!!感谢 railscard 给我的先机,诀窍是修改节目?客户策略文件中的方法,如下所示:

  def show?
    # Admins, ClientAdmins, and CustomerAdmins can see any customer details
    # Students cannot see customer details

    return true if user.has_role?(:admin) || user.has_role?(:client_admin)
    return true if user.customer_id == @record.id && user.has_role?(:customer_admin)
    false
  end

请注意,我必须使用@record 实例变量,因为这是应用程序策略类用来引用授权方法传入的记录的原因。

谢谢!!

4

2 回答 2

8

为了让 Pundit 的作用域为show动作工作,可以使用 Pundit 的policy_scope助手(或policy_scope!),或者您可以只show?从生成的ApplicationPolicy.

index动作已经正确使用,policy_scope我们只需要为动作做类似的事情show。以下是一些选项:

选项 1:将show操作修改为

def show
  # Also remove :show from the :only option where
  # before_action :set_customer, only: ... is called.
  @customer = policy_scope(Customer).find(params[:id])
  authorize @customer
end

或者

选项 2:修改set_customer

def set_customer
  @customer = policy_scope(Customer).find(params[:id])
end

或者

选项 3:修改 CustomerPolicy#show? 到

def show?
  # scope call here will return the 
  # result of CustomerPolicy::Scope#resolve
  # This is the same implementation generated
  # in the default ApplicationPolicy so you could
  # just delete this method here and inherit instead.
  scope.where(:id => record.id).exists?
end

是生成默认ApplicationPolicy#show?方法的代码。

有关更多详细信息,请参阅 Pundit 关于范围的 README 部分。

我认为您可以安全地删除您拥有的空show?方法CustomerPolicy::Scope,我不相信它会被调用。

于 2014-09-01T15:38:01.840 回答
6

我认为您不需要范围来限制对show操作的访问。

def show?
  return true if user.has_role? :admin || user.has_role? :client_admin
  return true if user.customer_id == customer.id && user.has_role? :customer_admin
  false
end

Pundit 范围通常用于获取用户有权访问的记录列表。如果是show方法(或控制器中的任何其他方法,您在其中调用authorize),Pundit 使用当前用户和给定客户实例化策略类,然后简单地调用show?方法来检查用户权限,即CustomerPolicy.new(current_user, @customer).show?

于 2014-07-07T17:56:48.440 回答