В процессе превращения большей части web-проектов в браузерные приложения, появляется много вопросов. И один из самых весомых из них – обработка прав доступа без лишнего кода. Размышления на эту тему привели нас к большой проблеме: комфортного способа реализовать защиту на уровне полей модели для ActiveRecord просто нет (Егор, привет! ;). CanCan добавляет ограничения на уровне контроллеров, но это слишком высокий уровень чтобы решить все проблемы.
Немножко пободавшись, мы написали два милых гема. Встречайте, Heimdallr (Хеймдаль) и его расширение Heimdallr::Resource. Они принесут в ваши модели мир и безопасность.
Давайте для начала рассмотрим проблему глубже. Огромная часть проектов действительно приравнивает безопасность к управлению доступом REST-контроллеров. Большие проекты нередко спускаются к моделям, чтобы не дублировать код. А чтобы число экшнов в контроллерах не стало невыносимо большим, иногда спускаются и к контролю доступа полей.

Для многих RESTful-приложений, 1-й и 2-й уровни идентичны. Поэтому в сухом остатке у нас:
При этом важность управления доступом к полям стремительно растет с ростом проекта. И недавний пример с дискредитацией Github – яркий пример последствий подхода «Поля? Да кому это надо!».
Вот пример того, как Heimdallr может помочь с этим:
Используя простой DSL внутри моделей, мы объявляем как ограничения доступа к самим моделям, так и к их полям. Heimdallr расширяет использующие его модели методом
Обратите внимание, что для вызовов
Эти ограничения можно использовать в проекте где угодно, не только в контроллерах. И это важно. Если вы попытаетесь прочитать защищенное поле – исключение. Такое поведение предсказуемо, но это не очень удобно для оформления вьюшек.
Чтобы решить проблему с вьюшками, Heimdallr реализует две стратегии, явную и неявную. По умолчанию, Heimdallr будет следовать явной модели поведение. А вот альтернативное поведение:
Ок. В начале статьи я упомянул CanCan. Но разве он не решает проблему принципиально иначе?
Для многих Rails-проектов термин «Безопасность» является синонимом именно для гема CanCan. CanCan действительно был целой эпохой и до сих пор отлично работает. Но у него есть ряд проблем:
Мы начали разработку Heimdallr'я как инструмента контроля моделей, но на практике оказалось, что у нас достаточно данных, чтобы ограничивать и контроллеры. Поэтому мы взяли и написали Heimdallr::Resource.
Эта часть Heimdallr'я мимикрирует под CanCan настолько, насколько это возможно. У вас есть тот же фильтр
Выглядит это так:
В начале повествования, я рассказал о корне идеи, синхронизации прав доступа между клиентским приложением и серверным REST-API. И вот к каким конвенциям мы в итоге пришли.
Представьте, что у вас есть простой CRUD-интерфейс с ролями, который нужно реализовать как клиентское JS-приложение. При этом на сервере у вас есть REST с index/create/update/destroy. Права доступа задают следующие вопросы:
Первый вопрос решается Heimdallr'ом от природы. Вы просто определяется нужный скоуп и все. Никто ничего лишнего просто не видит. Касательно остального. В своей последней статье я рассказал как мы рендерим JSON-представления для REST-провайдеров. Используя эту же технику, вьюху очень легко расширить следующими полями:
Для REST API метод new практически бесполезен. И это отличное место чтобы определить можем ли мы создавать что-то и что именно. Например так:
Если мы не можем создавать вообще, Heimdallr::Resource ответит на этот запрос ошибкой. Иначе мы получим список полей, доступных для заполнения.
Хеймдаль также объявляет метод
Идея аналогична созданию. Только в этот раз мы объявим метод edit:
Использование Хеймдалля и его расширения, Heimdallr::Resource, поможет легко управлять правами доступа без лишнего мусора в коде. И, что немаловажно, вы получаете дополнительную магию для ваших REST-API. Помните, Хомяков следит за вами!
ಠ_ಠ
Немножко пободавшись, мы написали два милых гема. Встречайте, Heimdallr (Хеймдаль) и его расширение Heimdallr::Resource. Они принесут в ваши модели мир и безопасность.
Heimdallr
Давайте для начала рассмотрим проблему глубже. Огромная часть проектов действительно приравнивает безопасность к управлению доступом REST-контроллеров. Большие проекты нередко спускаются к моделям, чтобы не дублировать код. А чтобы число экшнов в контроллерах не стало невыносимо большим, иногда спускаются и к контролю доступа полей.

Для многих RESTful-приложений, 1-й и 2-й уровни идентичны. Поэтому в сухом остатке у нас:
- Доступ к моделям
- Доступ к полям моделей
При этом важность управления доступом к полям стремительно растет с ростом проекта. И недавний пример с дискредитацией Github – яркий пример последствий подхода «Поля? Да кому это надо!».
Вот пример того, как Heimdallr может помочь с этим:
class Article < ActiveRecord::Base include Heimdallr::Model belongs_to :owner, :class_name => 'User' restrict do |user, record| if user.admin? # Администратор может делать что угодно scope :fetch scope :delete can [:view, :create, :update] else # Другие пользователи видят свои и не-секретные статьи scope :fetch, -> { where('owner_id = ? or secrecy_level < ?', user.id, 5) } scope :delete, -> { where('owner_id = ?', user.id) } # ... и видят все поля кроме уровня секретности # (хотя владельцы видет все поля)... if record.try(:owner) == user can :view can :update, { secrecy_level: { inclusion: { in: 0..4 } } } else can :view cannot :view, [:secrecy_level] end # ... а еще они могут их создавать, правда с ограничениями. can :create, %w(content) can :create, { owner_id: user.id, secrecy_level: { inclusion: { in: 0..4 } } } end end end
Используя простой DSL внутри моделей, мы объявляем как ограничения доступа к самим моделям, так и к их полям. Heimdallr расширяет использующие его модели методом
.restrict. Вызов этого метода обернет класс модели в прокси-обертку, которую можно использовать совершенно прозрачно.Article.restrict(current_user).where(:typical => true)
Обратите внимание, что для вызовов
Class.restrict, вторым параметром блока будет nil. Поэтому все проверки, зависящие от состояния полей текущего объекта должны быть обернуты в .try(:field).Эти ограничения можно использовать в проекте где угодно, не только в контроллерах. И это важно. Если вы попытаетесь прочитать защищенное поле – исключение. Такое поведение предсказуемо, но это не очень удобно для оформления вьюшек.
Чтобы решить проблему с вьюшками, Heimdallr реализует две стратегии, явную и неявную. По умолчанию, Heimdallr будет следовать явной модели поведение. А вот альтернативное поведение:
article = Article.restrict(current_user).first @article = article.implicit @article.protected_thing # => nil
Ок. В начале статьи я упомянул CanCan. Но разве он не решает проблему принципиально иначе?
CanCan
Для многих Rails-проектов термин «Безопасность» является синонимом именно для гема CanCan. CanCan действительно был целой эпохой и до сих пор отлично работает. Но у него есть ряд проблем:
- CanCan был задуман как инструмент, который не работает с моделями. Он предлагает архитектуру, в которой REST-контроллеры защищены, а до моделей злоумышленник попросту не дойдет. Иногда эта стратегия хороша, иногда нет. Но факт в том, что до полей при этом не добраться, как ни старайся. CanCan попросту не знает и ничего не может знать о полях.
- Ветка 1.х мертва и не поддерживается. В ней есть несколько неприятных багов, которые препятствуют работе в сложных случаях с namespac'ами. А ветка 2.х разрабатывается непозволительно долго.
Мы начали разработку Heimdallr'я как инструмента контроля моделей, но на практике оказалось, что у нас достаточно данных, чтобы ограничивать и контроллеры. Поэтому мы взяли и написали Heimdallr::Resource.
Эта часть Heimdallr'я мимикрирует под CanCan настолько, насколько это возможно. У вас есть тот же фильтр
load_and_authorize и вот как он работает:- Если для текущего контекста не объявлен скоуп :create (и следовательно вы не можете создавать сущности), значит вам нельзя в new и create
- Если у вас нет скоупа :update, нельзя в edit и update.
- Аналогичный подход для скоупа :destroy
- В экшны вы получаете сразу защищенную сущность, а следовательно не можете забыть вручную вызвать
restrict
Выглядит это так:
class ArticlesController < ApplicationController include Heimdallr::Resource load_and_authorize_resource # если имя контроллера отличается: # # load_and_authorize_resource :resource => :article # для вложенных: # # routes.rb: # resources :categories do # resources :articles # end # # load_and_authorize_resource :through => :category def index # @articles заполняются и restrict'ятся end def create # @article заполняются и restrict'ятся end end
Провайдеры REST-API
В начале повествования, я рассказал о корне идеи, синхронизации прав доступа между клиентским приложением и серверным REST-API. И вот к каким конвенциям мы в итоге пришли.
Представьте, что у вас есть простой CRUD-интерфейс с ролями, который нужно реализовать как клиентское JS-приложение. При этом на сервере у вас есть REST с index/create/update/destroy. Права доступа задают следующие вопросы:
- Какие сущности я могу получить через index?
- Какие из них я могу менять?
- Какие из них я могу удалить?
- Могу ли я создать новую сущность?
- Какие поля я могу задать при обновлении?
- Какие поля я могу задать при создании?
Первый вопрос решается Heimdallr'ом от природы. Вы просто определяется нужный скоуп и все. Никто ничего лишнего просто не видит. Касательно остального. В своей последней статье я рассказал как мы рендерим JSON-представления для REST-провайдеров. Используя эту же технику, вьюху очень легко расширить следующими полями:
{modifiable: self.modifiable?, destroyable: self.destroyable?}
Могу ли я создавать? И с какими полями?
Для REST API метод new практически бесполезен. И это отличное место чтобы определить можем ли мы создавать что-то и что именно. Например так:
Article.restrictions(current_user).allowed_fields[:create]
Если мы не можем создавать вообще, Heimdallr::Resource ответит на этот запрос ошибкой. Иначе мы получим список полей, доступных для заполнения.
Хеймдаль также объявляет метод
.creatable?, так что и его можно прокинуть через REST.Могу ли я обновлять?
Идея аналогична созданию. Только в этот раз мы объявим метод edit:
Article.restrictions(current_user).allowed_fields[:update]
В заключение
Использование Хеймдалля и его расширения, Heimdallr::Resource, поможет легко управлять правами доступа без лишнего мусора в коде. И, что немаловажно, вы получаете дополнительную магию для ваших REST-API. Помните, Хомяков следит за вами!
ಠ_ಠ