Rails Pundit 允许其他人查看已发布的内容,同时阻止其他人查看用户信息

Rails Pundit allow published content to be viewed by others while preventing others from viewing user's info

好的,所以我的系统中有 UserBookChapter 个实体。

如果作者(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属于BookBook属于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.

有条件地定义返回哪些属性