4
votes

Quelle est la manière DRY de restreindre un contrôleur entier avec Pundit in Rails?

J'utilise Pundit avec Rails, et j'ai un contrôleur que je dois complètement restreindre à partir d'un rôle d'utilisateur spécifique. Mes rôles sont «Personnel» et «Consommateur». Le personnel doit avoir un accès complet au contrôleur, mais les consommateurs ne doivent pas y avoir accès.

Existe-t-il un moyen de le faire qui soit plus SEC que de restreindre chaque action une par une?

exemple, voici ma politique:

class MaterialsController < ApplicationController
  before_action :set_material, only: [:show, :edit, :update, :destroy]

  # GET /materials
  def index
    @materials = Material.all
    authorize @materials
  end

  # GET /materials/1
  def show
    authorize @material
  end

  # GET /materials/new
  def new
    @material = Material.new
    authorize @material
  end

  # GET /materials/1/edit
  def edit
    authorize @material
  end

  # POST /materials
  def create
    @material = Material.new(material_params)
    authorize @material

    respond_to do |format|
      if @material.save
        format.html { redirect_to @material, notice: 'Material was successfully created.' }
      else
        format.html { render :new }
      end
    end
  end

  # PATCH/PUT /materials/1
  def update
    authorize @material
    respond_to do |format|
      if @material.update(material_params)
        format.html { redirect_to @material, notice: 'Material was successfully updated.' }
      else
        format.html { render :edit }
      end
    end
  end

  # DELETE /materials/1
  def destroy
    authorize @material
    @material.destroy
    respond_to do |format|
      format.html { redirect_to materials_url, notice: 'Material was successfully destroyed.' }
    end
  end

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

    # Never trust parameters from the scary internet, only allow the white list through.
    def material_params
      params.require(:material).permit(:name)
    end
end

Et mon contrôleur:

class MaterialPolicy < ApplicationPolicy
  attr_reader :user, :material

  def initialize(user, material)
    @user     = user
    @material = material
  end

  def index?
    user.staff?
  end

  def show?
    index?
  end

  def new?
    index?
  end

  def edit?
    index?
  end

  def create?
    index?
  end

  def update?
    create?
  end

  def destroy?
    update?
  end
end

Y a-t-il un moyen de faire ceci que je Je ne comprends pas, ou est-ce ainsi que Pundit est conçu pour exiger que vous soyez explicite?


0 commentaires

3 Réponses :


0
votes

Utilisez le deuxième argument de la méthode authorize . Par exemple:

authorize @material, :index?

Vous pouvez maintenant supprimer toutes les autres méthodes qui appellent simplement index?


3 commentaires

Voulez-vous dire dans la politique? Existe-t-il un moyen de restreindre l'accès à un contrôleur dans son ensemble et de ne pas avoir à appeler autoriser pour chaque action?


@LeeMcAlilly Je pense que vous pouvez utiliser before_action car vous n'avez besoin que de l'utilisateur


Merci une action avant a fait l'affaire pour le contrôleur, mais ce fichier de politique semble toujours faux. N'y a-t-il pas une manière plus concise d'exprimer cela?



1
votes

Pundit ne vous oblige pas à être explicite, mais il le permet. Si la méthode index? de votre stratégie n'a pas été dupliquée, vous voudrez que la capacité soit explicite.

Vous pouvez commencer par envisager de déplacer certaines des vérifications d'autorisation dans la méthode set_material , qui réduit plus de la moitié des vérifications.

L'autre moitié pourrait être extraite dans d'autres méthodes privées si vous le souhaitez, mais je pense qu'elles sont parfaites telles quelles.

Vous pouvez également envisager d'ajouter un rappel before_action pour appeler l'autorisation en fonction du nom de l'action, après avoir mémorisé @material via votre autre rappel, mais la lisibilité en souffrira probablement.


2 commentaires

Merci. Ça a du sens. Existe-t-il un moyen de rendre la classe de politiques plus concise? Pour le moment, la seule façon de le faire fonctionner est de répertorier explicitement chaque action dans la stratégie et de déclarer qui est en mesure d'accéder à cette action. N'y a-t-il pas un moyen de les définir tous à la fois dans la politique? Je jouais avec Scope mais je n'arrive pas à le faire fonctionner.


Vous pouvez utiliser quelque chose comme alias ou alias_method . Nous rédigeons nos politiques comme vous l'avez rédigée. Cela facilite leur modification plus tard. Les portées sont un peu plus délicates; vous devez utiliser policy_scope ou appeler la méthode de résolution de la politique explicitement (par exemple, MaterialPolicy :: Scope.new (current_user, Material) .resolve`. Tant que votre politique hérite d'une ApplicationPolicy de base en suivant l'exemple ou en implémentant la même structure elle-même, cela devrait "simplement fonctionner" github.com/varvet/pundit #scopes . Si vous rencontrez toujours des problèmes avec la portée, déposez une question avec ce que vous avez



5
votes

La première étape consiste simplement à déplacer l'appel à autoriser vers votre rappel:

class StaffPolicy < ApplicationPolicy
  %i[ show? index? new? create? edit? update? delete? ].each do |name|
    define_method name do
      user.staff?
    end
  end
end

class MaterialPolicy < StaffPolicy
  # this is how you would add additional restraints in a subclass
  def show?
    super && some_other_condition
  end
end

Vous pouvez également écrire @material = autoriser Material.find (params [: id]) si votre version de Pundit est à jour (les versions précédentes renvoyaient vrai / faux au lieu de l'enregistrement).

Pundit a une énorme flexibilité dans la façon dont vous choisissez de l'utiliser. Vous pouvez par exemple créer une politique sans tête distincte :

class MaterialsController < ApplicationController
  before_action :authorize_staff
  # ...

  def authorize_staff
    authorize :staff, :access?
  end
end


0 commentaires