2014-07-07 53 views
5

我使用Pundit gem(Devise和Rolify)來限制對基於登錄用戶角色的信息的訪問。Pundit中的實現範圍

此時,我爲我的用戶模型定義了三個角色:管理員,客戶端管理員和客戶管理員。

用戶屬於客戶。 客戶has_many用戶。

我在索引客戶模型下成功實施了一項權威政策。管理員和客戶端管理員可以查看所有客戶。客戶管理員只能看到他們自己的記錄。

問題在於我試圖限制客戶控制器的顯示方法。管理員和客戶端管理員可以查看所有客戶。但是,客戶管理員應該只能看到他自己的記錄。 但是,客戶管理員可以輸入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

我認爲您不需要限制訪問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 

專利範圍通常用於獲取用戶有權訪問的記錄列表。在show方法的情況下(或控制器,在那裏你調用任何其他方法authorize)權威人士實例化策略類與當前用戶和特定客戶,然後簡單地調用show?方法來檢查用戶的權限,即CustomerPolicy.new(current_user, @customer).show?

+0

在此先感謝您的幫助。我明白你的意思 - 當我希望返回一個集合時,應該使用範圍。但是我不確定把你描述的這個方法放在哪裏。這是在客戶的政策?抱歉。這讓我感到完全啞巴。 –

+0

要清楚:我試圖在客戶策略文件中插入方法,並且我沒有看到任何行爲差異。我想我不確定什麼會觸發該方法。 –

+0

GOT IT!你是我的英雄,非常感謝你。我會在問題中發佈修改過的代碼,但這幾乎是正確的答案。 –

5

爲了得到權威人士的範圍界定工作對於show操作,可以使用Pundit的policy_scope幫助器(或policy_scope!),,您可以從生成的ApplicationPolicy繼承show?

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 

OR

方法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?方法。

有關更多詳細信息,請參閱Pundit的自述文章部分Scopes

我認爲你可以安全地刪除CustomerPolicy::Scope中的空show?方法,我不相信它會被調用。