Применение области разрешений для отношений в Rails

Допустим, я использую rolify драгоценный камень. Камень rolify обрабатывает разрешения, которые пользователь может иметь для экземпляров ресурса или класса ресурса. Это просто Если нужно выяснить, может ли пользователь выполнить определенную задачу, это приведет к чему-то похожему на это:

if user.has_role? :admin
  @article.update(article_params)
else
  fail # just as an example
end

Тем не менее, я хочу иметь возможность применять эти разрешения в области, например, Article.all - в частности, я хочу иметь возможность определить, какие статьи может читать пользователь. Предполагая, что только пользователи с ролью :admin могу читать статьи, как бы я поступил так?

Редактировать: проблема, с которой я сталкиваюсь, заключается в следующем. Во время выполнения я не знаю, какие статьи сможет прочитать любой пользователь. Эта информация хранится в базе данных. Следовательно, использование решения, такого как cancancan или pundit, не поможет - и не может - помочь в этом случае (примечание: я действительно использую pundit). По сути, я должен выяснить, как реализовать Pundit's Scope учебный класс.

Прежде чем я нашел rolify драгоценный камень, я поставил вопрос другу как так:

Итак, что я сделал для того, чтобы решить это, было следующее: у каждого пользователя есть набор групп; права доступа могут быть определены как для пользователя, так и для групп (обе работают). Разрешения влияют на обобщенный набор записей (где я просто храню тип записи без идентификатора), конкретную запись (тип записи и идентификатор) или на запись в целом (тип записи, без идентификатора, с глобальным флагом). Разрешения также могут быть отменены, фактически удаляя любые разрешения, которые пользователь имеет для субъекта. Итак, моя база данных выглядит примерно так:

Permissions
   id: Integer
   actor_id: Integer
   actor_type: String
   subject_id: Integer
   subject_type: String
   action: String
   negated: Boolean
   global: Boolean
   precedence: Integer

Users
   id: Integer

Groups
   id: Integer

Articles
   id: Integer

Я включил Статьи, чтобы помочь продемонстрировать, что я пытаюсь сделать. Так что, если я применяю эти разрешения:

Permission.create(actor: Group.find(1), subject: Article.find(1), action: "show")
Permission.create(actor: Group.find(1), subject_type: "Article", action: "show")
Permission.create(actor: User.find(1), subject: Article.find(1), action: "show", negated: true, precedence: 1)

Когда я иду permission_scope(Article, User.find(1)) Я должен получить это:

[#<Article id=2>, #<Article id=3>, ...]

Проблема в том, что я не знаю, что содержимое permission_scope будет, так что я не знаю, как это сделать.

2 ответа

Это основные примеры политики из файла readme Pundit, адаптированного для Rolify.

Для простого случая, когда вам нужно проверить только роль пользователя:

class PostPolicy < ApplicationPolicy
  class Scope
    attr_reader :user, :scope

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

    def resolve
      if user.has_role?(:admin)
        scope.all
      else
        scope.where(:published => true)
      end
    end
  end

  def update?
    user.has_role?(:admin) or not post.published?
  end
end

Если у вас есть роли с областями ресурсов, вы можете использовать области, предоставленные Rolify:

class PostPolicy < ApplicationPolicy
  class Scope
    attr_reader :user, :scope

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

    def resolve
      Post.with_role(:author, user)
      # or
      Post.with_all_roles([:author, :editor], user)
    end
  end

  def update?
    user.has_role?(:author, post)
  end
end

Почему это сложно:

Чтобы сделать ту же область видимости с вашей системой разрешений, вам понадобится что-то вроде:

user = User.joins(:permissions, groups: [:permissions]).find(1)

article_ids = Permission.where(
  actor_id: [user.id] + groups.ids,
  subject_type: 'Article',
  action: :show
).pluck(:subject_id)

Article.where(id: article_ids)

Исходя из вашего обновления, вы наверняка захотите эту логику в отдельном классе и, вероятно, в разных модулях из-за предполагаемой сложности. Если это в какой-то вещи с открытым исходным кодом, дайте мне знать, потому что его использование поможет больше. Это непростая проблема, но, надеюсь, это приблизит вас.

module Scope
  class Permission < Struct.new(:relation, :entity, :action)
    def call
      relation.merge(::Query::Permission.new(:relation, :entity, :action)
    end
  end
end

module Query
  class Permission < Struct.new(:relation, :entity, :action)
    def call 
      #Here is where you implement the logic to find records related to a relation/entity/action
    end
  end
end

Так что теперь у вас есть кое-что, что позаботится о области видимости и что-то, что будет искать в ваших материалах разрешения, чтобы найти правильные записи.

Другие вопросы по тегам