динамический канкан из базы данных со сложными условиями

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

def initialize(user)
  user.projects_users.each do |project_user|
    project_user.role.privileges do |privilege|
      can :create, ProjectsUser, :project_id => project_user.project_id
    end
  end
end

Но я пытаюсь сохранить привилегии в базе данных таким образом, чтобы я мог сделать это, чтобы получить результат выше

def initialize(user)
  user.projects_users.each do |project_user|
    project_user.role.privileges do |privilege|
      can privilege.action.to_sym, privilege.subject_class.constantize, privilege.conditions
    end
  end
end

Проблема заключается в части «привилегия.условия». Я не могу сохранить условие, которое должно выполняться только в файле Ability.rb. Если я попытаюсь сохранить:

{ :project_id => project_user.project_id }

Он сказал бы, что нет переменной с именем «project_user». Я мог бы сохранить его как строку и в моем файле возможностей сделать eval(privacy.condition), однако мне нужно было бы сделать это только для значений. Я пробовал что-то вроде этого:

def initialize(user)
  user.projects_users.each do |project_user|
    project_user.role.privileges do |privilege|
      can privilege.action.to_sym, privilege.subject_class.constantize, privilege.conditions.each do |subject, id|
        subject => eval(id)
      end
    end
  end
end

Я получаю сообщение об ошибке: «синтаксическая ошибка, неожиданное =>, ожидание ключевого слова_end» для части «тема =>».

Не знаю, как именно это сделать...

Я использую эту строку команд для проверки:

@user_id = 4
@role = Role.create(name: "Tester", project_id: 4)
@priv = Privilege.create(:action => :create, :subject_class => 'ProjectsUser', :conditions => { :project_id => 'project_user.project_id' })
@role.privileges << @priv
@project_user = ProjectsUser.create(:user_id => @user_id, :role_id => @role.id, :project_id => @role.project_id)
@a = Ability.new(User.find(@user_id))
@a.can?(:create, ProjectsUser.new(:user_id => @user_id + 1, :role_id => @role.id, :project_id => @role.project_id))

Любой совет?


person TheJKFever    schedule 18.06.2015    source источник


Ответы (2)


Итак, я нашел действительно простую работу. По сути, блок do в условиях не оценивался правильно. Вот рабочий код:

user.projects_users.each do |project_user|
  project_user.role.privileges.each do |privilege|
    can privilege.action.to_sym, privilege.subject_class.constantize, Hash[privilege.conditions.map {|subject, condition| [subject, eval(condition)] }]
  end
end

Обратите внимание на Hash[privilege.conditions.map {|subject, condition| [subject, eval(condition)] }]

Это берет символ в качестве ключа в условиях, таких как :subject_id, и сопоставляет его с оцениваемым условием, которое оценивается для определенного идентификатора.

В моей модели у меня есть

class Privilege < ActiveRecord::Base
    has_and_belongs_to_many :roles

  serialize :conditions, Hash
end

И примерная модель:

Privilege.create(
  :action => :create, 
  :subject_class => 'ProjectsUser', 
  :conditions => { :project_id => 'project_user.project_id' }
)

Обновить

Этот метод работает только для условий глубиной в один уровень. Такое условие НЕ работает. Вы получите: TypeError: нет неявного преобразования Hash в String

:conditions => { 
  :project => { 
    :location_id => 'project_user.project.location_id'
  }
}

Это не лучшее решение, но обходной путь

:conditions => { 
  :project => "{ 
    :location_id => eval(\"project_user.project.location_id'\")
  }"
}
person TheJKFever    schedule 19.06.2015
comment
Проблема, с которой я сталкиваюсь сейчас с этой реализацией, заключается в том, что, хотя она работает, ее загрузка занимает ужасно много времени. Каждый раз, когда вы звоните, можете? на странице он загружает все возможные перестановки назначенных привилегий... - person TheJKFever; 22.07.2015

Я определенно сказал бы, что вы идете об этом немного назад. Создание набора правил аутентификации динамически из записей базы данных будет очень сложным для тестирования из-за количества настроек, которые потребуются повсюду в вашем тестовом наборе, и это довольно сложно.

Вместо этого используйте блоки для объявления сложных отношений и ролей для хранения привилегий.

Пример использования замечательного Rolify gem с ролью, привязанной к ресурсу:

can :moderate, Forum do |forum|
  user.has_role?(:moderator, forum)
end

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

if user.has_role? :admin
  can :crud, User
  # ...
end 

Я бы сказал, что если вам действительно нужно сложное полностью динамическое решение, в котором администраторы создают правила авторизации, определения ролей и т. д. из веб-интерфейса, то вы определенно переросли CanCan. Но нужно ли вам это ДЕЙСТВИТЕЛЬНО?

person max    schedule 19.06.2015
comment
На самом деле моему боссу тоже нужна эта функция. Я был удивлен, что разработчики канкана не подумали об этом. Описанное решение не очень хорошее, т.к. eval слишком уязвимое место. Думаю, я просто разделю наши статические роли для большей гибкости в проекте. - person Andrey; 21.01.2016
comment
Я полагаю, что Райан и многие разработчики, связанные с CanCan и CanCanCan, рассмотрели это и решили, что это выходит за рамки того, что CanCan (Can) должен достичь — относительно простое решение для аутентификации, которое по-прежнему надежно. На самом деле это не предназначено для бульдозера швейцарской армии для решения любых возможных проблем с авторизацией, например, когда объявление правил аутентификации больше не является проблемой разработчика, а скорее проблемой конечного пользователя. - person max; 21.01.2016
comment
Возможно, вы захотите взглянуть на Pundit, так как его идиома использования PORO в качестве классов политик предлагает гораздо большую гибкость - в гораздо более чистом виде. - person max; 21.01.2016
comment
Тем не менее, есть вики-страница CanCan о разрешениях БД, которая недостаточно полезна. Хорошо, я согласен с вами. - person Andrey; 22.01.2016
comment
Да, Пандит хорошо выглядит. Немного сложнее перейти на другой модуль аутентификации, но.. возможно) Спасибо за советы. - person Andrey; 22.01.2016