在Pundit中实现范围

时间:2014-07-07 17:45:03

标签: ruby-on-rails ruby-on-rails-4 pundit

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

此时我的用户模型定义了三个角色:管理员,客户管理员和客户管理员。

用户属于客户。 客户有很多用户。

我在索引客户模型时成功实施了Pundit政策。管理员和客户管理员可以查看所有客户。客户管理员只能看到他们的OWN记录。

当我尝试限制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实例变量,因为它是应用程序策略类用来引用authorize方法传入的记录的内容。

谢谢!

2 个答案:

答案 0 :(得分:7)

为了让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

Here's the code生成默认的ApplicationPolicy#show?方法。

有关其他详细信息,请参阅Scopes上的Pundit自述文件部分。

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

答案 1 :(得分:5)

我认为您不需要范围来限制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?