КРАТКОЕ ОПИСАНИЕ ACL ==================== Предназначение -------------- ACL предназначена для контроля прав пользователя на выполнение действий в системе и доступа к моделям по областям видимости. Решаемые задачи --------------- * Проверка наличия у пользователя прав для выполнения метода контроллера; * Прозрачная фильтрация моделей для исключения невидимых для пользователя записей. Возможности ----------- * Неограниченное количество моделей, над которыми могут выполняться действия (`target`); * Неограниченное количество моделей, которые могут выполнять действия над другими (`acter`); * Геренатор прав основывающийся на структуре приложения (см. далее); * Неограниченное количество ролей, которые могут назначаться для `acter` и содержать любую комбинацию прав и доступных видимостей; * Объединение прав `acter`-ов на глубину одной модели (см. далее); * Разграничение назначения ролей по классам (не завершено, на данный момент не критично); * Разграничение ролей на глобальные и локальные (см. далее). Типы моделей, с которыми взаимодействует ACL -------------------------------------------- * __ActerModel__ -- модель, которая может выполнять действия, разрешенные ролями, над другими моделями; * __TargetTarget__ -- модель, над которой могут выполняться действия, разрешенные ролями. __ActerModel__ может иметь глобальную роль, которая определяет возможность выполнения действий без привязки к конкретному экземпляру __TargetModel__ и неограниченное количество прав по отношению к конкретному экземпляру __TargetModel__. __TODO__: *Реализовать дополнение необходимым функционалом моделей, выбранных в качестве __ActerModel__ или __TargetModel__ при декларировании их роли в системе* Схема взаимодействия объектов ACL --------------------------------- Функционал ACL реализуется путем взаимодействия моделей `Right, Role, Relation`, реализующих основной функционал и особых моделей проекта, обозначенных на схеме как `ActerModel` и `TargetModel`. Экземпляры __ActerModel__ и __TargetModel__ связываются посредством модели `Relation`, через которую экземпляр __ActerModel__ получает неограниченное количество ролей по отношению к экземпляру __TargetModel__. ### Схема связей моделей: -------------- | ActerModel | / -------------- --------- -------- | | Right | | Role | V --------- -------- ------------ ... <= ... <= | Relation | --------- -------- ------------ | Right | | Role | | --------- -------- V --------------- | TargetModel | --------------- * Обозначения: <= -- Связь с несколькими моделями <-,/,| -- Связь с одной моделью Генератор прав -------------- Генератор ролей является Rake-task-ом и запускается командой `rake rights:generate`. Желательно запускать после добавления нового метода в контроллер для того, чтобы на этот метод в системе появилось право. Загрузка ролей из дампа ----------------------- Загрузку ролей из заранее подготовленного дампа можно произвести двумя способами: * В консоли, используя Rake task `rake roles:load`, который загрузит в базу роли, описанные в `config/roles.yml` * Через Web-интерфейс, на странице `/roles`, если у пользователя есть соответствующие права. Для загрузки через Web-интерфейс необходимо выбрать файл в поле выбора вверху страницы и нажать __Загрузить__. Получение дампа ролей --------------------- Дамп ролей может получить пользователь, имеющий на это права, зайдя на страницу `roles` и нажав кнопку `Скачать в YML`. Задание областей видимости моделей ---------------------------------- *Этот функционал скорее всего будет изменяться* Если модель должна иметь несколько областей видимости, нужно сделать следующее: * Добавить в модель константу `VISIBILITIES`, в которой задать названия областей видимости; * Добавить к таблице моделей поле `visibility:text`; * Добавить `attr_accessible :visibility` в модель; * Создать `scope :by_visibility`, принимающий аргументом массив областей видимости. После выполнения этих действий на странице редактирования роли появится поле выбора областей видимости для этой модели. ### Пример: model VisibilitiesExample < ActiveRecord::Base VISIBILITIES = ['open', 'hidden', 'open_for_admins'] attr_accessible :visibility scope :by_visibility, lambda {|v| {:conditions => ['visibility in (?)', v]}} end *Назначение методов описано в API* Задание типа модели ------------------- *Этот функционал скорее всего будет изменяться* Если модель должна иметь возможность быть связанной с другими с использованием ролей, необходимо произвести следующие действия: * Добавить в модель декларацию `relationable`, с аргументом `:as`, который может принимать заначения из `[:object, :target]`. Если модель будет __acter__-ом, передается `:object`, иначе `:target` Пример: `relationable :as => :object` * Добавить в модель связь `belongs_to :global_role, :class_name => 'Role'` * Добавить в модель связь с моделью `Relation` * Если модель -- __acter__ и она должна использовать как свои роли, так и роли из другой модели, необходимо добавить декларацию `inherit_rights_from` которой аргументом присвоить имя/имена связей с моделями, из которых должны браться роли. ### Примеры: * Модель, являющаяся __acter__: class ActerModel < ActiveRecord::Base relationable :as => :object belongs_to :global_role, :class_name => 'Role' has_many :targets, :as => :object, :class_name => 'Relation' end * Модель, являющаяся __acter__ и наследующая права другой модели: class ActerWithInheritableRolesModel < ActiveRecord::Base relationable :as => :object ingerit_rights_from :another_acter_model has_many :another_acters_models belongs_to :global_role, :class_name => 'Role' has_many :targets, :as => :object, :class_name => 'Relation' end * Модель, являющаяся __target__: class TargetModel < ActiveRecord::Base relationable :as => :target has_many :objects, :as => :target, :class_name => 'Relation' end * Модель, являющаяся и __acter__, и __target__: class ActerAndTargetModel < ActiveRecord::Base relationable :as => :object relationable :as => :target belongs_to :global_role, :class_name => 'Role' has_many :targets, :as => :object, :class_name => 'Relation' has_many :objects, :as => :target, :class_name => 'Relation' end *Назначение методов описано в API* Использование ACL в контроллере ------------------------------- Если необходимо ограничить доступ ко всем методам контроллера по глобальной роли пользователя вне зависимости от текущей модели, необходимо установить `before_filter :check_global_rights`. В случае, если у пользователя нет прав для выполнения текущего действия, он будет переотправлен на предыдущую страницу. Если необходимо проверить, может ли пользователь выполнить конкретное действие, необходимо в начале этого метода вызвать метод `can_perform?`. Если методу передан параметр, являющийся экземпляром класса __TargetModel__, метод возвратит `true`, если одна или несколько ролей пользователя над этой моделью позволяет ему выполнить может выполнить действие и `false` в противном случае. Если необязательный параметр опущен, или в качестве параметра передано `:system`, учитываются только глобальные роли. ### Примеры * Контроллер, некоторые методы которого доступны для всех: class StuffController < ApplicationController def index # доступ у всех ... end def show # 'Что-то полезное' выполнится только у тех, чьи роли над # @data позволяют выполнить конкретное действие. @data = Stuff.find(params[:id]) if can_perform? @data #что-то полезное else # сообщаем пользователю, что он не может выполнить действие end end def create # 'Что-то полезное' выполнится только у тех, чьи # глобальные роли позволяют выполнить метод if can_perform? # что-то полезное else # сообщаем пользователю, что он не может выполнить действие end end end * Контроллер, доступ к методам которого возможен только при наличии необходимых прав в глобальных ролях: class StuffController < ApplicationController before_filter :check_global_rights # разрешаем доступ только тем, # чьи роли это позволяют. def index # доступ только у тех, кому это позволяет глобальная роль ... end def show # 'Что-то полезное' выполнится только у тех, чьи роли # над @data это позволяют @data = Stuff.find(params[:id]) if can_perform? @data #что-то полезное else # сообщаем пользователю, что он не может выполнить действие end end end Использование ACL во view ------------------------- Используется метод `can_perform?` модели, для которой нужно проверить права доступа. Обычно этой моделью является `current_user`. ### Примеры: * Проверка на возможность выполнения глобального действия: -if current_user.can_perform?('some_controller', 'some_aciton') %a{:href => controller_action_path}= Some description * Проверка на возможность выполнения действия над текущей моделью: -if current_user.can_perform?('some_controller', 'some_aciton', @data) %a{:href => controller_action_path(@data)}= Some description API для работы с ACL -------------------- *Этот функционал скорее всего будет изменяться* ### Методы потомков `ActiveRecord::Base` *  Методы классов: - `relationable` -- устанавливает, кем является модель (acter/target) - `relationable?` -- может ли иметь связь с ролью/ролями с другими - `relation_acters` -- список моделей, которые могут иметь роли по отношению к другим (след. метод) - `relation_targets` -- список моделей, над которыми могут совершаться действия - `relation_acter? (model)`, `relation_target? (model)` -- является ли тем или другим   - `inherit_rights_from (:relation_name | [:relation_names])` -- права из каких связанных моделей наследовать   - `visible_to (model)` -- все видимые для модели записи, может включаться в цепочку (например, для paginate) * Методы инстансов: - `add_role_to(acter, role)` -- привязать acter-а с ролью к текущей записи - `add_role_on(target, role)` -- привязать текущую модель с ролью - `roles_to(object)` -- если object == :system, возвращает глобальные роли текущей записи, если передана запись -- то роли текущей модели над записью - `rights_to(object)` -- аргументы те же, но возвращается список прав, собранный из всех ролей - `right_to(controller_name, action)` -- возвращает запись с правом на выполнение действия action в контроллере c именем `controller_name` - `can_perform? (controller_name, action, target = :system)` -- показывает, может ли текущая модель выполнить действие контроллера над целью ### Методы потомков `ActiveController::Base` *Возможно, будут вынесены в хелпер для универсализации системы* - `can_perform? (target = :system)` -- может ли `current_user` выполнить текущее действие - `check_global_rights` -- делает редирект назад, если пользователь вообще не может совершить текущее действие - `roles_to(object)` -- возвращает список ролей `current_user`-а по отношению к объекту - `rights_to(object)` -- возвращает список прав `current_user`-а по отношению к объекту