Применение области разрешений для отношений в 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
Так что теперь у вас есть кое-что, что позаботится о области видимости и что-то, что будет искать в ваших материалах разрешения, чтобы найти правильные записи.