Перейти к содержанию

Группы

Группа - это специальный объект, объединяющий список сотрудников и иных ролей (без учёта их замещений), с возможностью делегировать управление таким списком. Использование групп позволяет предоставить связанные с этой группой административные возможности без необходимости указывать пользователей как глобальных администраторов системы (у которых в поле Уровень доступа в карточке Сотрудник указано Администратор).

Группы объявляются в коде серверных расширений посредством дескрипторов: в платформе, в модулях платформы (таких как CRM) или в проектных решениях. Для функционирования групп не требуется импортировать объекты конфигурации или выполнять иные настройки, достаточно объявить группу в коде и затем использовать её также в коде. Все объявленные группы будут автоматически созданы и актуализированы (в случае изменения дескрипторов в коде) при запуске любого из сервисов web. Также они всегда создаются в серверных и гибридных тестах или посредством консольной команды tadmin Script InitializeGroups. Таким образом, гарантируется, что группы в базе данных всегда актуальны тому, как они объявлены в коде, что упрощает их конфигурирование. Проверки на вхождение пользователя в состав группы или возможность управлять группой доступны в коде клиентских и серверных расширений, с учётом оптимизаций клиентского и серверного кэша.

Примеры групп:

  • Эксперты ИИ - пользователи, входящие в эту группу, получают расширенные возможности по управлению настройками взаимодействия с большими языковыми моделями (LLM) искусственного интеллекта.

  • Администраторы ЭП - включённые в группу пользователи имеют доступ к архивному переподписанию ЭП.

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

  • Администраторы CRM - входящие сюда пользователи могут конфигурировать и администрировать модуль CRM.

  • Создатели клиентов - группа с пользователями, которым разрешено создавать карточки клиентов в модуле CRM.

Параметры группы

Список групп, которые может администрировать текущий пользователь, выводится в меню системы, в пункте Настройки системы, в категории Группы.

Note

Если пользователь имеет уровень доступа Администратор (поле Уровень доступа в карточке Сотрудник), то он может администрировать любые группы, независимо от того, что указано в таблице Администраторы группы для каждой из них. Таким образом, пользователь-администратор видит все объявленные группы в этой таблице (если группа не является скрытой). Однако такой пользователь автоматически не входит во все группы (но может себя включить в состав любой из групп).

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

  • Например, когда группа Администраторы безопасности относится к категории Безопасность, то выбрав эту категорию в дереве интерфейса Настройки системы, пользователь увидит и группу (если у него есть права на её администрирование), и собственно настройки безопасности, такие как время жизни сессии и допустимая длина пароля (доступ к которым регулируется вхождением в состав упомянутой группы). При этом группа по-прежнему будет видна в категории Группы вместе с прочими доступными группами.

  • Через пункт Управление доступом, выводимый в меню для юнита настроек, возможно открыть диалог редактирования параметров группы, в котором доступно изменение состава группы и списка её администраторов (если права администрирования это позволяют посредством флага Ределегирование). Пункт отображается, когда у текущего пользователя есть права на администрирование группы.

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

  1. Имя - локализованное название группы.

  2. Описание - локализованное многострочное описание, подсказывающее администраторам, как используется и на что влияет эта группа.

  3. Категория - каждая группа принадлежит какой-либо категории в Настройках системы, которая выводится в заголовке диалога. Категории могут быть вложены друг в друга. Группа будет видна не только в таблице со всеми группами, но и вместе с настройками её категории (при наличии прав на администрирование группы).

  4. Режим генерации роли - для группы может генерироваться умная роль (в представлении она отображается с типом роли Группа), содержащая всех пользователей из состава группы (без учёта замещений по ролям). Имя и идентификатор роли совпадают с параметрами группы.

    • Если рядом с именем группы в скобках выведено (скрытая роль), то умная роль генерируется с флагом Скрытая (не видна в представлениях по умолчанию, без параметра фильтрации Показать скрытые).

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

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

  5. Состав группы - список ролей, которые объединяет текущая группа. Если пользователь будет входить в одну из этих ролей, то он также будет входить в группу, а значит - получит доступ.

    • Записи, отмеченные символом #, являются служебными, они настроены при объявлении группы кодом. Такие записи невозможно отредактировать через интерфейс или через клиентское API. В интерфейсе они отображаются для информации, доступные только для чтения.

    • В состав группы возможно включить как другие группы (для которых генерируется роль), так и роли следующих типов: сотрудники, подразделения, статические роли, динамические роли, умные роли, метароли (такие как Агрегатные роли вида “Подразделение (все)”).

    • Через интерфейс в составе группы нельзя выбрать другую группу, для которой не генерируется роль. При таком добавлении через API будет выброшена ошибка.

    • Если в составе одной группы указана другая группа, то это означает, что все указанные в текущий момент роли в составе той другой группы - также входят в состав этой группы. Например: пусть в состав группы “Администраторы безопасности” включена группа “Администраторы настроек сервера”, в состав которой включены роли “Иванов И.” (сотрудник) и “Отдел ИБ” (подразделение). Тогда и пользователь “Иванов И.”, и все пользователи из подразделения “Отдел ИБ” будут входить не только в группу “Администраторы настроек сервера”, но и в группу “Администраторы безопасности”.

    • Включение в состав группы никогда не учитывает замещения для перечисленных в составе ролей.

    • Пользовать может видеть группу, т.к. он является её администратором, но не входить в её состав (а значит, не иметь доступа к функциональности, которая проверяет вхождение в группу). Если пользователь администрирует группу, то он может, при желании, добавить себя в её состав (как сотрудника или через другую роль).

    • Система запрещает циклические вхождения групп в состав друг друга. Так, невозможно настроить две группы, взаимно входящие в состав друг друга. Запрет не ограничен по вложенности и количеству проверяемых групп.

  6. Администраторы группы - список ролей, вхождение в которые позволяет видеть группу в настройках, администрировать состав группы (менять таблицу Состав группы, за исключением служебных записей). При наличии флага Ределегирование пользователь также может администрировать эту таблицу Администраторы группы (за исключением служебных записей).

    • Записи, отмеченные символом #, являются служебными, они настроены при объявлении группы. Такие записи невозможно отредактировать через интерфейс или через клиентское API. В интерфейсе они отображаются для информации, доступные только для чтения.

    • В администраторы группы возможно включить как другие группы (в т.ч. в режиме без генерации роли), так и роли следующих типов: сотрудники, подразделения, статические роли, динамические роли, умные роли, метароли (такие как Агрегатные роли вида “Подразделение (все)”).

    • Если в администраторах одной группы указана другая группа, то это означает, что все указанные в текущий момент роли в составе (не администраторах!) той другой группы - также входят в администраторы этой группы. Например: пусть в администраторы группы “Создатели клиентов” включена группа “Администраторы CRM”, в состав которой включены роли “Петров П.” (сотрудник) и “Отдел ИТ” (подразделение). Тогда и пользователь “Петров П.”, и все пользователи из подразделения “Отдел ИТ” будут администрировать группу “Создатели клиентов”, если они входят в группу “Администраторы CRM”. Если некий пользователь “Васильев В.” администрирует группу “Администраторы CRM”, но не входит в её состав, то он не сможет администрировать группу “Создатели клиентов” (и даже увидеть её), пока не добавит себя в состав группы “Администраторы CRM” (что он может сделать, поскольку он администрирует группу).

    • Включение в администраторы группы никогда не учитывает замещения для перечисленных там ролей.

    • Циклические вхождения в администраторы невозможны, т.к. при добавлении туда другой группы - в администраторы текущей группы включается состав другой группы, а не администраторы той группы.

Также в поле Мой статус диалог отображает, какие возможности есть у текущего пользователя по отношению к группе:

  • не вхожу в группу - пользователь не входит в состав группы;

  • вхожу в группу - пользователь входит в состав группы;

  • администрирую состав - пользователь видит состав группы и может его изменять (в т.ч. добавить туда себя), но он не видит администраторов группы (и не может сделать себя администратором). Актуально, если в таблице Администраторы группы нет ролей, куда входит пользователь, с флагом Ределегирование, но есть одна из ролей без этого флага. Все другие настройки группы видны, но недоступны для изменения (это возможно только в коде объявления группы);

  • администрирую всё - пользователь видит и может изменить как состав группы, так и список администраторов группы. Актуально, если в таблице Администраторы группы его роль указана с флагом Ределегирование. Все другие настройки группы видны, но недоступны для изменения (это возможно только в коде объявления группы).

Note

Если у пользователя нет прав на администрирование группы, то она не будет видна в таблице с группами. Такой пользователь не может узнать, какие роли входят в состав или администраторы группы, и какие иные параметры установлены для группы. От сессии этого пользователя (с клиента или сервера) возможен только запрос к API групп (по идентификатору группы) для проверки вхождения в состав и для проверки наличия прав администратора.

Объявление группы

Группа объявляется в серверных расширениях Tessa.Extensions.Server путём добавления регистрации дескриптора IGroupDescriptor (обычно через класс GroupDescriptor) в объекте реестра IGroupDescriptorRegistry (из DI-контейнера IUnityContainer). Объявление дескриптора рекомендуется описывать в отдельном вспомогательном классе.

Рассмотрим объявление двух связанных групп:

CrmGroups.cs

using Tessa.Groups; using Tessa.Roles;

namespace Tessa.Extensions.Server.Groups { public static class CrmGroups { public static readonly GroupDescriptor CrmAdmins = new( // 785E682D-449D-4664-A665-7D99BF0F692D id: new(0x785e682d, 0x449d, 0x4664, 0xa6, 0x65, 0x7d, 0x99, 0xbf, 0x0f, 0x69, 0x2d), name: "Администраторы CRM", category: "crm") { Description = "Пользователи, входящие в эту группу, могут администрировать решение CRM", Members = [RoleHelper.AdminUserID] };

public static readonly GroupDescriptor ClientCreators = new( // 885E682D-449D-4664-A665-7D99BF0F692D id: new(0x885e682d, 0x449d, 0x4664, 0xa6, 0x65, 0x7d, 0x99, 0xbf, 0x0f, 0x69, 0x2d), name: "Создатели клиентов", category: "crm/clients") { Description = "Пользователи, входящие в эту группу, могут создавать карточки клиентов CRM", RoleMode = GroupRoleMode.Visible, FullAdmins = [CrmAdmins.ID], Members = [RoleHelper.AdminUserID] }; } }

Здесь описываются дескрипторы двух групп:

  1. Администраторы CRM (CrmGroups.CrmAdmins):

    • создаётся без генерации умной роли (по умолчанию GroupRoleMode.None);

    • имеет категорию с алиасом crm (будет видна на верхнем уровне в меню Настройки системы);

    • описание группы Description будет отображаться администраторам;

    • в состав группы включается служебная запись для пользователя Admin, чтобы этому пользователю не пришлось добавлять себя в группу (его идентификатор присутствует в стандартном поле RoleHelper.AdminUserID).

  2. Создатели клиентов (CrmGroups.ClientCreators):

    • создаётся с генерацией видимой умной роли GroupRoleMode.Visible (чтобы роль можно было добавлять в правила доступа);

    • имеет категорию с алиасом clients, вложенную в категорию crm;

    • описание группы Description будет отображаться администраторам;

    • полные права администрирования с учётом изменения таблицы Администраторы группы (как при установленном флаге Ределегирование) предоставляется всем пользователям, входящим в состав группы Администраторы CRM (для этого в таблице Администраторы группы будет добавлена соответствующая служебная запись);

    • в состав группы также включается служебная запись для пользователя Admin.

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

Registrator.cs

using Tessa.Groups; using Tessa.Platform;

namespace Tessa.Extensions.Server.Groups { [Registrator] public sealed class Registrator : RegistratorBase { public override void FinalizeRegistration() { if (this.UnityContainer.TryResolve<IGroupDescriptorRegistry>() is not { } registry) { return; }

registry.Register(CrmGroups.CrmAdmins); registry.Register(CrmGroups.ClientCreators); } } }

Серверная проверка вхождения в состав группы ClientCreators для пользователя в сессии при создании карточки типа CrmClient осуществляется вызовом метода-расширения IGroupAccessService.IsMemberOfGroupAsync. Ниже указан пример вызова из расширения CardStoreExtension с его регистрацией:

CrmClientStoreExtension.cs

using System.Threading.Tasks; using Tessa.Cards; using Tessa.Cards.Extensions; using Tessa.Groups; using Tessa.Platform.Validation; using Unity;

namespace Tessa.Extensions.Server.Groups { public sealed class CrmClientStoreExtension(IGroupAccessService groupAccessService) : CardStoreExtension { public override async Task BeforeRequestWhenTypeResolved(ICardStoreExtensionContext context) { if (context.Request.Card.StoreMode != CardStoreMode.Insert) { // не создание, а изменение return; }

if (!await groupAccessService.IsMemberOfGroupAsync( context.Session.User.ID, CrmGroups.ClientCreators.ID)) { context.ValidationResult.AddError(this, "Нет прав на создание клиентов."); } }

[Registrator] public sealed class Registrator : RegistratorBase { public override void RegisterUnity() => this.UnityContainer.RegisterSingleton<CrmClientStoreExtension>();

public override void RegisterExtensions(IExtensionContainer extensionContainer) => extensionContainer .RegisterExtension<ICardStoreExtension, CrmClientStoreExtension>(x => x .WithOrder(ExtensionStage.AfterPlatform) .WithUnity(this.UnityContainer) .WhenCardTypes("CrmClient")); } } }

Tip

Дескрипторы групп, входящих в платформу, доступны в классе Tessa.Groups.PlatformGroups.

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

crmGroups.ts

export namespace CrmGroups { export const CrmAdmins = '785e682d-449d-4664-a665-7d99bf0f692d'; export const ClientCreators = '885e682d-449d-4664-a665-7d99bf0f692d'; }

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

crmClientStoreExtension.ts

import { extension, inject } from '@tessa/application'; import { ValidationResult, ValidationResultType } from '@tessa/core'; import { CardStoreExtension, CardStoreMode, ICardStoreExtensionContext, IGroupAccessService, IGroupAccessService$ } from '@tessa/platform'; import { CrmGroups } from './crmGroups';

@extension({ name: 'CrmClientStoreExtension' }) export class CrmClientStoreExtension extends CardStoreExtension { constructor( @inject(IGroupAccessService$) private readonly groupAccessService: IGroupAccessService ) { super(); }

public async beforeRequest(context: ICardStoreExtensionContext): Promise<void> { if (context.request.card.storeMode !== CardStoreMode.Insert) { // не создание, а изменение return; }

if (!(await this.groupAccessService.isMember(CrmGroups.ClientCreators))) { context.validationResult.add( ValidationResult.fromText('Нет прав на создание клиентов.', { type: ValidationResultType.Error }) ); } } }

Регистрация расширения выглядит обычным образом:

crmRegistrator.ts

import { ExtensionRegistrator, ExtensionStage } from '@tessa/application'; import { whenCardTypeNameIs } from '@tessa/platform'; import { CrmClientStoreExtension } from './crmClientStoreExtension';

export const CrmRegistrator: ExtensionRegistrator = { async registerExtensions(container) { container.registerExtension({ extension: CrmClientStoreExtension, stage: ExtensionStage.AfterPlatform, when: whenCardTypeNameIs('CrmClient') }); } };

Tip

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

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

Теперь достаточно собрать серверные расширения (и клиентские - при наличии в них изменений), и запустить с ними веб-сервис web.

Important

Все объявленные группы будут автоматически созданы и актуализированы (в случае изменения дескрипторов в коде расширений) при запуске любого из сервисов web, у которого в конфигурационных файлах app*.json установлена настройка "Groups.InitializeOnStartup": true. В файле app-webdev.json эта настройка устанавливается в false для окружения разработки, которое определяется по переменной %ASPNETCORE_ENVIRONMENT%, равной одному из значений: Development, DevelopmentHot, SdkHot.

Также группы всегда инициализируются в серверных и гибридных тестах или посредством консольной команды tadmin Script InitializeGroups.

Будьте внимательны при запуске нескольких веб-сервисов web с различными версиями библиотек расширений (где дескрипторы зарегистрированы отличным образом), если каждый из этих веб-сервисов запущен с флагом "Groups.InitializeOnStartup": true. Процесс сервиса при своём очередном перезапуске будет изменять состояние групп в базе данных в соответствии с настройками в его версии библиотеки расширений Tessa.Extensions.Server.dll (перезапуск может быть неявным, например, в зависимости от настроек перезапуска пулов приложений в IIS).

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

Дескриптор группы

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

  • ID (параметр конструктора) - уникальный идентификатор группы. Идентификатор умной роли, создаваемой для группы (если создаётся в соответствии со свойством RoleMode), совпадает с идентификатором группы.

  • Name (параметр конструктора) - имя группы для отображения пользователю. Может быть строкой локализации или содержать плейсхолдеры локализации ("... {$Alias} ..."). Имя умной роли, создаваемой для группы (если создаётся в соответствии со свойством RoleMode), совпадает с именем группы.

  • Category (параметр конструктора) - дескриптор категории, к которой относится группа. Например, строка "serverSettings/security" определяет, что группа относится к подкатегории с алиасом "security" (“Безопасность”), которая вложена в категорию верхнего уровня "serverSettings" (“Настройки сервера”).

    Note

    Для отображения категорий с локализованными именами в интерфейсе Настройки системы на сервере нужно дополнительно создать и зарегистрировать дескрипторы категорий SettingsUnitCategoryDescriptor, как это продемонстрировано в примере Добавление нового юнита настроек, описание свойств объектов - в разделе руководства разработчика Подсистема настроек. Указанный алиас категории будет использован для ссылок в адресной строке.

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

  • Description (по умолчанию пусто) - описание группы для отображения пользователю (в интерфейсе Настройки системы). Может быть строкой локализации или содержать плейсхолдеры локализации ("... {$Alias} ..."). Допустимо указывать многострочный текст.

  • HiddenGroup (по умолчанию false) - признак того, что группа скрыта в интерфейсе Настройки системы. Группа доступна на клиенте и на сервере для проверок, и также может использоваться генерируемая для неё умная роль (если создаётся в соответствии со свойством RoleMode). Изменение группы при этом доступно только в коде либо через дескриптор, либо через API по изменению групп.

  • RoleMode (по умолчанию None) - режим генерации умных ролей для группы. Возможные значения:

    • None - умная роль, связанная с группой, не генерируется. Группу нельзя использовать, как роль, а также вкладывать в состав других групп. Однако группа может быть указана как администратор других групп;

    • Visible - группа связана с видимой умной ролью. Использование группы аналогично статическим ролям. Группу возможно вкладывать в состав и администраторы других групп;

    • Hidden - группа связана со скрытой умной ролью. Использование группы аналогично скрытым статическим ролям. Её возможно выбрать как роль, если в представлении указать параметр фильтрации “показать скрытые”. Группу возможно вкладывать в состав и администраторы других групп.

  • CheckOnWebClientOpening (по умолчанию false) - признак того, что вхождение текущего пользователя в состав группы должно быть проверено при открытии (инициализации) web-клиента. Используйте для групп, вхождение в состав которых требуется определить сразу же после открытия web-клиента, например, для отображения пунктов в системном меню. Это мера оптимизации, чтобы с клиента не отправлялся дополнительный запрос на вхождение в эту группу при каждом открытии браузера.

  • Members - состав группы (список сотрудников, ролей или других групп с включённым созданием умной роли), который устанавливается в группе системой. Указывается как список идентификаторов ролей, повторяющиеся идентификаторы будут отброшены. Для всех них система генерирует служебные записи в таблице Состав группы, т.е. такие записи всегда доступны и не могут быть изменены через интерфейс или API (только посредством модификации дескриптора).

  • FullAdmins - список администраторов (сотрудников, ролей или других групп) с полными правами, которые добавляются в группу системой. Указывается как список идентификаторов ролей, повторяющиеся идентификаторы будут отброшены. Для всех них система генерирует служебные записи в таблице Администраторы группы с установленным флагом Ределегирование, т.е. такие записи всегда доступны и не могут быть изменены через интерфейс или API (только посредством модификации дескриптора).

  • LimitedAdmins - список администраторов (сотрудников, ролей или других групп) с ограниченными правами на редактирование только состава группы, которые добавляются в группу системой. Указывается как список идентификаторов ролей, повторяющиеся идентификаторы будут отброшены. Для всех них система генерирует служебные записи в таблице Администраторы группы со сброшенным флагом Ределегирование, т.е. такие записи всегда доступны и не могут быть изменены через интерфейс или API (только посредством модификации дескриптора).

    Tip

    Администратор группы с полными правами может добавить ту же роль, которая указана в списке LimitedAdmins, предоставив ей полные права посредством установленного флага Ределегирование. При этом в таблице Администраторы группы будет две записи: одна служебная (без флага) и одна изменяемая (с флагом). Изменить служебную запись, иначе как через программное изменение дескриптора, невозможно.

Управление группами через API

В этом разделе описаны различные клиентские и серверные API, позволяющие выполнять операции с группами.

Tip

Создание новой группы через дескриптор с его регистрацией описана выше в разделе Объявление группы.

  1. Серверный объект IGroupClientService. Выполняет действия над группами с учётом проверок по возможности выполнения таких действий, и проверок видимости групп. Методы этого объекта позволяют на сервере достичь того же поведения, что и на клиенте (без необходимости вручную вызывать проверки доступа). Любые ошибки приводят к выбросу исключений.

    • Используйте этот объект в контроллерах на сервере, где требуется проверить наличие прав у пользователя, передавшего запрос на сохранение группы. Для получения локализованных ошибок, возвращаемых на клиент, в каждый из методов передавайте параметр wrapClientExceptions: true.

    • Если не требуется учитывать права или возвращать результат на клиент, то используйте объект IGroupService.

    • Метод StoreGroupAsync позволяет изменить группу (создание группы невозможно). В запросе указывается идентификатор группы GroupID, а также изменяемые записи из состава группы Members и администраторов группы Admins.

    Добавление сотрудника Admin в администраторы группы “Эксперты ИИ” с полными правами, т.е. с возможностью ределегирования.

    await this.GroupClientService.StoreGroupAsync( this.Session.User.ID, new GroupStoreRequest { GroupID = PlatformGroups.AiModuleExperts.ID, Admins = [ new GroupAdminInfo { RoleID = RoleHelper.AdminUserID, FullPermissions = true, State = GroupMemberState.Inserted } ] });

    • Метод GetGroupAsync позволяет получить информацию по отдельной группе, при этом имена ролей нормализуются. Если группа отсутствует или недоступна пользователю, то выбрасывается исключение.

    Получение строки с именем и категорией группы “Эксперты ИИ”, в которой выводим имена ролей в составе группы.

    var response = await this.GroupClientService.GetGroupAsync( this.Session.User.ID, PlatformGroups.AiModuleExperts.ID);

    var text = $"Группа {await LocalizeAsync(response.GroupInfo.Name)}" + $" (категория {response.GroupInfo.Category})." + $" Состав: {string.Join(", ", response.Members.Select(x => Localize(x.RoleName)))}";

    • Метод GetAllGroupsAsync загружает список всех доступных пользователю групп с базовой информацией по ним (без состава и списка администраторов). Имена ролей нормализуются. Метод не возвращает скрытые группы (у которых в дескрипторе установлен флаг HiddenGroup), а также группы, недоступные пользователю для администрирования (без учёта флага Ределегирование). Возвращается базовая информация по группе, но не содержимое таблиц Состав группы и Администраторы группы (для этого вызовите метод GetGroupAsync). Позволяет отфильтровать список групп по параметру category (в этом случае возвращаются и группы, входящие в указанную категорию, и группы из всех дочерних категорий).

    Получение доступных для администрирования нескрытых групп, имеющих категорию "serverSettings" или любую дочернюю категорию ("serverSettings/security" и пр.)

    var visibleGroups = await this.GroupClientService.GetAllGroupsAsync( this.Session.User.ID, category: SettingsUnitCategoryDescriptors.ServerSettings.Name);

    var text = "В категории 'Настройки сервера' пользователю доступны группы: " + $"{string.Join(", ", visibleGroups.Select(x => Localize(x.Name)))}.";

  2. Серверный объект IGroupService. Управляет группами с учётом их связи с умными ролями, но без проверок прав доступа пользователя.

    • Применяйте его для выполнения логики на сервере без привязки к какому-либо пользователю, т.е. если она не может быть вызвана с клиента из кода контроллеров или иным способом (из расширений на карточки). Используйте объект IGroupService, только если уверены, что все права проверены (иначе рассмотрите объект IGroupClientService).

    • Есть низкоуровневый объект IGroupRepository, он используется платформой, операции с ним могут сломать корректную логику взаимодействия с группами. Не используйте низкоуровневый объект, вместо этого всегда задействуйте IGroupService или IGroupClientService.

    • Метод StoreGroupAsync позволяет изменить группу (а также создать указанием флага IGroupStoreRequest.AllowSystemChanges = true). В запросе указывается идентификатор группы GroupID, а также изменяемые записи из состава группы Members и администраторов группы Admins. Права доступа не проверяются.

    Important

    Если группа создаётся посредством объявления дескриптора, то не выполняйте изменения такой группы с выставленным флагом IGroupStoreRequest.AllowSystemChanges = true. Иначе очередная инициализация может либо вернуть служебные записи (по составу и администраторам) и свойства группы к исходному состоянию, если в объявлении произошли изменения, так и не вернуть, если изменений не было. Т.е. поведение будет неконсистентным. Устанавливайте флаг запроса AllowSystemChanges только для групп, которые создаются и изменяются в коде посредством объекта IGroupService, но для которых не объявляется дескриптор.

    Добавление сотрудника Admin в состав группы “Администраторы центра файлов” от имени пользователя System без проверки доступа на группу.

    await using var _ = SessionContext.Create( Session.CreateSystemToken(this.TessaServerSettings));

    await this.GroupService.StoreGroupAsync( new GroupStoreRequest { GroupID = PlatformGroups.FileVaultAdministrators.ID, Members = [ new GroupMemberInfo { RoleID = RoleHelper.AdminUserID, State = GroupMemberState.Inserted } ] });

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

      Объект с опциями загрузки GroupLoadingOptions позволяет указать дополнительные настройки (по умолчанию все флаги false, а если опции не указаны, то это означает false по всех флагах):

      • SystemOnly - признак того, что для состава группы Members и списка администраторов Admins возвращаются только служебные записи с установленным флагом IsSystem. Служебные записи объявляются в дескрипторе группы.

      • NormalizeNames - признак того, что имена ролей должны быть заполнены из справочников нормализации для всех возвращаемых объектов. Если признак указан как false, то имена возвращаются как null. Укажите true, если результат возвращается на клиент.

      • DoNotLoadAdmins - признак того, что администраторы группы Admins не загружаются.

    Удаление всех неслужебных записей по составу группы “Администраторы центра файлов” от имени пользователя System без проверки доступа на группу.

    await using var _ = SessionContext.Create( Session.CreateSystemToken(this.TessaServerSettings));

    var response = await this.GroupService.GetGroupAsync( PlatformGroups.FileVaultAdministrators.ID, new() { DoNotLoadAdmins = true });

    var membersToDelete = response.Members.Where(x => !x.IsSystem) .Select(x => x.AsPlain()).ToList();

    if (membersToDelete.Count > 0) { membersToDelete.ForEach(x => x.State = GroupMemberState.Deleted);

    await this.GroupService.StoreGroupAsync( new GroupStoreRequest { GroupID = response.GroupInfo.ID, Members = membersToDelete }); }

    • Метод GetAllGroupsAsync загружает список всех групп с базовой информацией по ним (без состава и списка администраторов). По умолчанию права доступа не проверяются, имена ролей не нормализуются, и метод не возвращает скрытые группы (у которых в дескрипторе установлен флаг HiddenGroup). Возвращается базовая информация по группе, но не содержимое таблиц Состав группы и Администраторы группы (для этого вызовите метод GetGroupAsync). Позволяет отфильтровать список групп по категории GroupListLoadingOptions.Category (в этом случае возвращаются и группы, входящие в указанную категорию, и группы из всех дочерних категорий).

      Объект с опциями загрузки GroupListLoadingOptions позволяет указать дополнительные настройки (по умолчанию все флаги false и прочие свойства null, а если опции не указаны, то это означает false по всех флагах и null в прочих свойствах):

      • LoadHidden - признак того, что должны возвращаться все группы, включая скрытые (у которых в дескрипторе установлен флаг HiddenGroup).

      • NormalizeNames - признак того, что имена ролей должны быть заполнены из справочников нормализации для всех возвращаемых объектов. В списке групп это относится к именам пользователей, которые изменяли группу. Если признак указан как false, то имена возвращаются как null.Укажите true, если результат возвращается на клиент, где нужны эти имена.

      • WhereUserIDIsAdmin - идентификатор пользователя, который должен быть администратором каждой возвращаемой группы (с флагом Ределегирование или без него, при этом результат не отличается, поскольку администраторы групп этим методом не загружаются в любом случае). Если указано null (по умолчанию), то группы загружаются независимо от пользователя. Сессия текущего пользователя не влияет на загрузку групп, влияние оказывается только идентификатор в этом свойстве.

      • Category - путь к категории, в которой располагается группа. В качестве разделителя категорий в пути используется символ /. Если указано null, то фильтрация по категориям не применяется, иначе выполняется поиск для указанной категории и для всех дочерних категорий. Например, строка "serverSettings" определяет, что возвращаются только группы из категории верхнего уровня "serverSettings" (“Настройки сервера”), а также группы из подкатегории с алиасом "serverSettings/security" (“Безопасность”) и прочих подкатегорий "serverSettings/.../...".

    Получение групп, доступных для администрирования пользователю Admin, включая скрытые, и имеющие категорию "serverSettings" или любую дочернюю категорию ("serverSettings/security" и пр.)

    // сессия не влияет на права при загрузке, но она должна быть установлена await using var _ = SessionContext.Create( Session.CreateSystemToken(this.TessaServerSettings));

    var availableGroups = await this.GroupService.GetAllGroupsAsync(new() { LoadHidden = true, WhereUserIDIsAdmin = RoleHelper.AdminUserID, Category = SettingsUnitCategoryDescriptors.ServerSettings.Name });

    var text = "В категории 'Настройки сервера' пользователю Admin доступны группы: " + $"{string.Join(", ", availableGroups.Select(x => Localize(x.Name)))}.";

    • Метод DeleteGroupAsync удаляет группу с указанным идентификатором. Для корректного поведения системы не применяйте этот метод для групп, которые объявляются посредством дескрипторов. Используйте метод, если группа создавалась без дескриптора посредством метода StoreGroupAsync.

    Important

    Создание групп без объявления дескриптора возможно, но приводит к тому, что при очередной инициализации групп (например, при запуске веб-сервиса web) те группы, для которых отсутствует дескриптор, будут удалены. В текущей версии создание и удаление групп через API имеет смысл только при написании тестов.

    Создание, изменение и удаление группы без дескрипторов от имени пользователя System

    await using var _ = SessionContext.Create( Session.CreateSystemToken(this.TessaServerSettings));

    var group = new GroupInfo { ID = Guid.NewGuid(), Name = "My test group", Category = "test", HiddenGroup = true, RoleMode = GroupRoleMode.Hidden };

    // создаём группу с добавлением пользователя Admin // как администратора с полным доступом // (служебная запись IsSystem не редактируется с клиента даже для невидимой группы)

    // при создании вместе GroupID указывается полная информация GroupInfo, // при этом необходимо установить флаг AllowSystemChanges // (даже если служебных записей в составе и списке администраторов нет)

    await this.GroupService.StoreGroupAsync( new GroupStoreRequest { GroupInfo = group, AllowSystemChanges = true, Admins = [ new GroupAdminInfo { RoleID = RoleHelper.AdminUserID, FullPermissions = true, IsSystem = true, State = GroupMemberState.Inserted } ] });

    // сбрасываем флаг FullPermissions для администратора группы; // для этого сначала надо загрузить запись GroupAdminInfo, // где будет актуальный RowID

    var response = await this.GroupService.GetGroupAsync(group.ID, new() { SystemOnly = true });

    var adminInfo = response.Admins.First(x => x.IsSystem && x.RoleID == RoleHelper.AdminUserID).AsPlain(); adminInfo.FullPermissions = false; adminInfo.State = GroupMemberState.Modified;

    await this.GroupService.StoreGroupAsync( new GroupStoreRequest { GroupID = group.ID, AllowSystemChanges = true, Admins = [adminInfo] });

    // проверяем наличие полных прав у пользователя Admin - они отсутствуют var fullAdmin = await this.GroupAccessService.HasEnoughAdminPermissionsAsync( RoleHelper.AdminUserID, group.ID, GroupAdminPermissions.Full); Assert.That(fullAdmin, Is.False);

    // проверяем наличие прав на изменение состава у пользователя Admin - они присутствуют var limitedAdmin = await this.GroupAccessService.HasEnoughAdminPermissionsAsync( RoleHelper.AdminUserID, group.ID, GroupAdminPermissions.Limited); Assert.That(limitedAdmin, Is.True);

    // удаляем группу await this.GroupService.DeleteGroupAsync(group.ID);

  3. Серверный объект IGroupInitializer. Выполняет инициализацию групп для всех зарегистрированных дескрипторов.

    • Обычно прямое использование этого объекта не требуется, т.к. все объявленные группы будут автоматически созданы и актуализированы (в случае изменения дескрипторов в коде) при запуске любого из сервисов web. Также они всегда создаются в серверных и гибридных тестах или посредством консольной команды tadmin Script InitializeGroups.

    • Метод InitializeDescriptorsAsync инициализирует все группы по зарегистрированным дескрипторам. При этом удаляются группы, у которых нет дескрипторов. Возвращает результат валидации с возможными ошибками при создании или удалении отдельных групп.

      • Параметр forceRecalc (по умолчанию false) указывает, что служебная информация по всем группам принудительно вычисляется, даже если ранее она была корректно рассчитана по хеш-сумме. Укажите этот флаг, если в базе данных производились изменения служебных настроек и состава группы без использования API инициализации.

    Вызов инициализации при установленном флаге "Groups.InitializeOnStartup": true в конфигурационном файле.

    if (this.GroupSettings is not { InitializeOnStartup: true }) { logger.Debug("Synchronizing group descriptors: skipped"); return; }

    logger.Debug("Synchronizing group descriptors: started");

    await using var _ = SessionContext.Create( Session.CreateSystemToken(this.TessaServerSettings));

    var result = await this.GroupInitializer.InitializeDescriptorsAsync(); logger.LogResultItems(result);

    logger.Debug("Synchronizing group descriptors: completed");

  4. Клиентский объект IGroupService. Выполняет действия над группами со стороны клиента, с учётом проверок по возможности выполнения таких действий, и проверок видимости групп. Аналогичен серверному объекту IGroupClientService, при этом всегда функционирует для пользователя в текущей сессии. Любые ошибки приводят к выбросу исключений.

    Получение зависимости IGroupService в конструкторе класса.

    constructor( @IGroupService$() private readonly _groupService: IGroupService ) {}

    • Метод storeGroup позволяет изменить группу (создание группы или изменение служебных записей невозможны). Права проверяются для текущего пользователя. В запросе указывается идентификатор группы groupID, а также изменяемые записи из состава группы members и администраторов группы admins.

    Добавление сотрудника Admin в администраторы группы “Эксперты ИИ” с полными правами, т.е. с возможностью ределегирования.

    async storeGroupExample(): Promise<ValidationResult> { const adminInfo = new GroupAdminInfo(); adminInfo.roleID = '3db19fa0-228a-497f-873a-0250bf0a4ccb'; adminInfo.fullPermissions = true; adminInfo.state = GroupMemberState.Inserted;

    const request = new GroupStoreRequest(); request.groupID = PlatformGroups.AiModuleExperts; request.admins = [adminInfo];

    try { await this._groupService.storeGroup(request); return ValidationResult.empty; } catch (e) { return ValidationResult.fromError(e); } }

    • Метод getGroup позволяет получить информацию по отдельной группе, при этом имена ролей нормализуются. Права проверяются для текущего пользователя. Если группа отсутствует, то возвращается null. Если группа недоступна пользователю, то выбрасывается исключение.

    Получение строки с именем и категорией группы “Эксперты ИИ”, в которой выводим имена ролей в составе группы.

    async getGroupExample(): Promise<[string | null, ValidationResult]> { try { const response = await this._groupService.getGroup(PlatformGroups.AiModuleExperts); if (!response) { return [null, ValidationResult.fromError('$Groups_Messages_GroupDoesNotExist')]; }

    const text = `Группа ${localize(response.groupInfo?.name)}` + ` (категория ${localize(response.groupInfo?.category)}).` + ` Состав: ${response.members.map(x => localize(x.roleName)).join(', ')}`;

    return [text, ValidationResult.empty]; } catch (e) { return [null, ValidationResult.fromError(e)]; } }

    • Метод getAllGroups загружает список всех доступных пользователю групп с базовой информацией по ним (без состава и списка администраторов). Имена ролей нормализуются. Права проверяются для текущего пользователя. Метод не возвращает скрытые группы (у которых в дескрипторе установлен флаг HiddenGroup), а также группы, недоступные пользователю для администрирования (без учёта флага Ределегирование). Возвращается базовая информация по группе, но не содержимое таблиц Состав группы и Администраторы группы (для этого вызовите метод GetGroupAsync). Позволяет отфильтровать список групп по параметру category (в этом случае возвращаются и группы, входящие в указанную категорию, и группы из всех дочерних категорий).

    Получение доступных для администрирования нескрытых групп, имеющих категорию "serverSettings" или любую дочернюю категорию ("serverSettings/security" и пр.)

    async getAllGroupsExample(): Promise<[string | null, ValidationResult]> { try { const visibleGroups = await this._groupService.getAllGroups('serverSettings');

    const text = `В категории 'Настройки сервера' пользователю доступны группы: ` + ` Состав: ${visibleGroups.map(x => localize(x.name)).join(', ')}`;

    return [text, ValidationResult.empty]; } catch (e) { return [null, ValidationResult.fromError(e)]; } }

Проверка доступа к группе

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

  1. Вхождение в состав группы - определяет, входит ли пользователь в одну из ролей, указанную в таблице Состав группы (свойство Members в API). Группы создаются в основном для того, чтобы вхождение в их состав предоставляло доступ к функциональности (например, к изменению определённых настроек или к наличию дополнительных переключателей в интерфейсе, таких как настройка логирования в чате ИИ).

    • Вхождение всегда проверяется без учёта заместителей.

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

  2. Возможность администрирования группы - определяет, может ли пользователь видеть и изменять информацию по группе.

    • Если в таблице Администраторы группы (свойство Admins в API) есть хотя бы одна запись с флагом Ределегирование (свойство FullPermissions в API), в которой указана роль, и пользователь входит в эту роль, то такой пользователь видит и может изменять как таблицы Состав группы, так и таблицу Администраторы группы.

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

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

    • Если группа является скрытой (флаг HiddenGroup в дескрипторе), то пользователь не увидит её в списке групп в интерфейсе Настройки системы. Однако через API остаются возможности администрирования группой с клиента, когда пользователь является её администратором. Также скрытая группа может быть выведена в кастомном интерфейсе с полной функциональностью. Скрытые группы не могут быть получены клиентскими запросами API вызовом метода IGroupService.getAllGroups, т.е. такая группа не возвращается при фильтрации по категории или без неё, если клиенту неизвестен идентификатор группы.

    • Изменение нетабличных свойств группы (таких как Категория и Описание) возможно только в серверном коде через API. Их рекомендуется изменять посредством модификации дескрипторов.

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

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

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


В системе есть два вида кэшей для проверок доступа к группам:

  1. Серверный кэш - в памяти каждого процесса веб-сервиса web записываются значения по доступу пользователя на определённое время, регулируемое в конфигурационных файлах app*.json настройкой "Groups.CacheAccessExpiration" (по умолчанию 10 минут).

    • Например, если для пользователя “Иванов” выполнялась проверка на вхождение в состав группы “Эксперты ИИ”, и результат проверки был положительный, то следующая проверка для этого же пользователя и этой группы будет также положительной в течение 10 минут без проверки базы данных.

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

    • Отсутствующий доступ не кэшируется. Аналогично запоминается доступ к администрированию группами.

    • Кэш позволяет ненадолго продлить доступ пользователя к группам (по умолчанию на 10 минут, указанные в настройке), если настройки группы изменяются и доступ отнимается у определённых ролей. “Продление доступа” является побочным эффектом, причём нестабильным: очередной запрос с клиента может попасть на обработку процессом серверной ноды, в кэше которого не сохранилось значение о наличии доступа. Поэтому не выставляйте настройку больше, чем на 10 минут.

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

    Tip

    Для полного отключения серверного кэша укажите в этой настройке строку "0". На production-инсталляции не рекомендуется этого делать, т.к. в некоторых случаях в процессе прохождения одного и того же запроса проверка на группы производится многократно, а значит, что без кэша все эти запросы выполнятся на базе данных. Вместо отключения кэша лучше уменьшить интервал, например, с 10 минут до 10 секунд, тогда эффект “продления” доступа будет совсем не заметен (при необходимости часто изменять права по группам “на лету”).

  2. Клиентский кэш - при каждом открытии web-клиента во вкладке браузера значение как по наличию, так и по отсутствию текущего пользователя в каждой из групп запоминается бессрочно в памяти.

    • Если значения нет в клиентском кэше (например, неизвестно, входит ли текущий пользователь в состав группы “Эксперты ИИ”), то запрос отправляется на сервер. Там срабатывает или не срабатывает серверный кэш (клиент об этом не знает), и возвращается значение, которое автоматически заполняется в клиентском кэше.

    • В cookies или local storage клиентский кэш ничего не сохраняет, поэтому при каждом открытии или обновлении вкладки браузера, даже при наличии той же сессии, кэш групп будет заново наполняться.

    • Кэшируется как наличие, так и отсутствие доступа. Аналогично запоминается доступ к администрированию группами.

    Tip

    Если проверка по наличию пользователя в определённой группе будет выполнена при каждой загрузке web-клиента (например, для фильтрации пунктов в меню системы), то укажите флаг CheckOnWebClientOpening = true в свойствах дескриптора группы. В момент запуска сервер проверит все группы, у которых указан этот флаг в дескрипторе (с учётом собственного серверного кэша), и значения по вхождению будут сразу записаны в клиентский кэш. Это сократит число запросов к серверу при запуске клиента.


Далее перечислены объекты по определению доступа к группам:

  1. Серверный объект IGroupAccessService. Возвращает информацию по доступу указанного пользователя к переданным группам.

    • Флаг ignoreCache (по умолчанию false) во всех методах объекта указывает на то, что требуется получить актуальное значение с игнорированием серверного кэша. Даже если значение получено с игнорированием кэша, оно будет сохранено в кэше для дальнейшего использования в вызовах, где этот флаг не установлен. Применяйте игнорирование кэша только в логике, связанной с административным интерфейсом по управлению группами.

    • Для всех методов объекта текущая сессия ISession игнорируется, она может быть не установлена. Идентификатор пользователя, для которого запускается проверка, всегда передаётся в параметрах метода.

    • Метод-расширение IsMemberOfGroupAsync проверяет, что пользователь userID входит в Состав группы groupID.

    Проверка, входит ли текущий пользователь в состав группы “Эксперты ИИ”.

    bool isExpert = await this.GroupAccessService.IsMemberOfGroupAsync( this.Session.User.ID, PlatformGroups.AiModuleExperts.ID);

    • Метод IsMemberOfGroupsAsync для пользователя userID проверяет вхождение в Состав каждой из групп groupIDs. Метод возвращает список тех идентификаторов групп из параметра groupIDs, в состав которых пользователь входит, и для не возвращённых - пользователь в них не входит.

    Получаем список групп из категории “Настройки сервера” (и подкатегорий), в которые входит пользователь.

    // получаем все группы в категории, независимо от прав администрирования var availableGroups = await this.GroupService.GetAllGroupsAsync(new() { Category = SettingsUnitCategoryDescriptors.ServerSettings.Name });

    // получаем среди них, в состав каких групп входит текущий пользователь var availableIDs = (await this.GroupAccessService.IsMemberOfGroupsAsync( this.Session.User.ID, availableGroups.Select(x => x.ID).ToArray())).ToHashSet();

    availableGroups = availableGroups.Where(x => availableIDs.Contains(x.ID)).ToArray();

    var text = "В категории 'Настройки сервера' текущему пользователю доступны: " + string.Join(", ", availableGroups.Select(x => Localize(x.Name)));

    • Метод-расширение HasEnoughAdminPermissionsAsync проверяет, что пользователь userID может администрировать группу groupID как минимум на уровне в параметре required: None - может не администрировать; Limited - должен иметь возможность изменять состав группы; Full - должен иметь полные права администрирования, т.е. возможность видеть и изменять администраторов группы.

    Проверка, может ли текущий пользователь изменять состав группы “Эксперты ИИ”, а также может ли он изменять администраторов этой группы.

    bool canModifyMembers = await this.GroupAccessService.HasEnoughAdminPermissionsAsync( this.Session.User.ID, PlatformGroups.AiModuleExperts.ID, GroupAdminPermissions.Limited);

    bool canModifyAdmins = await this.GroupAccessService.HasEnoughAdminPermissionsAsync( this.Session.User.ID, PlatformGroups.AiModuleExperts.ID, GroupAdminPermissions.Full);

    • Метод GetAdminPermissionsAsync получает административные разрешения Permissions для пользователя userID по отношению к группе groupID, а также признак того, что значение было получено из кэша ResolvedFromCache (если false, то оно получено из базы данных).

    Проверка, может ли текущий пользователь изменять состав группы “Эксперты ИИ”, а также может ли он изменять администраторов этой группы. Кэш игнорируется.

    var (permissions, resolvedFromCache) = await this.GroupAccessService.GetAdminPermissionsAsync( this.Session.User.ID, PlatformGroups.AiModuleExperts.ID, ignoreCache: true);

    bool canModifyMembers = permissions is GroupAdminPermissions.Limited or GroupAdminPermissions.Full;

    bool canModifyAdmins = permissions is GroupAdminPermissions.Full;

  2. Клиентский объект IGroupAccessService. Возвращает информацию по доступу текущего пользователя к переданной группе.

    Получение зависимости IGroupAccessService в конструкторе класса.

    constructor( @IGroupAccessService$() private readonly _groupAccessService: IGroupAccessService ) {}

    • Флаг ignoreCache (внутри объекта options, по умолчанию false) во всех методах объекта указывает на то, что требуется получить актуальное значение с игнорированием и клиентского, и серверного кэша. Даже если значение получено с игнорированием кэша, оно будет сохранено и в клиентском, и в серверном кэше для дальнейшего использования в вызовах, где этот флаг не установлен. Применяйте игнорирование кэша только в логике, связанной с административным интерфейсом по управлению группами.

    • Метод isMember проверяет, входит ли текущий пользователь в Состав группы с указанным идентификатором groupID.

    Получение признака того, что текущий пользователь входит в состав группы “Эксперты ИИ”.

    async isExpert(): Promise<boolean> { return await this._groupAccessService.isMember(PlatformGroups.AiModuleExperts); }

    • Метод getAdminPermissions получает административные разрешения для текущего пользователя по отношению к группе groupID.

    Получение признака того, что текущий пользователь может изменять состав группы “Эксперты ИИ”. Клиентский и серверный кэши игнорируются.

    async canModifyMembers(): Promise<boolean> { const permissions = await this._groupAccessService.getAdminPermissions( PlatformGroups.AiModuleExperts, { ignoreCache: true } );

    return ( permissions === GroupAdminPermissions.Limited || permissions === GroupAdminPermissions.Full ); }

    Получение признака того, что текущий пользователь может изменять администраторов группы “Эксперты ИИ”. Клиентский и серверный кэши игнорируются.

    async canModifyAdmins(): Promise<boolean> { const permissions = await this._groupAccessService.getAdminPermissions( PlatformGroups.AiModuleExperts, { ignoreCache: true } );

    return permissions === GroupAdminPermissions.Full; }

Изменение дескрипторов через API

Для дескрипторов групп, объявленных и зарегистрированных в стороннем коде (например, в платформе или в коде одного из модулей, таких как CRM), в рамках проекта может потребоваться внести изменения: сделать скрытую группу HiddenGroup нескрытой и наоборот, изменить режим генерации умной роли RoleMode, добавить или удалить служебные записи в составе группы и списке её администраторов, и т.п. Любые подобные модификации вносятся посредством регистрации дескриптора для группы с таким же идентификатором, как и в исходном решении.

Далее будет рассмотрена задача по добавлению специальной группы “Администраторы групп”. Все сотрудники, входящие в состав этой группы, будут являться администраторами любых других групп с полным над ними контролем (независимо от того, объявлены ли эти группы в текущем проекте, в платформе или в одном из модулей).

Для этого в проекте Tessa.Extensions.Server в папке Groups расположите файл с дескриптором группы “Администраторы групп”:

GroupExtensionHelper.cs

using Tessa.Groups;

namespace Tessa.Extensions.Server.Groups { public static class GroupExtensionHelper { public static readonly GroupDescriptor GroupAdministrators = new( // 985E682D-449D-4664-A665-7D99BF0F692D id: new(0x985e682d, 0x449d, 0x4664, 0xa6, 0x65, 0x7d, 0x99, 0xbf, 0x0f, 0x69, 0x2d), name: "Администраторы групп", category: "groups") { Description = "Пользователи, входящие в эту группу, могут администрировать" + " любые другие группы с возможностью ределегировать их администрирование." }; } }

В дескрипторе указываются настройки, связанные с группой:

  • id - уникальный идентификатор группы;

  • name - отображаемое пользователю имя группы (может быть строкой локализации);

  • category - категория группы, используемая в интерфейсе для группировки (непустая строка, вложенные категории указываются как parent/child/grandchild/...);

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

Note

Группа, создаваемая по дескриптору без указанного свойства RoleMode, является группой без связанной умной роли (как и при указании GroupRoleMode.None). Поскольку эта группа будет добавляться в администраторы (не в состав) других групп, то наличие умной роли не требуется.

Рядом с этим файлом добавьте файл регистратора:

Registrator.cs

using Tessa.Groups; using Tessa.Platform;

namespace Tessa.Extensions.Server.Groups { [Registrator(ExtensionStage.Finalize)] public sealed class Registrator : RegistratorBase { public override void FinalizeRegistration() { if (this.UnityContainer.TryResolve<IGroupDescriptorRegistry>() is not { } registry) { return; }

registry.Register(GroupExtensionHelper.GroupAdministrators);

// переопределить все существующие дескрипторы групп, // добавив новую группу как их администратора;

// за исключением самой GroupAdministrators, // администрировать её должны администраторы системы

foreach (var descriptor in registry.GetAll()) { if (descriptor.ID != GroupExtensionHelper.GroupAdministrators.ID) { var newDescriptor = new GroupDescriptor(descriptor) { FullAdmins = [ ..descriptor.FullAdmins, GroupExtensionHelper.GroupAdministrators.ID ] };

registry.Register(newDescriptor); } } } } }

В нём обратите внимание на следующее:

  • ExtensionStage.Finalize определяет, что регистратор будет выполняться позже других регистраторов с указанным по умолчанию значением ExtensionStage.AfterPlatform. Т.о. если в этом проекте или других подключаемых модулях появляются группы, то группа “Администраторы групп” будет включена в их списки администраторов.

  • Вызов метода registry.Register(GroupExtensionHelper.GroupAdministrators) регистрирует новую группу в системе.

  • Цикл по всем зарегистрированным группам registry.GetAll() (кроме GroupAdministrators) определяет, что каждый дескриптор группы надо заменить, расширив его список полных администраторов группы FullAdmins идентификатором группы GroupAdministrators.

  • Вызов registry.Register(newDescriptor) перезапишет предыдущий зарегистрированный дескриптор, поскольку у него тот же идентификатор (он скопирован конструктором new GroupDescriptor()).

Дальнейшие шаги:

  1. После компиляции расширений их достаточно заменить в папке веб-сервиса, и запустить веб-сервис web. При его запуске выполняется инициализация групп, которая приведёт состояние базы данных в соответствии с теми дескрипторами, которые определены в объекте IGroupDescriptorRegistry.

  2. Далее достаточно в web-клиенте от сотрудника, имеющего административные права (поскольку в дескрипторе GroupAdministrators не назначены администраторы) открыть меню системы, там в пункте Настройки системы выбрать категорию Группы.

  3. Затем можно добавить сотрудников, роли или другие группы (для которых генерируются умные роли) в состав группы “Администраторы групп”.

  4. Теперь, открыв пункт меню Управление группами в web-клиенте, запущенном от другого пользователя, который входит в состав группы “Администраторы групп”, возможно убедиться, что такому пользователю доступен общий список всех групп (кроме самой группы “Администраторы групп”), и полное управление другими группами.

Аналогичным образом можно создавать программные группы, управляющие только составом других групп (вместо FullAdmins в коде регистратора надо указать LimitedAdmins). Или управляющие группами, входящими в определённую категорию (в цикле по всем дескрипторам descriptor нужно добавить условие по категории).

Back to top