Урок 19. Динамические права доступа

В прошлом уроке мы рассмотрели статические права доступа, когда разработчик через серверный xml-файл настраивает разрешения для групп пользователей. Но платформа Workflow Technology предусматривает возможность использования в приложении динамических прав доступа. Таким образом, пользователи через интерфейс программы могут настраивать разрешения для групп.

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

Динамические права доступа

Настройка динамических прав доступа вынесена из серверной xml в базу данных.

Для этих целей существуют таблицы template.permission и template.group_permission. В первой мы должны перечислить имена всех Permission из серверного xml-файла, а во второй задать соответствие групп пользователей и доступных им Permission.

Описание Roles и Groups в серверном xml-файле теперь необязательно, но они не будут противоречить динамическим правам доступа. При совместном использовании статических и динамических прав доступа используется принцип разрешения. Иными словами, если в xml-файле у группы нет прав на выполнения какого-то запроса, а в таблице в базе данных такие права есть, то сервер будет разрешать пользователю выполнять этот запрос.

Подготовка Permission

Первым делом скорректируем набор Permission в серверном xml-файле: какие-то разрешения мы объединим, чтобы уменьшить их количество, а какие-то мы дополним необходимыми запросами. Таким образом, нам будет проще в дальнейшем настроить интерфейс для динамических прав.

Перейдем в файл серверной xml (Template.xml).

MaterialViewPermission

Скорректируем MaterialViewPermission, добавив запрос на получение списка единиц измерений:

Template.xml
<Permission Name="MaterialViewPermission">
  <AccessPoint Name="MaterialViewAccessPoint" />
  <SqlQuery Name="MaterialSelectSqlQuery" />
  <SqlQuery Name="MaterialByIdSelectSqlQuery" />
  <!-- Дополнительно -->
  <SqlQuery Name="UnitSelectSqlQuery" />
</Permission>

Таким образом, в разрешении на просмотр списка ТМЦ мы будем сразу получать доступ к необходимому списку единиц измерений. При этом у группы пользователей может не быть доступа к UnitViewPermission.

OrderViewPermission

Скорректируем OrderViewPermission: перенесем необходимые запросы из OrderPaymentViewSqlQueryPermission, OrderPositionViewSqlQueryPermission, OrderPositionMaterialViewSqlQueryPermission, CityOrderViewSqlQueryPermission и ClientOrderViewSqlQueryPermission. А сами эти разрешения удалим.

Так же добавили запрос AccountSelectSqlQuery для оплат в заказе.

Итоговый синтаксис разрешения будет иметь вид:

OrderEditPermission

Подобным образом скорректируем и OrderEditPermission, объединив с OrderPaymentEditSqlQueryPermission и OrderPositionEditSqlQueryPermission:

CashViewPermission

В CashViewPermission так же добавим необходимые запросы AccountSelectSqlQuery и OperationCashSelectSqlQuery:

ClientViewPermission

А в ClientViewPermission добавим запрос на список городов CityShortSelectSqlQuery:

Permission в базе данных

Помимо системных таблиц (template.permission и template.group_permission) создадим пару дополнительных таблиц, которые упростят нам работу с динамическими правами:

  • template.permission_block - будет описывать категории разрешений. Например, "Списки" или "Финансы";

  • template.permission_block_item - будет описывать группу разрешений, необходимых для чтения и редактирования списка сущности. Например, "Клиенты", "Заказы" или "Касса".

Выполним скрипт создания таблиц:

Обратите внимание, что в таблицах устанавливаем ограничение уникальности значений в полях id_title.

Внесем корректировку в таблицу template.permission:

Заполним таблицы данными:

Таким образом, мы сформировали 7 основных блоков (категорий разрешений), в которых определили 15 групп, добавили имена всех разрешений из серверного файла и распределили их по группам.

В запросах мы создаем блок "Группы пользователей" и добавляем в него два разрешения GroupViewPermission и GroupEditPermission. Они пригодятся нам дальше, когда будем реализовывать возможность пользователям самим добавлять в программу новые группы пользователей.

У группы UserGroup в таблице template.group удалим значение из колонки name. Тем самым уберем группу из системных и дадим возможность настраивать ей права доступа через интерфейс программы. Можем для группы "Пользователи" изменить описание на "Настраиваемый доступ". Не забудьте удалить с колонки name ограничение NOT NULL.

Добавим группе "Администраторы" (AdministratorGroup) все права доступа, а группам "Пользователи" и "Гости" (GuestGroup) только общие права доступа - BaseViewPermission.

Вернемся в серверный xml-файл (Template.xml) и удалим тэги <Roles> и <Groups> - теперь будем работать только через динамические права доступа, чтобы не возникало путаницы с разрешениями прав доступа.

Добавим в Template.xml тэг <UserSettings> сразу после открывающего тэга <Workflow>:

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

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

Для группы "Пользователи" будут падать предупреждения, что у пользователя нет прав доступа к запросам CitySimpleSelectSqlQuery и ClientSimpleSelectSqlQuery. Чтобы исправить, вынесем их в новую команду AdditionalQueriesForOrdersDataConnectionRefreshCommand и добавим на нее условие OrderViewAccessPointCondition:

Добавим вызов новой команды во все места, где вызывается команда AllPrimaryGetDataConnectionRefreshCommand.

Список групп пользователей

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

Форма списка

Создайте форму для списка групп пользователей:

На главной форме (TemplateStart.xml) в меню добавьте пункт Администрирование -> Группы пользователей..., по которому будет открываться новая форма.

Скорректируем текст запроса для списка групп:

GroupSelectSqlQuery
GroupArchiveUpdateSqlQuery

Не забудьте сразу создать необходимые AccessPoint и добавить их на формы.

Создайте функции для удаления и работы с архивом:

template.group_try_delete(smallint)
template.group_try_archive(smallint, boolean)

На форму списка групп пользователей (TemplateGroupList.xml) в таблицу добавьте колонки:

Колонка System необходима для того, чтобы ограничить возможность удалять и отправлять в архив системные группы. Создайте соответствующее условие и реализуйте эти ограничения. Значения из этой колонки будем передавать в одноименный параметр на форму редактирования группы.

Добавьте проверку результата команды архивирования группы пользователей. Переделайте проверку результата команды удаления.

Карточка группы

Перейдем в файл карточки группы (TemplateGroupEdit.xml) и добавим шрифт, который пригодится в таблице прав доступа:

Как говорилось ранее, карточка группы будет работать в двух режимах:

  • Полный доступ - для системных групп, которым права доступа задает разработчик. Таким группам пользователь сможет менять отображаемое имя и описание. Например, группа "Администраторы", у которой полный доступ ко всем данным и формам в приложении;

  • Настраиваемый доступ - для пользовательских групп, которым пользователь сам настраивает права доступа.

Запросы

Перейдем в файл Template.xml и скорректируем текст запроса GroupByIdSelectSqlQuery:

Добавим запрос на получение дерева прав доступа с пометкой выбранных прав доступа для редактируемой группы:

В запросе используется функция:

Добавьте запрос PermissionBlockItemSelectSqlQuery в GroupViewPermission.

Форма

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

Создайте на форме объекты TitleTextBox (Наименование), DescriptionTextBox (Описание) и PermissionPanel, в которой будет располагаться таблица PermissionDatabaseTable с заголовком PermissionLabel (Настраиваемые права доступа):

Где PermissionBlockItemPrimaryGetDataConnection имеет вид:

Запустите приложение и проверьте отображение объектов формы:

Так же проверьте отображение списка возможных прав доступа:

Скорректируем логику выбора прав доступа в таблице, чтобы было проще работать с таблицей. Реализуем два момента:

  • Когда ставим галочку в строке с заголовком блока (Например, по строке "Отчеты"), то галочки должны проставиться во все строки этого блока. И наоборот, если галочку снимаем с заголовка блока, то должны сняться галочки со всех строк блока;

  • Когда поставили галочки на всех строках блока, то автоматически должна проставиться галочка на заголовке блоке. Если сняли галочку хотя бы с одной строки блока, то должны снять галочку и с заголовка блока.

Нам понадобится условие проверки изменения значения ячейки в колонке (CellValueChangedCondition):

Добавим условие проверки, что в выбранной строке в колонке PermissionBlockItemId пустое значение, то есть пользователь кликнул по строке заголовка блока:

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

С особенностями универсального значения <Array> мы уже немного знакомы. В разделе "Дополнительно" из блока "Основной" в статье Array разбирали несколько примеров. Если Вы пропустили эту статью, советуем сначала прочитать ее, а затем вернуться к уроку и продолжить выполнение задания.

Ниже подробно рассмотрим <Array> из нового условия.

Из таблицы PermissionDatabaseTable с помощью get-проперти DictionaryArrayData получаем словарь массивов со значениями каждой колонки, который передаем в тэг <Source> в качестве источника для <Array>.

Из полученного словаря нам интересны только три колонки (PermissionBlockId, PermissionBlockItemId и Checked), значения которых достаем в тэге <Select> по имени и формируем матрицу значений.

Затем фильтруем матрицу и оставляем только те строки, у которых значение 0-го элемента (колонка PermissionBlockId) совпадает со значением в колонке PermissionBlockId выделенной строки и значение 1-го элемента (колонка PermissionBlockItemId) не является пустым.

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

С помощью тэга <Distinct> мы формируем массив уникальных значений по 2-ому элементу (колонка Checked).

После с помощью тэга <Count> считаем их количество, которое передается в условие и сравнивается с единицей.

Если все строки имеют одинаковое значение в колонке Checked, то тэг <Count> вернет значение 1, и значение условия PermissionCheckedAllCondition будет True.

Теперь создадим команду, которая будет всем строкам блока в колонку Checked проставлять значение выделенной строки:

Эту команду будем вызывать, если условие PermissionCheckedAllCondition имеет значение True.

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

В этой команде для параметра RowIndex с помощью <Array> находим индекс строки заголовка блока для выделенной строки.

Создадим Execution, в котором соберем все условия и команды:

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

Запустите приложение и проверьте работу формы TemplateGroupEdit.xml.

Сохранение прав доступа

Скорректируем сохранение изменений для прав доступа.

Чтобы извлечь из таблицы массив идентификаторов разрешений (PermissionBlockItemId), которые пользователь выбрал для группы, воспользуемся get-проперти FilteredColumnValues объекта DatabaseTable:

В параметр ColumnName передаем имя колонки, из которой хотим получить значения. А в параметр Filter укажем условие выборки строк: в колонке Checked должно стоять значение true, и значение в колонке PermissionBlockItemId не должно быть пустым.

Эту конструкцию укажем в качестве значения нового параметра для GroupInsertSetDataConnection и GroupUpdateSetDataConnection:

Обратите внимание, что у тэга <Parameter> появился атрибут SendAsArray со значением True, таким образом, значение параметра будет передаваться как массив, и в запросе вместо переменной будет подставляться конструкция ARRAY[..] с переданными значениями. В противном случае на сервер передавался бы первый элемент массива.

Скорректируем запросы сохранения изменений:

Для всех новых групп сразу добавляем разрешение по умолчанию (by_default).

Так как на таблице template.group_permission стоит ограничение уникальности пары group_id и permission_id, то в INSERT-запрос добавлена инструкция ON CONFLICT (group_id, permission_id) DO NOTHING. Таким образом, при нарушении ограничения уникальности PostgreSQL не будет предпринимать никаких действий.

Подробнее про предложение ON CONFLICT можно почитать в официальном справочнике PostgreSQL по ссылке.

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

Самостоятельно

Все Permission на работу с заказами мы объединили в одно разрешение "Заказы". Но можно разделить их на два: одно разрешение будет на создание и редактирование заказов, а второе на добавление и редактирование оплат в заказе. С помощью такого разделения можно гибко настроить права доступа к заказам. Например, группа "Пользователи" может создавать заказы и добавлять в них оплаты, а группа "Бухгалтеры" может только просматривать карточку заказа (без редактирования) и редактировать оплаты в заказе.

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

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

Итоги

В этом уроке мы рассмотрели правила организации динамических прав доступа и реализовали их в нашем проекте.

Ответы

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

288KB
Open

Last updated