Авторизация действий

Авторизация действий #

Приложению необходимо выполнять авторизацию действий пользователя. В рамках учебных приложений реализуем одну из простых схем: выделение списка действий с дальнейшей их группировкой в роли для пользователей.

Формирование списка действий #

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

  • Для выполнения действия необходимо знать человека, который его инициировал. Например при создании объекта необходимо сохранить авторство.
  • Выполнение действия должно быть доступно только выделенной группе людей, например владельцам портала.

В рамках приложения по управлению треугольниками можно выделить следующие действия:

  • Просмотр списка треугольников.
  • Просмотр информации о конкретном треугольнике.
  • Добавление нового треугольника в систему.
  • Просмотр списка пользователей.
  • Добавление нового пользователя.

В приложении можно выделить следующие 3 роли:

  • Неавторизованный пользователь, гость.
  • Авторизованный пользователь.
  • Администратор системы.

Распределим возможности по выделенным ролям:

Функция Гость, неавторизованный пользователь Авторизованный пользователь Администратор
Просмотр списка треугольников Да Да Да
Просмотр информации о конкретном треугольнике Да Да Да
Добавление нового треугольника в систему Нет Да Да
Просмотр списка пользователей Нет Нет Да
Добавление нового пользователя Нет Нет Да

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

Хранение ролей в приложении #

Можно воспользоваться двумя подходами:

  • Описать права роли в перечислении внутри приложения.
  • Описать права роли в хранилище (базе данных).

Вне зависимости от выбранного подхода к реализации описания ролей, в списке пользователей необходимо будет добавить идентификатор роли. Таким образом удобно будет поместить описание роли внутрь хранилища (базы данных) и работать с ней как с любой другой информацией в приложении.

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

С точки зрения приложения для работы с треугольниками права можно описать следующим классом данных:

data class Permissions {
  val canAddTriangle: Boolean = false,
  val canListUsers: Boolean = false,
  val canAddUser: Boolean = false,
}

Значения по умолчанию подходят для роли гостя.

Связывание роли и пользователя #

Веб-приложение при каждом запросе к нему должно выполнять процедуру аутентификации пользователя. Обычно она выполняется в фильтрах и использует сессионные токены, расположенные в куках. На основании этой информации следует проводить вычислять роль пользователя. Удобное местоположение — фильтр, следующий за фильтром аутентификации.

Результат вычисления списка разрешений, объект класса Permissions, следует расположить внутри контекстных данных запроса.

Таким образом для реализации фильтра, вычисляющего разрешения для конкретного пользователя, необходимо передать:

  • Линзу для получения объекта, описывающего аутентифицированного пользователя.
  • Операцию для получения объекта, описывающего права (роль) текущего пользователя.
  • Линзу для записи информации о разрешениях пользователя.

Рекомендуется сделать так, чтобы операция по формированию объекта с разрешениями, всегда возвращала корректный объект, который можно записать в контекст запроса. Т.е. по умолчанию необходимо предоставлять объект, в котором записаны пустые полномочия. Это будет соответствовать возможностям неавторизованного пользователя.

Уровень реализации авторизации #

Авторизация в приложении может быть реализована на различных уровнях:

  • Авторизация на уровне маршрутизатора.
  • Авторизация на уровне обработчика запроса.
  • Авторизация внутри конкретной операции по доступу к данным приложения.

Также необходимо помнить, что внутри пользовательского интерфейса не надо предоставлять возможности по выполнению действий, которые не доступны текущей роли. Это является желаемым уровнем защиты, но не является достаточным. Проверку на стороне сервера, внутри логики обработки HTTP-запросов всё-равно необходимо выполнять.

Таким образом информация из контекста запроса должна учитываться на уровне обработки HTTP-запросов и на уровне шаблонизатора.

Проверка авторизации на уровне маршрутизатора #

Для реализации проверок на уровне маршрутизатора можно использовать специальный фильтр, который следует вызывать перед конкретным обработчиком запроса. Для работы фильтра ему необходимо передать:

  • Линзу для извлечения информации о правах текущего пользователя.
  • Функцию, которая будет отвечать на вопрос: разрешено ли выполнить нужную нам операцию.

Внутри фильтра необходимо получить ссылку на объект, описывающий разрешения пользователя, применить к нему функцию по проверке прав. Если функция вернула ложь, то необходимо вернуть ответ с кодом 403, доступ запрещён. Если функция вернула правду, то необходимо передать задачу по обработке запроса следующему обработчику.

Таким образом интерфейс функции по созданию фильтра будет выглядеть следующим образом:

fun permissionFilter(
    permissionLens: RequestContextLens<Permissions>,
    canUse: (Permissions) -> Boolean,
)

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

routes(
    "/user/new" bind Method.GET to permissionFilter(permissionLens, Permissions::canAddUser).then(
        addNewUserHandler(...)
    )
)

Но если простого чтения свойства из класса недостаточно, то можно описать функцию произвольной сложности.

Учёт разрешений в обработчике HTTP-запроса #

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

В такой ситуации проверку прав на выполнение действия логично перенести внутрь обработчика HTTP-запроса, т.к. необходимо обратиться к хранилищу данных для получения всей информации, на основании которой можно выполнить решение: разрешать редактирование информации или не разрешать.

Для реализации данной схемы необходимо обработчику HTTP-запроса передать линзы для получения идентификатора пользователя и вычисленных прав доступа. С их помощью из контекстного хранилища необходимо получить данные о пользователе. Затем эти данные необходимо передать в операцию по определению прав на выполнение целевой операции.

Учёт разрешений в виде #

Для учёта разрешений на уровне представления необходимо передать линзу для получения объекта со списком разрешений из контекста запроса.

Задача #

Реализуйте систему для проверки прав доступа внутри приложения по управлению треугольниками.

Для этого необходимо:

  1. Добавить новое хранилище для сохранения разрешений для каждой роли.
  2. Заполнить хранилище двумя записями: авторизованный пользователь и администратор.
  3. Связать существующие учётные записи с одной из данных ролей.
  4. Добавить новый фильтр для получения списка разрешений для конкретного пользователя.
  5. Интегрировать данный фильтр в цепочку фильтров, установить его после фильтра аутентификации.
  6. Модифицировать маршрутизатор приложения таким образом, чтобы соответствующие маршруты были доступны только при наличии соответствующего разрешения.
  7. Модифицируйте подсистему отображения HTML-документов так, чтобы она не показывала ссылки на маршруты, которые не доступны при текущем уровне доступа.
  8. Проверьте, что приложение по-разному показывает интерфейс:
    • если пользователь не вошёл в систему;
    • когда пользователь авторизовался как обычный пользователь;
    • когда пользователь авторизовался как администратор.
  9. Выйдите из системы. Попытайтесь открыть список пользователей. Как повела себя система?
  10. Доработайте процедуру добавления новых пользователей, чтобы администратор мог указать роль нового пользователя.

© A. M. Васильев, 2024, CC BY-SA 4.0, andrey@crafted.su