在Pundit中实现范围

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

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

用户属于客户。 客户has_many用户。

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

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

我对范围很模糊。 我的理解是,政策方法(即指数?和展示?)是限制世界卫生组织可以执行这些行动,而范围界定方法限制可以获得哪些记录。 我在编写上述场景的正确范围时遇到了麻烦。

这是Customer控制器:

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  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实例变量,因为这是Application策略类用于引用authorize方法传入的记录的内容。

谢谢!!

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

为了让Pundit的范围工作为show动作,可以使用Pundit的policy_scope帮助器(或policy_scope! ), 或者你可以inheritanceshow? 从生成的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? 的代码 ApplicationPolicy#show? 方法。

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

我想你可以放心地删除空show? 您在CustomerPolicy::Scope拥有的方法,我不相信它会被调用。