Русский
Русский
English
Статистика
Реклама

ГибкаяавторизацияспомощьюCasbinиPERM.Практический пример

После написания предыдущей статьи по языку PERM и библиотеке Casbin, возникли вопросы. Причем не у одного человека, и я хотел ответить сначала в комментарии, но понял, что объем материала выходит за рамки обычного комментария, поэтому изложу этот ответ в виде отдельной статьи.


Я долго не мог понять ту конкретную конструкцию, которая сидела в голове у вопрошающих, но в конечном итоге после уточняющих вопросов получил ответы, компиляцию которых приведу в цитате.


А как с такими DSL решается задача показать список объектов, которые я могу видеть? Надо же в SQL-запрос это как-то транслировать, не выгребать же все записи с БД.

Есть интерфейс на сайте, показывающий список чего-либо. Скажем статей в админке CMS. Статей с базе десятки тысяч, но обычно пользователь имеет доступ только к десятку. Как достать из БД статьи, которые видны конкретному пользователю? Ну, если мы все правила что кому видно вынули из кода в какой-то DSL?
Другими словами как написать запрос типа

select * from articles a
join roles r on r.userId = currentUserId
where article.owner = currentUserId
OR (r.role in ['admin', 'supevisor']) админ всего
OR (r.domain = currentDomainId AND r.role in ['domain-admin', 'domain-supervisor']) админ домена

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

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


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


Описание задачи


У нас имеется CMS, в которую пользователи через админпанель могут добавлять статьи. В адмике пользователь с ролью user может видеть только свои статьи. Статьидругихлюдейоннеможетвидеть, если только ему не присвоена роль admin или supervisor. Пользователь с ролью supervisor может видеть и редактировать все статьи, а admin имеет все те же права что и supervisor, но кроме этого, еще может и удалять любые статьи.


Структура, схема и содержимое БД:


Структура БД нашей CMS:
Схема таблиц БД


Содержимое таблицы пользователей Users
Содержимое таблицы Users


Пользователям присвоены следующие роли
Содержимое таблицы Roles
Содержимое таблицы Roles


Как мы видим, Piter является администратором, а Bob супервизором. Alice обычный пользователь, может видеть, создавать и редактировать только свои статьи.


Содержимое таблица с статьями Articles
Содержимое таблицы Articles


Исходя из вопроса, выборка осуществляется для администратора (Piter, id=3) таким образом:


select * from articles aleft join roles r on r.userId = 3where a.owner = 3OR (r.role in ('admin', 'supevisor'))

Результат выборки для администратора


Выборка для супервизора (Bob, id=2) таким образом:


select * from articles aleft join roles r on r.userId = 2where a.owner = 2OR (r.role in ('admin', 'supevisor'))

Результат выборки для супервизора


А выборка для пользователя (Alice, id=1) выглядит так:


select * from articles aleft join roles r on r.userId = 1where a.owner = 1OR (r.role in ('admin', 'supevisor'))

Результат выборки для пользователя


Давайте теперь попробуем использовать другой подход к авторизации, на базе библиотеки Casbin.


Подход с использованием Casbin


Для начала давайте определимся что ресурс в подходе PERM это не столько экземпляр сущности, сколько сама сущность.
Т.е. когда мы описываем модель авторизации, под ресурсом в нашем примере подразумевается сама сущность (таблица) Статья. А не конкретная запись из этой таблицы (с Id=1 например).


Дальше необходимо уточнить, что те роли, которые используются в описании этой задачи это не классические роли из подхода RBAC.
Роли RBAC описывают те разрешения, которые можно выполнить с сущностью. Например в классическом RBAC роль user могла бы только читать статьи, роль author могла бы наследовала роль user (т.е. чтение статей), и еще могла бы редактировать создавать новые статьи, а роль admin могла бы наследовать все предыдущие разрешения и плюс еще удалять статьи.
В описанной же нами выше задаче, по сути эти все роли не отличаются друг от друга. И user и supervisor и admin имеют одни и те же права, один набор разрешений каждый носитель любой из ролей может создавать, редактировать или удалять статьи. Разница только в области видимости, user может видеть в админке только свои статьи, и соответственно редактировать их и удалять. А admin и supervisor не только свои, но еще и чужие.
И в этом заключается большой минус модели RBAC, так это статичная модель авторизации, и с ее помощью вообще невозможно выразить бизнес-правила, в которых используются атрибуты, значения которых заранее не известны и вычисляются в процессе работы.
Об этом подробно уже было рассказано в статье Подходы к контролю доступа: RBAC vs. ABAC


А те роли что мы используем (user, admin) это так называемые динамические роли. Термин не официальный, и зачастую каждый подразумеваем под ним свое, они реализуются различными способами, и описанное автором решение которое я привел в начале статьи один из таких подходов.


Выборка значений с учетом "динамических ролей"


Для начала давайте определим модель политики RBAC (rbac_model.conf), ее подробное описание я привел в предыдущей статье:


[request_definition]r = sub, obj, act[policy_definition]p = sub, obj, act[role_definition]g = _, _[policy_effect]e = some(where (p.eft == allow))[matchers]m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

Далее, мы убираем таблицу ролей Roles. Роли у нас теперь будут описаны в хранилище политик. Это может быть как обычный *.csv файл, так и таблица в базе данных. Для простоты я буду использовать cvs файл rbac_policy.csv:


p, user, article, readp, user, article, modifyp, user, article, createp, user, article, deleteg, supervisor, userg, admin, supervisorg, 1, userg, 2, supervisorg, 3, admin

Суть здесь такая, что мы даем роли user права на чтение, модификацию, создание и удаление статей. Затем роль supervisor наследует права роли user. А роль admin наследует права роли supervisor.
Далее пользователю alice(1) мы присваиваем роль user, bob(2) у нас supervisor, а piter(3) admin
В принципе этого достаточно, чтобы решить проблему, которую описывал автор вопроса.


Этот код конечно не для продакшена, а для демонстрации. Для продакшена я советую использовать cross-cutting concern с CQRS+MediatR


    public IList<Article> GetArticlesForAdminPanel(int currentUserId)    {        var e = new Enforcer("CasbinConfig/rbac_model.conf", "CasbinConfig/rbac_policy.csv");        var obj = "article";        var act = "read";        //Сначала проверяем, что пользователь имеет права на чтение статей        if (e.Enforce(currentUserId.ToString(), obj, act))        {            //Получаем список ролей пользователя            var currentUserRoles = e.GetRolesForUser(currentUserId.ToString());            //Проверяем, является ли пользователем админиом или супервизором            var isAdmin = currentUserRoles.Any(x => x == "admin" || x == "supervisor");            //Если админ, вернуть все записи, иначе только те, которые принадлежат пользователю            if (isAdmin) return _context.Articles.ToList();            else return _context.Articles.Where(x => x.OwnerId == currentUserId).ToList();        }        else        {            // отклонить запрос, показать ошибку            throw new Exception("403. У вас нет прав для чтения статей");           }    }

Тадам! Задача решена, ответ на вопрос дан.


Редактирование статей с учетом "динамических ролей"


Теперь же пойдем еще дальше. Мы получили список статей, отобразили в админке, и попытаемся отредактировать какую-нибудь статью. И нам соответственно надо проверить, имеем ли мы права чтобы ее отредактировать, пользователь с ролью user может отредактировать только свои статьи, supervisor и admin могут отредактировать все статьи.


Для этого определяем новую модель, называем ее rbac_with_abac_model.conf:


[request_definition]r = sub, obj, act[policy_definition]p = sub, obj, act[role_definition]g = _, _[policy_effect]e = some(where (p.eft == allow))[matchers]m = (r.sub == r.obj.OwnerId.ToString() || g(r.sub, "supervisor")) && g(r.sub, p.sub) && r.act == p.act

Данная модель не сильно отличается от модели чтения, за исключением секции [matchers], в ней мы конструкцию r.obj == p.obj заменили на (r.sub == r.obj.OwnerId.ToString() || g(r.sub, "supervisor")). Это следует читать как r.sub (id пользователя) должен совпадать с полем r.obj.OwnerId (id владельца обновляемой записи) или r.sub должен входить принадлежать группе "supervisor". Поскольку группа admin наследует все права группы supervisor то и члены группы admin будут соответствовать этому правилу.


Файл с политиками остается прежним, его мы не меняем. Теперь смотрим как это выглядит в коде:


    public void UpdateArticle(int currentUserId, Article newArticle)    {        var e = new Enforcer("CasbinConfig/rbac_with_abac_model.conf", "CasbinConfig/rbac_policy.csv");        var act = "modify";        //Проверяем, что пользователь имеет права на редактирование статьи        if (e.Enforce(currentUserId.ToString(), newArticle, act))        {            //Обновляем, и сохраняем изменения            _context.Articles.Update(newArticle);            _context.SaveChanges();        }        else        {            // отклонить запрос, показать ошибку            throw new Exception("403. Недостаточно прав");        }    }

Здесь стоит обратить внимание на то, что мы в метод e.Enforce передаем вторым параметром объект, который представляет из себя экземпляр класса Article.


Ну и последний шаг попытаемся удалить статью.


Удаление статьи


Бизнес-правило у нас здесь такое, что пользователь с ролью user может удалить свою статью, supervisor не имеет прав удалять чужие статьи, а admin такое право имеет.
Опишем теперь это бизнес-правило в модели политики PERM, в файле delete_model.conf:


[request_definition]r = sub, obj, act[policy_definition]p = sub, obj, act[role_definition]g = _, _[policy_effect]e = some(where (p.eft == allow))[matchers]m = (r.sub == r.obj.OwnerId.ToString() || g(r.sub, "admin")) && g(r.sub, p.sub) && r.act == p.act

Она не сильно отличается от предыдущей политике редактирования, за тем исключением, что удалять чужие статьи мы разрешили только роли admin. Если носитель роли supervisor попытается удалить чужую статью, у него выйдет ошибка превышения полномочий.


Как и в случае с моделью, код мало чем отличается от предыдущего примера на редактирование:


    public void DeleteArticle(int currentUserId, Article deleteArticle)    {        var e = new Enforcer("CasbinConfig/delete_model.conf", "CasbinConfig/rbac_policy.csv");        var act = "delete";        //проверяем, что пользователь имеет права на удаление статьи        if (e.Enforce(currentUserId.ToString(), deleteArticle, act))        {            //Удаляем статью            _context.Articles.Remove(deleteArticle);            _context.SaveChanges();        }        else        {            // отклонить запрос, показать ошибку            throw new Exception("403. Недостаточно прав");        }    }

Резюме


Надеюсь, данный пример продемонстрировал гибкость, универсальность и удобство использования библиотеки Casbin и языка PERM для построения решений по разделению доступа и авторизации.


Еще отмечу, что как сами модели политики, так и правила политики могут храниться в БД. И предусмотрена возможность фильтрации правил, если их большое множество, и это может стать узким местом в высоконагруженных приложениях.


Casbin под капотом использует библиотеку DynamicExpresso.Core для интерпретации простых выражений C# при сопоставлении правил политик с входными значениями, что позволяет эффективно использовать Casbin даже в самых сложных сценариях авторизации.


Не смотря на свою молодость, Casbin активно развивается, используется во множестве проектов, обрастает полезными инструментами и API. Такими например как UI для управления политиками.


Полностью работоспособный и самодостаточный код примера, который я использовал для написания данной статьи я разместил у себя на Github, можете скачать и поиграться, если есть интерес и желание.


Полезные ссылки


Источник: habr.com
К списку статей
Опубликовано: 02.02.2021 02:11:38
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Информационная безопасность

Разработка веб-сайтов

Java

Анализ и проектирование систем

Net

Abac

Rbac

Acl

Perm

Pml

Авторизация

Динамические роли

Casbin

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru