ruby-on-rails 为什么一个控制器的Pundit策略会受到另一个控制器的影响?

2ledvvac  于 2022-11-19  发布在  Ruby
关注(0)|答案(2)|浏览(242)

我也许误解了Pundit的政策,但我面临着一个问题,UserPolicySongPolicy发生了冲突。
如果AssertUserPolicy中的语句时忽略了SongPolicy中的内容,会发生什么情况:

Pundit::NotAuthorizedError in SongsController#edit 

not allowed to edit? this User

  def authorization
    authorize Current.user
  end

这个问题是在为用户引入新角色后出现的,但我认为我可能没有正确配置它,出于某种原因,在SongsController中只查看UserPolicy来Assert授权。
我有两个控制器检查用户是否登录(require_user_logged_in),另一个控制器检查Pundit的策略(authorization):
第一次
authorization方法如下所示:

def authorization
    authorize Current.user
  end

有一个应用程序级别的策略类ApplicationPolicy

# frozen_string_literal: true

class ApplicationPolicy
  attr_reader :user, :params, :record

  # Allows params to be part of policies.
  def initialize(context, record)
    if context.is_a?(Hash)
      @user = context[:user]
      @params = context[:params]
    else
      @user = context
      @params = {}
    end
    @record = record
  end

  def index?
    false
  end

  def show?
    false
  end

  def create?
    false
  end

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end

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

    def resolve
      raise NotImplementedError, "You must define #resolve in #{self.class}"
    end

    private

    attr_reader :user, :scope
  end
end

UserPolicy保护用户视图:

class UserPolicy < ApplicationPolicy
  class Scope < Scope
  end

  def index?
    user.has_role?(:admin)
  end

  def show?
    # Access if admin or the same user only.
    user.has_role?(:admin) || is_same_user?
  end

  def create?
    index?
  end

  def new?
    create?
  end

  def update?
    index? || is_same_user?
  end

  def edit?
    update? # This is called when accessing a view for `SongsController`.
  end

  def destroy?
    index? || is_same_user? 
  end

  def delete?
    destroy?
  end

  private

  # Used to keep a user from editing another.
  # Admins should be allowed to edit all users.
  def is_same_user?
    # Check if user being accessed is the one being logged in.
    params[:id].to_s == Current.user.username.to_s
  end
end

SongPolicy

class SongPolicy < ApplicationPolicy
  class Scope < Scope
  end

  def index?
  end

  def show?
  end

  def create?
    user.has_role?(:admin) || user.has_role?(:collaborator) # This is ignored.
  end

  def new?
    create?
  end

  def update?
    create?
  end

  def edit?
    create?
  end

  def destroy?
    user.has_role?(:admin)
  end

  def delete?
    destroy?
  end
end

不知道还有什么可以尝试在这里,我敢肯定我错过了一些东西,如果有人更多的知识Maven可以让我知道他们的想法,为什么一个政策的声明可以泄漏到另一个,这将是非常有帮助的。

xtfmy6hx

xtfmy6hx1#

你在当前用户上调用authorize,这是一个User,所以Pundit将推断UserPolicy策略,除非你提供一个Song记录,否则它不会自动推断SongPolicy策略,即使你在SongController控制器中。
如果要使用不同的策略,则需要通过authorize(policy_class:)提供。

authorize Current.user, policy_class: SongPolicy

像这样的隐式授权通常是一种代码味道。理想情况下,您应该根据当前用户上下文显式授权当前Song记录。

sigwle7e

sigwle7e2#

我的观点是你对权威的方法有误解。特别是你把授权的主体和授权的客体扭曲了。让我们试着解释一下。
您有一个参与者要对对象应用操作对象可能已被授权接收操作
默认情况下,权威人士总是认为演员current_user

操作是策略上的一种方法。
对象是您正在使用的资源;在最普通的情况下,它可以是ActiveRecord对象-但也不是必须如此。

Pundit的authorize方法用简单的英语来说就是“授权资源 bar 接收来自当前用户的动作 foo”,而你要做的是“授权当前用户在资源 bar 上应用动作 foo”。
有什么区别?授权的主体和客体互换了。IMO,在进行授权过程中,您应该回答以下问题:“此对象是否有权接收参与者的此操作?”

object        action
          ------------  ------
authorize Current.user, :edit?

注意:参与者隐式为current_user
注意:如果未声明action,则它将隐式为action_name
这将解决问题“是否授权此特定用户从当前用户接收:edit?”
根据推理,这是我认为适合您的示例场景的正确方法:

class SongsController < ApplicationController
  before_action :require_user_logged_in!, :authorization, except: [:index, :show]

  private

  def authorization
    authorize Song
  end
end

我不建议依赖回调,我宁愿编写更显式的代码

def edit
  @song = Song.find(params[:id])
  authorize @song, :edit?
end

这段程式码会解析问题“这首特定的歌曲是否已受权从目前的使用者接收:edit?”。

关于使用自定义policy_class的警告

像在

authorize Current.user, policy_class: SongPolicy

利用该代码,将通过调用SongPolicy#edit?来进行授权,但是record将有规律地被设置为Current.user的值;让我们假设

class SongPolicy
  def edit?
    record.in_my_playlist?
  end
end

其中in_my_playlist?Song的示例方法:你就不会有

undefined method `in_my_playlist?` for #<User>

也许你没有做你想做的事情。

关于在逻辑中使用Current.user的警告

如果Current.user正在使用http://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html,并且整个应用程序都依赖于该单例,那么您可能希望重新定义Pundit的默认用户,如本文所述

class ApplicationController < ActionController::Base
  def pundit_user
    Current.user
  end
end

否则,您的业务逻辑和授权逻辑最终将依赖于两个可能不同的事实来源。

相关问题