Я создаю сайт на основе сообщества в Rails для членов реальной организации. Я стараюсь придерживаться лучших практик дизайна RESTful, и большинство из них более или менее по-книжной. Проблема, заключающаяся в том, что мой мозг работает в опрятных кругах RESTful, это вопрос авторизации. Аутентификация - это легкая, долго разрешенная проблема с широко принятыми решениями RESTful, но авторизация RESTful, по-видимому, немного черная. Я пытаюсь найти подход, который обеспечит наиболее общую и гибкую структуру для контроля доступа к ресурсам, будучи как можно более простым, и все это будет соответствовать архитектуре RESTful. (Также, пони.)
Вопросы:
- Мне нужно контролировать доступ к различным ресурсам, таким как пользователи, страницы, сообщения и т.д.
- Авторизация для данного ресурса должна быть более тонкой, чем простой CRUD.
- Я хочу позволить себе и другим редактировать правила авторизации из приложения.
- Правила авторизации должны зависеть от предикатов, таких как (концептуально) Владелец (Пользователь, Ресурс) или Заблокировано (Тема)
Рассмотрение (2) - это тот, который касается меня больше всего. Кажется, существует несоответствие импеданса между моей концепцией разрешений и концепцией RESTful действий. Например, возьмите сообщения (как на доске объявлений). REST определяет существование четырех операций на ресурсе Post: создание, чтение, обновление и удаление. Легко сказать, что пользователь должен иметь возможность обновлять свои собственные сообщения, но только определенным пользователям (или ролям или группам) должно быть разрешено блокировать их. Традиционный способ представления блокировки находится в состоянии сообщения, но это приводит к запаху, который Пользователь в тех же условиях может или не сможет обновить сообщение в зависимости от (полностью допустимых) значений, которые он поставляет. Мне кажется ясным, что есть действительно два разных действия, чтобы изменить состояние Почты, и обучать их просто замаскировать нарушение принципов RESTful.
(Я должен отметить, что эта проблема сильно отличается от проблемы с отказом обновления из-за недействительных или несогласованных данных - запрос блокировки от непривилегированного пользователя в принципе вполне допустим, просто запрещен.)
Разве не разложено другое слово для гниения?
Это можно преодолеть, разложив сообщение: блокировка - это субресурс определенного сообщения, а для создания или уничтожения один может иметь отдельные разрешения. У этого решения есть кольцо REST, но оно приносит с собой как теоретические, так и практические трудности. Если я определяю блокировки, то как насчет других атрибутов? Предположим, я решил, в припадке каприза, разрешить только члену Администратора изменять заголовок сообщения? Простое изменение авторизации потребует реструктуризации базы данных для ее размещения! Это не решение. Чтобы обеспечить такую гибкость в стратегии декомпозиции, требуется, чтобы каждый атрибут был ресурсом. Это немного дилемма. Мое неявное предположение заключалось в том, что ресурс представлен в базе данных в виде таблицы. В этом предположении ресурс для каждого атрибута означает таблицу для каждого атрибута. Ясно, что это нецелесообразно. Однако, чтобы удалить это предположение, возникает несоответствие импеданса между таблицами и ресурсами, что может открыть собственную банку червей. Использовать этот подход потребовало бы гораздо более глубокого рассмотрения, чем я дал. Во-первых, пользователи разумно ожидают, что смогут редактировать сразу несколько атрибутов. Куда идет запрос? К самому маленькому ресурсу, который содержит все атрибуты? Для каждого отдельного ресурса параллельно? К луне?
Некоторые из этих вещей не похожи на других...
Предположим, что я не разлагаю атрибуты. Альтернативой тогда, кажется, является определение набора привилегий для каждого ресурса. На данный момент, однако, однородность REST теряется. Чтобы определить правила доступа к ресурсу, система должна обладать конкретными знаниями об этих ресурсах. Кроме того, теперь невозможно в общих чертах распространять разрешения на ресурсы потомков, даже если дочерний ресурс имеет привилегию с тем же именем, нет никакой врожденной семантической связи между привилегиями. Определение REST-подобного набора стандартных привилегий представляется мне наихудшим из обоих миров, поэтому я придерживаюсь отдельной иерархии разрешений для каждого типа ресурсов.
Хорошо, мы сделали нос. И шляпа. Но это ресурс!
Одно из предположений, которое я видел, смягчает некоторые из недостатков вышеупомянутого подхода, заключается в определении разрешений, таких как создание/удаление ресурсов и чтение/запись атрибутов. Эта система является компромиссом между атрибутами-ресурсами и привилегиями для каждого ресурса: по-прежнему остается только CRUD, но для целей авторизации чтение и обновление относятся к атрибутам, которые можно рассматривать как псевдоресурсы. Это обеспечивает многие практические преимущества подхода "атрибуты-ресурсы", хотя концептуальная целостность в определенной степени скомпрометирована. Разрешения могут распространяться от ресурса к ресурсу и от ресурса до псевдо-ресурса, но никогда от псевдо-ресурса. Я не полностью изучил последствия этой стратегии, но кажется, что это может быть многообещающим. Мне приходит в голову, что такая система лучше всего будет функционировать как неотъемлемая часть Модели. Например, в Rails это может быть модификация ActiveRecord
. Для меня это выглядит довольно радикально, но авторизация является такой фундаментальной сквозной проблемой, что это может быть оправдано.
О, и не забывайте о пони
Все это игнорирует проблему предикативных разрешений. Очевидно, что Пользователь должен иметь возможность редактировать свои собственные сообщения, но никого другого. В равной степени очевидно, что в таблице администраторских разрешений не должно быть отдельных записей для каждого пользователя. Это вряд ли является необычным требованием - трюк делает его общим. Я думаю, что вся необходимая функциональность могла бы быть получена, если бы предикативы правил были предикатами, так что возможность применения правила могла быть решена быстро и немедленно. Правило "allow User write Post where Author(User, Post)
" переведет "for all User, Post such that Author(User, Post), allow User write Post
" и "deny all write Post where Locked(Post)
" на "for all Post such that Locked(Post), deny all write Post
". (Было бы замечательно, если бы все такие предикаты могли быть выражены в терминах одного Пользователя и одного Ресурса.) Концептуально результирующие "окончательные" правила были бы не предикативными. Это ставит вопрос о том, как реализовать такую систему. Предикаты должны быть членами классов Model, но я не уверен, как можно грамотно относиться к ним в контексте правил. Чтобы сделать это безопасно, потребовалось бы какое-то отражение. Здесь снова возникает ощущение, что для этого потребуется модернизация реализации модели.
Как вы повторяете это снова?
Последний вопрос - как лучше всего представить эти правила авторизации в качестве данных. Таблица базы данных может сделать трюк с столбцами enum для allow/deny и C/R/U/D (или, возможно, CRUD-бит? Или, возможно, {C, R, U, D} × {allow, deny, inherit}?), и столбец ресурсов с контуром. Возможно, в качестве удобства, "наследовать" бит. Я в недоумении до предикатов. Отдельная таблица? Конечно, много кеширования, чтобы помешать ему быть слишком нехорошим.
Я предполагаю, что этого много, чтобы просить. Я попытался сделать домашнее задание, прежде чем задавать вопрос, но на данный момент мне действительно нужна внешняя перспектива. Я был бы признателен за любые данные, которые могут возникнуть у любой из вас.