我可能误解了Pundit的政策,但我面临着UserPolicy
与SongPolicy
冲突的问题。
如果UserPolicy
中的语句被断言而忽略了SongPolicy
:中所写的内容,会发生什么
Pundit::NotAuthorizedError in SongsController#edit
not allowed to edit? this User
def authorization
authorize Current.user
end
这个问题是在为用户引入新角色后出现的,但我认为我可能没有正确配置它,并且出于某种原因,只有UserPolicy
被视为在SongsController
中声明授权?
我有两个控制器,用于检查要登录的用户(require_user_logged_in
(,另一个用于检查Pundit的策略(authorization
(:
class UsersController < ApplicationController
before_action :require_user_logged_in!, :authorization, :turbo_frame_check
# Actions were removed for brevity.
end
class SongsController < ApplicationController
before_action :require_user_logged_in!, :authorization, except: [:index, :show]
# Actions were removed for brevity.
end
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
不知道还能在这里尝试什么,我肯定我错过了一些东西,如果有人对Pundit有更多的了解,能让我知道他们对为什么一项政策的声明会泄露到另一项政策中的想法,那将非常有帮助。
您正在当前用户上调用authorize
,该用户是User
,因此Pundit将推断UserPolicy
策略。除非您提供Song
记录,否则它不会自动推断SongPolicy
策略,即使您在SongController
控制器中也是如此。
如果您想使用不同的策略,则需要通过authorize(policy_class:)
提供。
authorize Current.user, policy_class: SongPolicy
像这样的隐式授权通常是一种代码气味。理想情况下,您应该针对当前用户上下文明确授权当前Song
记录。
我认为您对Pundit的方法有误解。特别是您将授权的主题和授权的对象扭曲在一起。让我们试着解释一下。
您有一个参与者,他想对对象应用操作。对象可能被授权接收操作。
默认情况下,Pundit总是将参与者视为current_user
。
操作是策略上的一种方法。
对象是您正在处理的资源;在最琐碎的场景中,它可能是一个ActiveRecord对象,但它不必。
Pundit的authorize
方法,用通俗的英语来说,是";授权资源bar从当前用户接收动作foo";。相反,你想做的是";授权当前用户在资源栏上应用操作foo。
有什么区别?授权的主体和对象是交换的。IMO,在进行授权过程时,您应该回答以下问题:;该对象是否被参与者授权接收此操作">
object action
------------ ------
authorize Current.user, :edit?
注意:参与者隐含地是
current_user
注意:如果没有声明操作,那么它将隐式地为
action_name
它解决了问题";该特定用户是否有权接收:编辑?来自当前用户">
根据推理,这是我认为适用于您的示例场景的正确方法:
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
该代码解决了";这首特定的歌曲是否被授权接收:编辑?来自当前用户">
关于使用自定义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
否则,您的业务逻辑和授权逻辑将依赖于两个可能不同的真相来源。