Pundit作用域使用空结果



假设我有一个场景,我们有Users,每个用户都可以创建自己的Projects

我试图将Rails控制器的Show操作限制为只允许管理员或项目所有者执行Show操作。

我面临的问题是,也许我误解了如何在Pundit中使用Scopes。

我的Show操作如下:

def show
project = policy_scope(Project).find_by({id: project_params[:id]})
if project
render json: project
else
render json: { error: "Not found" }, status: :not_found
end
end

我的PunditScope类如下:

class Scope < Scope
def resolve
if @user.admin?
scope.all
else
# obviously, if non-matching user id, an ActiveRelation of  
# empty array would be returned and subsequent find_by(...) 
# would fail causing my controller's 'else' to execute
# returning 404 instead of 403
scope.where(user_id: @user.id)
end
end
end

在我的Rails测试中,我试图断言非项目所有者应该收到一个403禁止:

test "show project should return forbidden if non admin viewing other user's project" do
# "rex" here is not the owner of the project
get project_path(@project.id), headers: @rex_authorization_header
assert_response :forbidden
end

我的考试不及格。我得到错误:

Failure:
ProjectsControllerTest#test_show_project_should_return_forbidden_if_non_admin_viewing_other_user's_project [/Users/zhang/App_Projects/LanceKit/Rails_Project/LanceKit/test/controllers/projects_controller_test.rb:40]:
Expected response to be a <403: forbidden>, but was a <404: Not Found>.
Expected: 403
Actual: 404

我觉得我没有正确使用Pundit。

我应该使用Pundit的authorize project而不是使用policy_scope(Project)...进行Show操作吗?

我希望scope.where(...)检测到不正确的用户id,并返回一些错误,说"您无权查看此资源",而不是返回结果。

根据我的测试结果,使用show操作的范围是错误的。

我的发现告诉我,Pundit作用域只用于过滤一组数据,只返回那些符合条件的数据,它不检查current_user是否是资源的所有者。Pundit作用域不会引发403 Forbidden错误。

换句话说,仅在show操作中使用作用域将导致语义错误,例如说this project with id 3 does not exist in the database而不是说you are not authorized to view this project because it belongs to a different user

我自己的总结:

  • 使用policy_scope执行index操作
  • authorize用于showcreateupdatedelete
  • 如果你不是资源所有者,并且试图访问一些时髦的多资源路由(如),请使用authorizepolicy_scope

    get "/user/1/projects" => "Project.index"

    以防您想检查用户是否是允许查看您的项目的"项目经理"或"合作者"。在这种情况下,您可能需要使用额外的elsif子句来修改范围代码。

关于我的上述问题,我修改了我的项目,在我的show操作中使用authorize

def show
project = Project.find_by({id: project_params[:id]})
authorize project
if project
render json: project
else
render json: { error: "Not found" }, status: :not_found
end
end

然后,这引发了预期的403 Forbidden错误,这是我的测试所期望的,因此我的测试通过了。

关于作用域的专家文档表明,您确实可以将它们用于显示操作:

def index
@posts = policy_scope(Post)
end
def show
@post = policy_scope(Post).find(params[:id])
end

如果用户(手动)打开了一个带有实例id参数的URL,而她应该无法查看,那么仅仅使用authorize可能是不够的。

为了避免RecordNotFound错误,我使用了推荐的NilClassPolicy:

class NilClassPolicy < ApplicationPolicy
class Scope < Scope
def resolve
raise Pundit::NotDefinedError, "Cannot scope NilClass"
end
end
def show?
false # Nobody can see nothing
end
end

最新更新