Rails Pundit 允许其他人查看已发布的内容,同时阻止其他人查看用户信息
Rails Pundit allow published content to be viewed by others while preventing others from viewing user's info
好的,所以我的系统中有 User
、Book
和 Chapter
个实体。
如果作者(User
实体)出版了一本书和一章,那么 public 可以看到。让我们打电话给作者吉姆。
这意味着如果另一个名为 Tycus 的普通用户想要阅读 Jim 的书和书中的章节,他应该能够这样做。
我正在使用 Pundit gem (https://github.com/elabs/pundit) 获取权限。
我面临的问题是,当 Tycus 尝试访问 Jim 的书时,我的 Rails 似乎正在尝试连同它一起获取链接关系 (chapter --> book --> author
):
Started GET "//books/16" for ::1 at 2016-12-16 23:29:38 +0800
Processing by BooksController#show as JSON
Parameters: {"id"=>"16"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 21], ["LIMIT", 1]]
Book Load (0.1ms) SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT ? [["id", 16], ["LIMIT", 1]]
Role Load (0.1ms) SELECT "roles".* FROM "roles" WHERE "roles"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
[active_model_serializers] User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
[active_model_serializers] Chapter Load (0.1ms) SELECT "chapters".* FROM "chapters" WHERE "chapters"."book_id" = ? [["book_id", 16]]
[active_model_serializers] Genre Load (0.1ms) SELECT "genres".* FROM "genres" INNER JOIN "books_genres" ON "genres"."id" = "books_genres"."genre_id" WHERE "books_genres"."book_id" = ? [["book_id", 16]]
[active_model_serializers] Love Load (0.1ms) SELECT "loves".* FROM "loves" WHERE "loves"."book_id" = ? [["book_id", 16]]
[active_model_serializers] Rendered BookSerializer with ActiveModelSerializers::Adapter::JsonApi (30.04ms)
Completed 200 OK in 48ms (Views: 29.6ms | ActiveRecord: 2.0ms)
Started GET "//chapters/5" for ::1 at 2016-12-16 23:29:38 +0800
Processing by ChaptersController#show as JSON
Parameters: {"id"=>"5"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 21], ["LIMIT", 1]]
Chapter Load (0.1ms) SELECT "chapters".* FROM "chapters" WHERE "chapters"."id" = ? LIMIT ? [["id", 5], ["LIMIT", 1]]
Book Load (0.1ms) SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT ? [["id", 16], ["LIMIT", 1]]
Started GET "//chapters/7" for ::1 at 2016-12-16 23:29:38 +0800
Started GET "//chapters/6" for ::1 at 2016-12-16 23:29:38 +0800
[active_model_serializers] User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
Processing by ChaptersController#show as JSON
Processing by ChaptersController#show as JSON
Started GET "//users/2" for ::1 at 2016-12-16 23:29:38 +0800
[active_model_serializers] Rendered ChapterSerializer with ActiveModelSerializers::Adapter::JsonApi (10.73ms)
Parameters: {"id"=>"7"}
Parameters: {"id"=>"6"}
Processing by UsersController#show as JSON
Completed 200 OK in 24ms (Views: 14.2ms | ActiveRecord: 0.9ms)
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 21], ["LIMIT", 1]]
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 21], ["LIMIT", 1]]
Parameters: {"id"=>"2"}
Chapter Load (0.2ms) SELECT "chapters".* FROM "chapters" WHERE "chapters"."id" = ? LIMIT ? [["id", 7], ["LIMIT", 1]]
Chapter Load (0.2ms) SELECT "chapters".* FROM "chapters" WHERE "chapters"."id" = ? LIMIT ? [["id", 6], ["LIMIT", 1]]
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 21], ["LIMIT", 1]]
Book Load (0.1ms) SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT ? [["id", 16], ["LIMIT", 1]]
Book Load (0.1ms) SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT ? [["id", 16], ["LIMIT", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
[active_model_serializers] User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
[active_model_serializers] User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
Role Load (0.2ms) SELECT "roles".* FROM "roles" WHERE "roles"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
[active_model_serializers] Rendered ChapterSerializer with ActiveModelSerializers::Adapter::JsonApi (6.75ms)
[active_model_serializers] Rendered ChapterSerializer with ActiveModelSerializers::Adapter::JsonApi (5.92ms)
Completed 403 Forbidden in 16ms (ActiveRecord: 1.0ms)
Completed 200 OK in 22ms (Views: 9.9ms | ActiveRecord: 1.6ms)
Completed 200 OK in 20ms (Views: 7.9ms | ActiveRecord: 1.0ms)
Pundit::NotAuthorizedError (not allowed to show? this #<User id: 2, first_name: "James", last_name: "Raynor", username: "Jimmy", email: "chewedon+jim@gmail.com", password_digest: "axCQKiku7YD.xjzbj34/P.4JUHCOf4lKXbVeqKy2PNb...", banned: false, role_id: 3, created_at: "2016-12-01 13:56:30", updated_at: "2016-12-11 08:31:42", photo: "jim_raynor.jpg", email_confirmed: true, confirm_token: nil, password_reset_token: nil>):
app/controllers/users_controller.rb:33:in `show'
因此,Pundit 提出了一个 Pundit::NotAuthorizedError
因为它以某种方式认为我正在尝试访问用户的信息。
我的 Emberjs 前端对引发的这个异常产生了正确的共鸣:
我的Chapter_Controller
当然不会明确要求作者信息:
def show
chapter = Chapter.find_by(id: params[:id])
if chapter.present?
authorize chapter
render json: chapter
else
skip_authorization
render status: :not_found
end
end
我可以通过将 User
策略 show?
方法修改为 return true
:
来修复此错误
def show?
true
end
我的show?
方法目前是这样的:
def show?
# Allowing admins to view other admins (but do not allow update or deleting other admins)
if @user.superuser? || @record.id == @user.id || (@user.admin? && !@record.superuser?)
return true
elsif (@record.id != @user.id)
return false
end
end
但这会将我的用户信息暴露给任何人查看。例如,假设一位作者不想透露他们的真实姓名,只想透露他们的用户名(也许作者不太相信 his/her 书会卖得很好,所以使用别名 username
来隐藏他们身份)。
通过在我的用户策略显示方法中指定 true
,任何登录用户都可以向以下地址发出 GET 请求:http://localhost:3000/users/{author_id}
并查看作者的详细信息。
所以我的问题是 - 有没有办法允许其他用户查看作者的书籍和书籍章节,但同时不允许其他用户查看作者的个人信息?
更新
看来我的活动模型序列化程序是试图拉取用户记录的那个。
我认为在活动模型序列化程序 github 页面上正在进行类似的讨论:https://github.com/rails-api/active_model_serializers/issues/1552
更新 2 - 章节策略显示方法
def show?
# superuser and admins should respect author's privacy
# and not be able to view author's unpublished works
owner? || @record.published
end
def owner?
@record.book.author_id == @user.id
end
Chapter
属于Book
,Book
属于User
。
How is ChapterPolicy#show defined? Is that method referencing the
UserPolicy class? (If so, don't do that!)
好的...如果我不检查当前登录的用户是否是作者?
如果有任何帮助,我的 Chapter、Book 和 User 的 Active Model Serializer 如下:
章节:
class ChapterSerializer < ActiveModel::Serializer
attributes :id, :title, :order, :content, :published, :picture
attribute :content, if: :content_author?
belongs_to :book
def content_author?
# ---------------------------------------------------------
# Only author of the content can view their unpublished
# chapter content. Other users including superuser, admin
# and other normal users should not be able to view
# author's unpublished chapter content, even during
# admin/superuser updating author's chapter operation.
#
# We want to respect author's privacy and entitlement
# to publishing their story whenever they feel it's ready.
# ---------------------------------------------------------
if current_user != object.book.author && !object.published
return false
else
return true
end
end
end
图书:
class BookSerializer < ActiveModel::Serializer
attributes :id, :title, :blurb, :adult_content, :published, :cover
belongs_to :author, class_name: "User"
has_many :chapters
has_many :genres
has_many :loves
end
用户:
class UserSerializer < ActiveModel::Serializer
attributes :id, :first_name, :last_name, :username, :email, :banned, :photo
belongs_to :role
has_many :friends
end
你的问题是由于你的模型是如何相关的:
class ChapterSerializer < ActiveModel::Serializer
belongs_to :book
end
class BookSerializer < ActiveModel::Serializer
belongs_to :author, class_name: "User"
end
在前端应用程序的某处,您正在请求有关该书作者的信息 - 目前尚不清楚这是什么信息,但是例如您可能正在查找 "other books by this author"?
这触发了对 /users/:id
的 GET 请求,由于您实施了 UserPolicy#show?
.
,returns 出现 403
错误
您的问题没有单一的答案,因为它在某种程度上是您需要进行的架构设计 issue/decision。但是,例如,可能的方法是允许用户始终可见:
class UserPolicy
def show?
true
end
end
...在您的 UserSerializer
中,根据 current_user
.
有条件地定义返回哪些属性
好的,所以我的系统中有 User
、Book
和 Chapter
个实体。
如果作者(User
实体)出版了一本书和一章,那么 public 可以看到。让我们打电话给作者吉姆。
这意味着如果另一个名为 Tycus 的普通用户想要阅读 Jim 的书和书中的章节,他应该能够这样做。
我正在使用 Pundit gem (https://github.com/elabs/pundit) 获取权限。
我面临的问题是,当 Tycus 尝试访问 Jim 的书时,我的 Rails 似乎正在尝试连同它一起获取链接关系 (chapter --> book --> author
):
Started GET "//books/16" for ::1 at 2016-12-16 23:29:38 +0800
Processing by BooksController#show as JSON
Parameters: {"id"=>"16"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 21], ["LIMIT", 1]]
Book Load (0.1ms) SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT ? [["id", 16], ["LIMIT", 1]]
Role Load (0.1ms) SELECT "roles".* FROM "roles" WHERE "roles"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
[active_model_serializers] User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
[active_model_serializers] Chapter Load (0.1ms) SELECT "chapters".* FROM "chapters" WHERE "chapters"."book_id" = ? [["book_id", 16]]
[active_model_serializers] Genre Load (0.1ms) SELECT "genres".* FROM "genres" INNER JOIN "books_genres" ON "genres"."id" = "books_genres"."genre_id" WHERE "books_genres"."book_id" = ? [["book_id", 16]]
[active_model_serializers] Love Load (0.1ms) SELECT "loves".* FROM "loves" WHERE "loves"."book_id" = ? [["book_id", 16]]
[active_model_serializers] Rendered BookSerializer with ActiveModelSerializers::Adapter::JsonApi (30.04ms)
Completed 200 OK in 48ms (Views: 29.6ms | ActiveRecord: 2.0ms)
Started GET "//chapters/5" for ::1 at 2016-12-16 23:29:38 +0800
Processing by ChaptersController#show as JSON
Parameters: {"id"=>"5"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 21], ["LIMIT", 1]]
Chapter Load (0.1ms) SELECT "chapters".* FROM "chapters" WHERE "chapters"."id" = ? LIMIT ? [["id", 5], ["LIMIT", 1]]
Book Load (0.1ms) SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT ? [["id", 16], ["LIMIT", 1]]
Started GET "//chapters/7" for ::1 at 2016-12-16 23:29:38 +0800
Started GET "//chapters/6" for ::1 at 2016-12-16 23:29:38 +0800
[active_model_serializers] User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
Processing by ChaptersController#show as JSON
Processing by ChaptersController#show as JSON
Started GET "//users/2" for ::1 at 2016-12-16 23:29:38 +0800
[active_model_serializers] Rendered ChapterSerializer with ActiveModelSerializers::Adapter::JsonApi (10.73ms)
Parameters: {"id"=>"7"}
Parameters: {"id"=>"6"}
Processing by UsersController#show as JSON
Completed 200 OK in 24ms (Views: 14.2ms | ActiveRecord: 0.9ms)
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 21], ["LIMIT", 1]]
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 21], ["LIMIT", 1]]
Parameters: {"id"=>"2"}
Chapter Load (0.2ms) SELECT "chapters".* FROM "chapters" WHERE "chapters"."id" = ? LIMIT ? [["id", 7], ["LIMIT", 1]]
Chapter Load (0.2ms) SELECT "chapters".* FROM "chapters" WHERE "chapters"."id" = ? LIMIT ? [["id", 6], ["LIMIT", 1]]
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 21], ["LIMIT", 1]]
Book Load (0.1ms) SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT ? [["id", 16], ["LIMIT", 1]]
Book Load (0.1ms) SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT ? [["id", 16], ["LIMIT", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
[active_model_serializers] User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
[active_model_serializers] User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
Role Load (0.2ms) SELECT "roles".* FROM "roles" WHERE "roles"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
[active_model_serializers] Rendered ChapterSerializer with ActiveModelSerializers::Adapter::JsonApi (6.75ms)
[active_model_serializers] Rendered ChapterSerializer with ActiveModelSerializers::Adapter::JsonApi (5.92ms)
Completed 403 Forbidden in 16ms (ActiveRecord: 1.0ms)
Completed 200 OK in 22ms (Views: 9.9ms | ActiveRecord: 1.6ms)
Completed 200 OK in 20ms (Views: 7.9ms | ActiveRecord: 1.0ms)
Pundit::NotAuthorizedError (not allowed to show? this #<User id: 2, first_name: "James", last_name: "Raynor", username: "Jimmy", email: "chewedon+jim@gmail.com", password_digest: "axCQKiku7YD.xjzbj34/P.4JUHCOf4lKXbVeqKy2PNb...", banned: false, role_id: 3, created_at: "2016-12-01 13:56:30", updated_at: "2016-12-11 08:31:42", photo: "jim_raynor.jpg", email_confirmed: true, confirm_token: nil, password_reset_token: nil>):
app/controllers/users_controller.rb:33:in `show'
因此,Pundit 提出了一个 Pundit::NotAuthorizedError
因为它以某种方式认为我正在尝试访问用户的信息。
我的 Emberjs 前端对引发的这个异常产生了正确的共鸣:
我的Chapter_Controller
当然不会明确要求作者信息:
def show
chapter = Chapter.find_by(id: params[:id])
if chapter.present?
authorize chapter
render json: chapter
else
skip_authorization
render status: :not_found
end
end
我可以通过将 User
策略 show?
方法修改为 return true
:
def show?
true
end
我的show?
方法目前是这样的:
def show?
# Allowing admins to view other admins (but do not allow update or deleting other admins)
if @user.superuser? || @record.id == @user.id || (@user.admin? && !@record.superuser?)
return true
elsif (@record.id != @user.id)
return false
end
end
但这会将我的用户信息暴露给任何人查看。例如,假设一位作者不想透露他们的真实姓名,只想透露他们的用户名(也许作者不太相信 his/her 书会卖得很好,所以使用别名 username
来隐藏他们身份)。
通过在我的用户策略显示方法中指定 true
,任何登录用户都可以向以下地址发出 GET 请求:http://localhost:3000/users/{author_id}
并查看作者的详细信息。
所以我的问题是 - 有没有办法允许其他用户查看作者的书籍和书籍章节,但同时不允许其他用户查看作者的个人信息?
更新
看来我的活动模型序列化程序是试图拉取用户记录的那个。
我认为在活动模型序列化程序 github 页面上正在进行类似的讨论:https://github.com/rails-api/active_model_serializers/issues/1552
更新 2 - 章节策略显示方法
def show?
# superuser and admins should respect author's privacy
# and not be able to view author's unpublished works
owner? || @record.published
end
def owner?
@record.book.author_id == @user.id
end
Chapter
属于Book
,Book
属于User
。
How is ChapterPolicy#show defined? Is that method referencing the UserPolicy class? (If so, don't do that!)
好的...如果我不检查当前登录的用户是否是作者?
如果有任何帮助,我的 Chapter、Book 和 User 的 Active Model Serializer 如下:
章节:
class ChapterSerializer < ActiveModel::Serializer
attributes :id, :title, :order, :content, :published, :picture
attribute :content, if: :content_author?
belongs_to :book
def content_author?
# ---------------------------------------------------------
# Only author of the content can view their unpublished
# chapter content. Other users including superuser, admin
# and other normal users should not be able to view
# author's unpublished chapter content, even during
# admin/superuser updating author's chapter operation.
#
# We want to respect author's privacy and entitlement
# to publishing their story whenever they feel it's ready.
# ---------------------------------------------------------
if current_user != object.book.author && !object.published
return false
else
return true
end
end
end
图书:
class BookSerializer < ActiveModel::Serializer
attributes :id, :title, :blurb, :adult_content, :published, :cover
belongs_to :author, class_name: "User"
has_many :chapters
has_many :genres
has_many :loves
end
用户:
class UserSerializer < ActiveModel::Serializer
attributes :id, :first_name, :last_name, :username, :email, :banned, :photo
belongs_to :role
has_many :friends
end
你的问题是由于你的模型是如何相关的:
class ChapterSerializer < ActiveModel::Serializer
belongs_to :book
end
class BookSerializer < ActiveModel::Serializer
belongs_to :author, class_name: "User"
end
在前端应用程序的某处,您正在请求有关该书作者的信息 - 目前尚不清楚这是什么信息,但是例如您可能正在查找 "other books by this author"?
这触发了对 /users/:id
的 GET 请求,由于您实施了 UserPolicy#show?
.
403
错误
您的问题没有单一的答案,因为它在某种程度上是您需要进行的架构设计 issue/decision。但是,例如,可能的方法是允许用户始终可见:
class UserPolicy
def show?
true
end
end
...在您的 UserSerializer
中,根据 current_user
.