Pull to refresh

Comments 13

Пример ты понял правильно. Но решение — так себе. Casbin тут занимается обычным RBAC, а нам нужен был ABAC. А ABAC-аспект про owner-а статьи — вынесен в хард-код.

Даже хуже — можно было бы ввести разрешение на действие «list_all_articles», и дать его админам, а без этого этот Casbin чисто заменяет табличку с ролями, которая зачем-то из БД (где к ней можно прикрутить админку, например), переехала в загадочный CSV.

Это мы еще не трогали случаи:
— когда есть вложенные сущности. Скажем, картинки в статье. И надо к ним давать доступ только если пользователь имеет доступ к статье
— когда есть атрибуты у сущности, доступ к которым ограничен. Вроде тут можно просто — проверяем есть ли доступ, если нет — отдаем null через API. Но при этом, если по такому полю возможна сортировка — то можно по порядку сортировки догадаться что было в этом поле, т.к. сортироваться будет на стороне БД, которая ничего не знает про доступ к полю
— когда есть всякие рекурсивные штуки, типа «человека видит его менеджер, менеджер менеджера, и т.д.»

Может быть это у меня специфика такая — бизнес-приложения для энтерпрайза. А где попроще, и подходит RBAC — такая штука и пойдет.

Но для моих кейсов, мне критически надо вычислять роли прямо в БД, чтобы отсекать доступ прямо там.

И это нормально делается без всяких библиотек. Если надо считать на сервере — просто выносим логику в функции типа:

GetArticlePermissions(article: Article) {
   let isOwner = article.Owner == user.Id;
   return new { 
      CanRead: user.IsAdmin || isOwner,
      CanEdit: user.IsAdmin || isOwner,
      ...
   }
}


Дальше этот код можно звать чтобы:
— проверить может ли человек сделать что-то
— отдать на UI чтобы показать флажки

А чтобы втаскивать какие-то условия в БД, делается трюк с Expression, типа так:

const CanReadExpr: Expression<Func<Article, bool>> = (article: Article) => user.IsAdmin || article.Owner = user.Id; // это можно юзать в LINQ: .Where(CanRead)

const CanRead: Func<Article, bool> = CanReadExpr.Compile(); // а это можно вызывать в обычном коде, в том же GetPermissions()


На деле все еще сложнее:
— надо «жонглировать» Expression-ами — упрощать их (если админ — нечего засорять SQL ненужными условиями), добавлять только нужные термы (если мы дергаем API админки, куда можно только админам — нечего проверять на owner-а
— надо аккуратно вклеить это все в код, чтобы
— невозможно было бы забыть добавить проверки
— если мы тащим в API parent-сущность, а в ней — вложенные, то не обязательно проверять правила про parent-а для вложенных еще раз — мы же уже достали parent-а и проверили.

Так что мне вообще непонятно, зачем хард-код — со строгой типизацией, читаемый, и гибкий, и решающий все мои задачи — надо заменять каким-то загадочным DSL, который надо хакать от входа.
Может решение которое я здесь привел и не самое изящное. Но в целом ценность такой библиотеки в переносе правил авторизации на уровень конфигурации с уровня кода, что позволяет более гибко ими управлять, как сказал один комментатор. Плюс бизнес-правила хранятся в одном месте.
В вашем же подходе — получается спагетти. Бизнес-правила размазаны, и зашиты в SQL. Те сложности которые вы перечисляете — более легко и изящно решаются как раз-то с использованием Casbin, нежели если лепить собственный велосипед, который вы предлагаете.
Мы же оба видим в твоем примере что ABAC занимается не Casbin, а хард-код — тот самый if(role == «Admin»). Ты уже не можешь добавить через конфигурацию правила вроде «а еще у статьи есть аттрибут reviewerId, и мы ревьюверам доступ на чтение, редактирование, а еще — и на публикацию статей». Тебе придется опять лезть и хардкодать это. Т.е. задача перенести авторизацию в конфигурацию — не решена.

Ну и второе — я искренне не понимаю негативный контекст в словосочетании «хард-код». Почему у меня сразу будет «велосипед» и «спагетти»? Почему заменяя один язык на другой, мы почему-то сразу делаем что-то правильное? Почему просто на C# нельзя все сделать аккуратно, вынести все правила в один сервис, построить какой-нибудь eDLS если требуется? Почему какой-то странный DLS — без типизации, без каких-либо compile-time проверок, с необходимостью всей командой учить его — почему это считается крутым? Чем таким волшебным отличается файлик .cs от файлика с другим расширением в репозитории?

Можно ещё добавить группы пользователей с определённой контекстной ролью по отношению к сущности. Например, группа исполнителей поручения с правами чтения и группа замещающих автора с правами редактирования). Группы могут включать другие группы и пользователей.

Да. И, что важно в контексте, «группа» — скорее всего будет не просто абстрактной «группой», это будет «склад» с атрибутом «начальник», или «отдел», или «грузовик» с атрибутом «водитель».

Авторизация делается поверх существующей модели предметной области, очень сильно с ней пересекается. И все что касается групп и ролей — живет в БД вместе с остальными данными.

Casbin пытается отделить авторизацию, пытается забрать в себя роли и группы — такой для бизнес-приложений — обречён.

Я не говорю что авторизаци прям невозможно вытащить в библиотеку. Но делать это надо нежнее — не забирая к себе данные, а глубоко интегрируясь в Data Access Layer приложения.
Спасибо что прочитали. Да, хороший вариант. Можно решить и через пользовательские функции.

Пример с article это RBAC модель контроля доступа к типу сущности, но не самой сущности — entity (описание терминов см. https://en.m.wikipedia.org/wiki/Entity%E2%80%93relationship_model раздел Entity–relationship model).
Наиболее близкий пример к DAC (https://en.m.wikipedia.org/wiki/Discretionary_access_control) это Owner. Но он реализован на уровне SQL. Casbin тут не помогает. Пока вывод, что Casbin хорош для RBAC.


Наиболее близкую реализацию DAC я пока встречал только в Spring Security ACL (https://docs.spring.io/spring-security/site/docs/3.0.x/reference/domain-acls.html)

Пока вывод, что Casbin хорош для RBAC.

Я не согласен с такой формулировкой. Это не совсем верно. Casbin хорош для RBAC, но кроме этго, он хорош и для другим моделей. Будь то ACL, ABAC либо их комбинации. Casbin хорош для как ядро механизма авторизации. Он поддерживает разные модели не только RBAC. Casbin позволяет создавать свои собственные модели авторизации.
Глупо ожидать от Casbin выборки значений — такие ожидания — это признак того, что вы не поняли предназначение этой библиотеки.

Ок, не только RBAC. Повторюсь, для моделей ABAC и ACL (точнее DAC) Casbin практически применим только для контроля доступа к единичным объектам. Ожидать от Casbin формирования фильтров запросов для полноценной поддержки DAC и ABAC не буду, а жаль. В CUBA DataManager, например, делается нечто подобное. PERM не плохая основа для вывода оптимизированных фильтров.

Мы для себя сделали вывод, что применять Casbin для защиты многих тысяч сущностей, которые создаются в ходе работы системы, не очень себе идея - как раз потому, что нет ясного подхода по работе с эффективными полномочиями. То есть декларативный подход и PERM прекрасны для случая, когда знаешь, какие сущности надо защитить (и как они будут связаны), еще в ходе разработки, а не на момент эксплуатации.

А когда у тебя дерево например объектов, которые создал пользователь, доступ на которые раздали в рантайме, то лучше старые добрые ACL.

В результате была написана библиотека Redberries OBAC (https://github.com/redberriespro/Redb.OBAC), суть которой - раздавать права на иерархические структуры объектов и вычислять для каждого объекта иерархии эффективные полномочия. Посчитанные полномочия можно запросить у библиотеки, а можно и принять в прикладной код специальным ресивером и положить например в БД.

В результате, получение списков из БД с одновременным применением проверки доступа выглядит как-то так:

 var docs = from d in ctx.Documents
 join p in ctx.EffectivePermissions
   on d.Id equals p.ObjectId
 where
   p.UserId == userId && p.PermissionId == PERM_READ
 select d;


Пример на C# и Entity Framework, вот тут весь он: https://github.com/redberriespro/Redb.OBAC/blob/main/Examples/HelloObacEf/Program.cs

С моей точки зрения вы сделали ошибочный вывод и изобрели очередной велосипед. Ибо в той компании в которой я работаю, мы разрабатываем no-code, low-code систему в которой пользователь как раз то может создавать тысячи сущностей (классов данных), с сотнями тысяч, если не миллионами записей (объектами), и защита идет как на уровне сущностей, так и на уровне записей. И архитектура разрешений и политик построена на базе библиотеки Casbin. Делали нагрузочное тестирование, и система прекрасно справляется как с выборкой больших коллекций данных с учетом прав доступа, так и единичными проверками разрешений.

Может вы просто готовить не умете правильно эту библиотеку?

OBAC, несомненно, велосипед по сравнению с Сasbin, которая по сути, сейчас стандарт де-факто для атрибутированного контроля доступа в опенсорсе.

Имеет ли смысл решать ту же задачу по-другому и делать генератор эффективных полномочий или на Сasbin можно сделать все то же самое и даже лучше - это хороший вопрос.

Точнее даже три вопроса:

  • Производительность назначения прав ACL

  • Производительность проверки эффективных полномочий

  • Удобство использования

Насчет померять производительность - я планирую сделать бенчмарк OBAC-а, на каком-нибудь не самом тривиальном случае, навроде такого:

  • 5 уровней в иерархии объектов

  • 5 типов проверяемых полномочий

  • 5 записей ACL на каждый уровень, с назначением доступа пользователям и группам

  • запретительные и разрешительные полномочия. Тут не знаю, как будет правильно, но как идея, для каждого из 5 типов полномочия в цепочке наследования должны попадаться

    • 1 назначение через юзера (allow)

    • 1 назначение через группу (allow)

    • 1 назначение через юзера (deny)

    • 1 назначение через группу (deny)

  • кол-во объектов значимое, например по 10000 в узле нижнего уровня, на каждом уровне по 2 sibling-a (то есть узлов 2/4/8/16 узлов на уровнях 1-4 и 160тысяч объектов нижнего уровня)

  • 10 000 пользователей и 50 групп по 500 пользователей в каждой

и собственно предмет исследования - оконная функция (сортировка+пейджинг) по узлам нижнего уровня.

В случае OBAC очевидно, что скорость сортировки и пейджинга будет эквивалентна одному дополнительному джоину (см мой пример выше), основные ресурсы будут тратиться в момент заполнения ACL, вычисления и сохранения эффективных полномочий.

В случае Casbin/PERM, попробую применить ваши методики.
Самому интересно, что получится.

Sign up to leave a comment.

Articles