2014-07-07 12 views
5

Używam gem Pundit (z Devise i Rolify), aby ograniczyć dostęp do informacji na podstawie zalogowanych ról użytkownika.Implementowanie zakresów w Pundit

W tej chwili mam zdefiniowane trzy role dla mojego modelu użytkownika: Admin, Client Admin i Customer Admin.

Użytkownik należy do klienta. Klient ma wielu użytkowników.

Pomyślnie zaimplementowałem politykę Pundit, gdy indeksowanie modelu klienta. Administratorzy i administratorzy klienta widzą wszystkich klientów. Administrator klienta może zobaczyć tylko swój rekord OWN.

Problem polega na tym, że próbuję ograniczyć metodę klienta kontrolera show. Administratorzy i administratorzy klienta widzą wszystkich klientów. Jednak administrator klienta powinien mieć dostęp tylko do własnego rekordu. Ale w tej postaci Administrator klienta może wprowadzić dowolny identyfikator w adresie URL i zobaczyć dowolny rekord klienta.

Jestem niewyraźny w sprawie zakresu. Rozumiem, że metody Polityki (tj. Index? I show?) Mają ograniczać WHO do wykonywania tych akcji, a metody Scopingu ograniczają, DO KTÓRYCH można uzyskać RECORDS. Mam problem z komponowaniem poprawnego zakresu dla powyższego scenariusza.

Oto kontroler klienta:

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 

A oto polityka klienta:

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 

Sukces !! Dzięki tej niespodzianki, którą dostałem od railscard, sztuczka polegała na modyfikacji show? metoda w pliku zasad klienta jak następuje:

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 

Zauważ, że musiałem użyć zmiennej instancji @record, jako że to właśnie klasa polityka aplikacja używa w odniesieniu do rekordu są przekazywane w metodą autoryzacji.

Dzięki!

Odpowiedz

4

Myślę, że nie potrzebujesz zakresu, aby ograniczyć dostęp dla akcji 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 

Zakresy Pundit zwykle służą do pobrania listy rekordów, do których użytkownik ma dostęp. W przypadku show metody (lub jakiejkolwiek innej metody w kontrolerze, gdzie można nazwać authorize) Pundit instancję klasę polityczną z bieżącego użytkownika i danego klienta, a następnie po prostu wywołuje show? metody, aby sprawdzić uprawnienia użytkownika, tj CustomerPolicy.new(current_user, @customer).show?

+0

Z góry dziękujemy za pomoc. Rozumiem, co mówisz - zakres powinien być używany, gdy oczekuję zwrotu kolekcji. Ale nie jestem pewien, gdzie umieścić tę metodę, którą opisałeś. Czy to by było w polityce klienta? Przepraszam. To sprawia, że ​​czuję się zupełnie głupio. –

+0

Aby było jasne: Próbowałem wstawić metodę do pliku zasad klienta i nie widzę żadnej różnicy w zachowaniu. Chyba nie jestem pewien, co wywołałoby tę metodę. –

+0

GOT IT! Jesteś moim bohaterem, dziękuję bardzo. W pytaniu opublikuję poprawiony kod, ale była to właściwie właściwa odpowiedź. –

5

Aby uzyskać scoping ekspert w pracy w przypadku akcji show można użyć pomocnika Pundit: policy_scope (lub policy_scope!), lub po prostu można dziedziczyć show? z wygenerowanego ApplicationPolicy.

Akcja już działa poprawnie przy użyciu policy_scope, po prostu musimy zrobić coś podobnego do akcji policy_scope.Oto kilka opcji:

Opcja 1: Modyfikacja działania show do

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 

LUB

Opcja 2: Modyfikacja set_customer do

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

LUB

Opcja 3: Zmienić wyświetlanie programu CustomerPolicy #? do

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 że generuje domyślną ApplicationPolicy#show? metody.

Aby uzyskać dodatkowe informacje, patrz rozdział PUNKT README na stronie Scopes.

Myślę, że możesz bezpiecznie usunąć pustą show? metodę, którą masz w CustomerPolicy::Scope, nie wierzę, że zostanie wywołana.

Powiązane problemy