允许非注册用户使用专家查看内容

Allow non registered users to view content with pundit

我无法让 non-registered/non-logged 用户查看索引并显示博客部分的页面。我正在使用 Pundit 进行授权,并意识到目前我的政策设置为不允许非用户查看博客部分的任何部分,但我不知道如何解决这个问题,因为没有索引政策和显示页面。

我的目标是:

Allow Admin and Editors to view, create, edit, and delete blogs

这部分工作完美

Allow registered users to view blogs

这部分完美

Allow non-registered/non-logged in users to view blogs

这部分不行

当我尝试以用户 non-registered/non-logged 的身份查看索引页面时,我会收到一条来自我的应用程序控制器的访问被拒绝的闪现消息,它正在做它应该做的事情当前政策。

所以我的问题是:如何修改我的策略以允许 non-registered/non-logged 用户仅查看索引和显示页面?

应用程序控制器

class ApplicationController < ActionController::Base
  include Pundit
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
  before_filter :configure_permitted_parameters, if: :devise_controller?

  private

    def user_not_authorized(exception)
      flash[:danger] = "Access denied. You are not authorized to view that page."
      redirect_to (request.referrer || root_path)
    end


protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit(:username, :email, :password, :password_confirmation, :remember_me) }
    devise_parameter_sanitizer.permit(:sign_in) { |u| u.permit(:username, :email, :password, :remember_me) }
    devise_parameter_sanitizer.permit(:account_update) {|u| u.permit(:username, :email, :password, :password_confirmation, :current_password)}
  end


end

申请政策

class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    raise Pundit::NotAuthorizedError, "You must be logged in to perform this action" unless user
    @user = user
    @record = record
  end

  def index?
    true
  end

  def show?
    scope.where(:id => record.id).exists?
  end

  def create?
    false
  end

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end

  def scope
    Pundit.policy_scope!(user, record.class)
  end

  class Scope
    attr_reader :user, :scope

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

    def resolve
      scope
    end
  end
end

Post 政策

class PostPolicy < ApplicationPolicy
  attr_reader :post

  class Scope < Scope
    def resolve
      if user&.admin?&.editor?&.user?
        scope.all
      else user != admin? || editor? || user?
        scope
      end
    end
  end

  def permitted_attributes
    if user.admin? || user.editor?
      [:title, :body, :image, :permalink, :description, :tag_list, :username]
    else
      [:title, :body, :image, :username]
    end
  end

  def index?
    true
  end

  def show?
    true
  end

  def new?
    user.admin? || user.editor?
  end

  def create?
    user.admin? || user.editor?
  end

  def update?
    user.admin? || user.editor?
  end

  def destroy?
    user.admin? || user.editor?
  end
end

Post 控制器

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]
  after_action :verify_authorized, only: [:destroy]

  def index
    @meta_title = "Blog"
    @meta_description = "page description here"
    @posts = Post.all.order("created_at DESC").paginate(:page => params[:page], :per_page => 4)
  end

  def show
    @meta_title = @post.title
    @meta_description = @post.description
  end

  def new
    @meta_title = "Add New Blog"
    @meta_description ="Add a new blog."
    @post = Post.new
    authorize @post
  end

  def edit
    @meta_title = "Edit Blog"
    @meta_description ="Edit an existing blog."
    authorize @post
  end

  def create
    @post = Post.new
    @post.update_attributes(permitted_attributes(@post))
    @post.user = current_user if user_signed_in?

    authorize @post

    if @post.save
      redirect_to @post, notice: 'Post was successfully created.'
    else
      render :new
    end
  end

  def update
    @post = Post.find(params[:id])
    if @post.update_attributes(permitted_attributes(@post))
      authorize @post
      redirect_to @post, notice: 'Post was successfully updated.'
    else
      render :edit
    end
  end

  def destroy
    if @post.present?
      @post.destroy
      authorize @post
    else
      skip_authorization
    end

    redirect_to posts_url, notice: 'Post was successfully deleted.'
  end

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

    # Only allow the white list through.
    def post_params
      params.require(:post).permit(policy(@post).permitted_attributes)
    end
end

我看到有人问过类似的问题 ,但那里建议的解决方案似乎对我的情况不起作用。

几经折腾,终于解决了这个问题。非常感谢@Scott 帮助我们按照应有的方式设置控制器和测试,并且几乎让策略生效。

原来 Application Policyinitializer 部分中的 raise Pundit::NotAuthorizedError, "must be logged in" unless user 不允许未登录的用户访问索引页面(就像它应该在你想要一个封闭的系统......)。由于我的应用程序是开放的,任何人都可以在索引中查看并显示博客页面,因此我需要删除该行。

删除后,应用程序会为尝试访问博客索引页面的未登录用户抛出 undefined method admin?' for nil:NilClass。这是通过使用 Post Policy 中正确的用户识别约定解决的。对于策略中的每个定义,我有 user.admin? || user.editor?。需要更改为 user&.admin? || user&.editor?.

代码最终如下:

申请政策

    class ApplicationPolicy
  attr_reader :user, :record

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

  def index?
    false
  end

  def show?
    scope.where(:id => record.id).exists?
  end

  def create?
    false
  end

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end

  def scope
    Pundit.policy_scope!(user, record.class)
  end

  class Scope
    attr_reader :user, :scope

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

    def resolve
      scope
    end
  end
end

Post 政策

class PostPolicy < ApplicationPolicy

  attr_reader :post


  class Scope < Scope
    def resolve
      if user&.admin? || user&.editor?
        scope.all
      else 
      end
    end
  end

  def permitted_attributes
    if user.admin? || user.editor?
      [:title, :body, :image, :permalink, :description, :tag_list, :username]
    else
      [:title, :body, :image, :username]
    end
  end

  def index?
    true
  end

  def show?
    true
  end

  def new?
    admin_or_editor
  end

  def create?
    admin_or_editor
  end

  def update?
    admin_or_editor
  end

  def destroy?
    admin_or_editor
  end

  private

  def admin_or_editor
    user&.admin? || user&.editor?
  end
end

Post 控制器

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!, except: [:index, :show]
  after_action :verify_authorized, except: [:index, :show]

  def index
    @meta_title = "Blog"
    @meta_description = "blog description"
    @posts = Post.all.order("created_at DESC").paginate(:page => params[:page], :per_page => 4)
  end

  def show
    @meta_title = @post.title
    @meta_description = @post.description
  end

  def new
    @meta_title = "Add New Blog"
    @meta_description ="Add a new blog."
    @post = Post.new
    authorize @post
  end

  def edit
    @meta_title = "Edit Blog"
    @meta_description ="Edit an existing blog."
    authorize @post
  end

  def create
    @post = Post.new
    @post.update_attributes(permitted_attributes(@post))
    @post.user = current_user if user_signed_in?

    authorize @post

    if @post.save
      redirect_to @post, notice: 'Post was successfully created.'
    else
      render :new
    end
  end

  def update
    @post = Post.find(params[:id])
    authorize @post
    if @post.update_attributes(permitted_attributes(@post))
      redirect_to @post, notice: 'Post was successfully updated.'
    else
      render :edit
    end
  end

  def destroy
    if @post.present?
      @post.destroy
      authorize @post
    else
      skip_authorization
    end

    redirect_to posts_url, notice: 'Post was successfully deleted.'
  end

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

  # Only allow the white list through.
  def post_params
    params.require(:post).permit(policy(@post).permitted_attributes)
  end
end