Перейти к содержимому

API: Права доступа и группы

Пошаговый гайд для разработчиков расширений (C# SDK: check, Grant, группы, AD):
Права доступа из кода.

Требуется авторизация.

resourceTypeНаследование
nodeРодительские узлы до корня
listСписок → узел → предки узла
list_itemЭлемент → список → узел → предки (или только элемент при inherits_permissions = false)
libraryБиблиотека → узел → предки
pageСтраница → узел → предки
surveyОпрос → узел → предки
fileФайл/папка → родительские папки → библиотека → узел → предки
attachmentВложение → элемент → список → узел → предки
resourceTyperesourceIdДоп. параметры
nodeUUID узла
listUUID списка
libraryUUID библиотеки
pageUUID страницы
surveyUUID опроса
list_itemUUID элемента или числовой id (порядковый номер в списке)listId — обязателен при числовом resourceId
fileUUID файла/папки или числовой idlibraryId — обязателен при числовом resourceId
attachmentUUID вложения

Внутри БД ACL всегда хранится по UUID. REST принимает публичный числовой id элементов и файлов, если передан контекст списка/библиотеки.

GET /permissions?resourceType={type}&resourceId={id}&listId={uuid}&libraryId={uuid}

Заголовок раздела «GET /permissions?resourceType={type}&resourceId={id}&listId={uuid}&libraryId={uuid}»

Список явных назначений на ресурс (без унаследованных). Требуется право edit на ресурс.

Примеры:

GET /api/v1/permissions?resourceType=node&resourceId=550e8400-e29b-41d4-a716-446655440000
GET /api/v1/permissions?resourceType=list_item&resourceId=12&listId=550e8400-e29b-41d4-a716-446655440000
GET /api/v1/permissions?resourceType=file&resourceId=5&libraryId=660e8400-e29b-41d4-a716-446655440001

Добавить или обновить права (upsert по паре principal + resource). Требуется право edit на ресурс. Для list, library и survey — только администратор портала.

Тело запроса:

{
"resourceType": "list",
"resourceId": "550e8400-e29b-41d4-a716-446655440000",
"principalType": "group",
"principalId": "770e8400-e29b-41d4-a716-446655440002",
"permissionLevel": "view"
}

Для элемента списка с числовым id:

{
"resourceType": "list_item",
"resourceId": "12",
"listId": "550e8400-e29b-41d4-a716-446655440000",
"principalType": "user",
"principalId": "880e8400-e29b-41d4-a716-446655440003",
"permissionLevel": "edit"
}

Для файла/папки:

{
"resourceType": "file",
"resourceId": "5",
"libraryId": "660e8400-e29b-41d4-a716-446655440001",
"principalType": "group",
"principalId": "770e8400-e29b-41d4-a716-446655440002",
"permissionLevel": "view"
}

Уровни: view, add, edit, delete (каждый следующий включает предыдущие). В интерфейсе delete отображается как Полный доступ.

principalType: user или group.

В панели прав UIодин чип-пикер: несколько пользователей и групп сразу, один уровень на всех выбранных. Поиск субъектов — GET /users?q= и GET /groups?q= (рядом с селектом уровня — справка ?).

Удалить права по UUID записи ACL. Требуется право edit на соответствующий ресурс.

GET /permissions/check?resourceType={type}&resourceId={id}&listId={uuid}&libraryId={uuid}

Заголовок раздела «GET /permissions/check?resourceType={type}&resourceId={id}&listId={uuid}&libraryId={uuid}»

Эффективные права текущего пользователя с учётом наследования по цепочке ресурсов и членства в группах.

Ответ 200:

{
"success": true,
"data": {
"permissionLevel": "edit",
"canView": true,
"canAdd": true,
"canEdit": true,
"canDelete": false
}
}

Если доступа нет, permissionLevel будет null, все флаги — false.

Только portal_admin. Управление группами в UI: Группы доступа.

group_typeОписаниеCRUD в UI / API
portalСоздана вручную в порталеПолный CRUD и управление участниками
domain_linkedИмпортирована из AD (ad-sync)Только чтение; изменение состава — через ad-sync

Для domain_linked запросы PATCH, DELETE, POST/DELETE .../members возвращают ошибку валидации: «Состав группы управляется синхронизацией AD (ad-sync)» (для update/delete группы — аналогично).

МетодПутьОписание
GET/groupsСписок всех групп (id, name, description, group_type, members_count)
GET/groups?q=...Поиск по названию/описанию (до 20, для picker’ов назначения прав)
POST/groupsСоздать группу portal
GET/groups/:idГруппа с массивом members
PATCH/groups/:idОбновить name и/или description (только portal)
DELETE/groups/:idУдалить группу (только portal)
POST/groups/:id/membersДобавить участника (только portal)
DELETE/groups/:id/members/:userIdУдалить участника (только portal)
POST /api/v1/groups
Content-Type: application/json
{ "name": "Редакторы HR", "description": "Редактирование раздела HR" }
PATCH /api/v1/groups/{id}
Content-Type: application/json
{ "name": "Новое название", "description": "Обновлённое описание" }

По существующему пользователю портала:

POST /api/v1/groups/{id}/members
Content-Type: application/json
{ "userId": "uuid" }

Upsert доменного пользователя из каталога (создаётся запись в users при необходимости):

POST /api/v1/groups/{id}/members
Content-Type: application/json
{
"externalId": "object-guid-from-ad",
"login": "ivanov",
"displayName": "Иванов Иван",
"email": "ivanov@company.local"
}

Удаление участника:

DELETE /api/v1/groups/{id}/members/{userId}

В группу добавляются пользователи, не вложенные группы. AD-группы как отдельные сущности создаются только через ad-sync (см. API: admin).

{
"success": true,
"data": {
"id": "uuid",
"name": "Редакторы HR",
"description": "",
"group_type": "portal",
"members": [
{
"id": "user-uuid",
"login": "petrov",
"display_name": "Петров Пётр",
"auth_source": "domain"
}
]
}
}

При удалении группы инвалидируется кэш прав; назначения principalType: "group" на эту группу перестают действовать.

Список пользователей для назначения прав (только admin).

Карточка сотрудника (любой аутентифицированный)

Заголовок раздела «Карточка сотрудника (любой аутентифицированный)»
МетодПутьОписание
GET/users/{id}/profileПрофиль: имя, email, card_fields (meta по personCardVisibleFields в ad-sync), has_avatar, manager_user_id
GET/users/{id}/avatarФото из объектного хранилища (image/jpeg) или 404
GET/users/{id}/org-chainЦепочка руководителей от корня до пользователя (chain[])
GET/users/{id}/org-childrenПрямые подчинённые для lazy-раскрытия (children[])

Узлы оргструктуры: id, display_name, login, title, has_avatar, has_children.

const profile = await PortalApi.users.getProfile(userId);
const chain = await PortalApi.users.getOrgChain(userId);
const children = await PortalApi.users.getOrgChildren(userId);
const imgSrc = PortalApi.users.avatarUrl(userId); // /api/v1/users/{id}/avatar
  • WebPart: ctx.Users.GetProfileAsync / GetOrgChainAsync / GetOrgChildrenAsync / AvatarUrl — см. WebPart SDK
  • Event Receiver: api.Users.* — см. Event Receiver SDK
  • Timer Job: api.Users.* — см. Timer Job SDK

Типы ответа: Portal.Contracts.Users (UserPublicProfile, UserOrgNode, UserOrgChain, UserOrgChildren).

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

Для файлов и папок библиотеки цепочка включает все родительские папки. Если у папки или файла отключено наследование (inherits_permissions = false), объект становится границей: права библиотеки и узла выше границы не применяются к нему и его потомкам. При отключении наследования копируются эффективные права родителя как явные назначения.

Для элементов списка поведение аналогично: по умолчанию inherits_permissions = true, цепочка list_item → list → node → …. При отключении наследования элемент становится границей — права списка и узла не применяются; при снятии флага копируются эффективные права списка (и узла) как явные назначения на list_item. Управление флагом — PATCH /lists/{listId}/items/{itemRef}/permission-settings (только администратор портала). Явные права на элемент — через /api/v1/permissions с resourceType=list_item и listId.

Настройки наследования (элементы и документы)

Заголовок раздела «Настройки наследования (элементы и документы)»

Только администратор портала.

GET /api/v1/lists/{listId}/items/{itemRef}/permission-settings
PATCH /api/v1/lists/{listId}/items/{itemRef}/permission-settings
Content-Type: application/json
{ "inheritFromParent": false }
GET /api/v1/libraries/{libraryId}/files/{fileRef}/permission-settings
PATCH /api/v1/libraries/{libraryId}/files/{fileRef}/permission-settings
{ "inheritFromParent": false }

itemRef / fileRef — UUID или числовой id. При inheritFromParent: false копируются эффективные права родителя; при true — удаляются уникальные назначения на ресурсе.

Ниже — типовые сценарии проверки и назначения прав для всех типов ресурсов. Уровни: view < add < edit < deleteUI «Полный доступ»). Администратор портала (is_portal_admin) всегда получает delete на любой ресурс.

resourceTypeКто может вызывать POST/DELETE /permissions
node, pageПользователь с edit на ресурс
list, library, survey, list_item, fileТолько администратор портала (плюс edit на ресурс)
attachmentПользователь с edit на вложение
Окно терминала
# Узел
curl -s -b cookies.txt \
"$BASE/api/v1/permissions/check?resourceType=node&resourceId=$NODE_ID"
# Список
curl -s -b cookies.txt \
"$BASE/api/v1/permissions/check?resourceType=list&resourceId=$LIST_ID"
# Элемент списка (числовой id)
curl -s -b cookies.txt \
"$BASE/api/v1/permissions/check?resourceType=list_item&resourceId=12&listId=$LIST_ID"
# Библиотека
curl -s -b cookies.txt \
"$BASE/api/v1/permissions/check?resourceType=library&resourceId=$LIBRARY_ID"
# Файл / папка
curl -s -b cookies.txt \
"$BASE/api/v1/permissions/check?resourceType=file&resourceId=5&libraryId=$LIBRARY_ID"
# Страница
curl -s -b cookies.txt \
"$BASE/api/v1/permissions/check?resourceType=page&resourceId=$PAGE_ID"
# Вложение к элементу списка
curl -s -b cookies.txt \
"$BASE/api/v1/permissions/check?resourceType=attachment&resourceId=$ATTACHMENT_ID"

Ответ при достаточных правах:

{
"success": true,
"data": {
"permissionLevel": "edit",
"canView": true,
"canAdd": true,
"canEdit": true,
"canDelete": false
}
}

Если доступа нет: permissionLevelnull, все флаги false.

Окно терминала
# Выдать группе «Редактирование» на узел
curl -s -b cookies.txt -X POST "$BASE/api/v1/permissions" \
-H 'Content-Type: application/json' \
-d '{
"resourceType": "node",
"resourceId": "'"$NODE_ID"'",
"principalType": "group",
"principalId": "'"$GROUP_ID"'",
"permissionLevel": "edit"
}'
# Список явных назначений на страницу
curl -s -b cookies.txt \
"$BASE/api/v1/permissions?resourceType=page&resourceId=$PAGE_ID"
# Удалить права
curl -s -b cookies.txt -X DELETE "$BASE/api/v1/permissions/$PERMISSION_ID"

Поиск пользователей и групп для picker’ов (только admin):

GET /api/v1/users?q=иванов
GET /api/v1/groups?q=hr

Проверка через PortalApi (как в listDetailView.js, permissionsPanel.js):

async function checkPermissions(resourceType, resourceId, context = {}) {
const params = new URLSearchParams({
resourceType,
resourceId: String(resourceId),
});
if (resourceType === 'list_item' && context.listId) {
params.set('listId', String(context.listId));
}
if (resourceType === 'file' && context.libraryId) {
params.set('libraryId', String(context.libraryId));
}
const res = await PortalApi.get(`/permissions/check?${params}`);
return res.data; // { permissionLevel, canView, canAdd, canEdit, canDelete }
}
// Узел
const nodePerms = await checkPermissions('node', nodeId);
if (!nodePerms.canEdit) return;
// Элемент списка (удобнее брать item.permissions из GET /lists/{id}/items)
const itemPerms = await checkPermissions('list_item', 12, { listId });
if (itemPerms.canEdit) { /* редактирование */ }
// Файл
const filePerms = await checkPermissions('file', 5, { libraryId });

Добавить права:

await PortalApi.post('/permissions', {
resourceType: 'list_item',
resourceId: String(itemId),
listId,
principalType: 'group',
principalId: groupId,
permissionLevel: 'view',
});
// Список явных назначений
const query = new URLSearchParams({
resourceType: 'file',
resourceId: String(fileId),
libraryId,
});
const list = await PortalApi.get(`/permissions?${query}`);

Панель прав в UI: PortalPermissionsPanel.render(resourceType, resourceId, $container, { listId, libraryId }) — см. frontend/public/js/permissions/permissionsPanel.js. Форма назначения использует единый multi-пикер пользователей и групп (PortalAclPrincipalPicker).

WebPart (portalContext.js) — упрощённая проверка для node, list, library, page (без listId/libraryId):

const ctx = PortalWebPartContext.create({ nodeId, api: PortalApi });
const canEditList = await ctx.permissions.check('list', listId, 'edit');
// level: 'view' | 'add' | 'edit' | 'delete'

Для list_item и file в WebPart вызывайте PortalApi.get с listId / libraryId напрямую (см. пример checkPermissions выше).

Рекомендуемый путь в расширениях — ctx.Permissions / api.Permissions (те же серверные правила ACL).

using Portal.Contracts.Permissions;
// WebPart
var listId = ctx.Properties.GetGuid("listId"); // или FindBySlug / ResolvePath — см. гайд
var node = await ctx.Permissions.ForCurrentNodeAsync(ct);
var list = await ctx.Permissions.ForListAsync(listId, ct);
var item = await ctx.Permissions.ForListItemAsync(listId, "12", ct);
var editors = await ctx.Groups.FindByNameAsync("Редакторы HR", ct);
await ctx.Permissions.GrantToGroupAsync(
PermissionResource.ListItem, "12", editors!.Id, PermissionLevel.Edit, listId: listId, ct: ct);
var acl = await ctx.Permissions.ListAsync(PermissionResource.ListItem, "12", listId: listId, ct: ct);
await ctx.Permissions.RevokeAsync(acl[0].Id, ct);
// Event Receiver / Timer Job — тот же API: api.Permissions.ForListAsync(…)
ТипОписание
PortalPermissionLevel, CanView / CanAdd / CanEdit / CanDelete, Has(level)
PortalAclEntryЯвная запись ACL (Id, PrincipalType, PrincipalId, PermissionLevel, …)
PermissionResourceКонстанты Node, List, ListItem, Library, Page, Survey, File, Attachment

Кто может назначать — см. таблицу выше. На list / library / survey — только portal admin; Timer Job идёт от portal-system (admin) и может менять ACL.

Инжектируйте PermissionService (проверка) и PermissionAdminService (управление ACL), если пишете код платформы, а не extension SDK.

using Portal.Contracts.Permissions;
using Portal.Application.Permissions;
var canEdit = await permissionService.HasPermissionAsync(
user, "list_item", itemGuid, PermissionLevel.Edit, ct);
await permissionAdmin.UpsertAsync(user, new UpsertPermissionRequest
{
ResourceType = "list_item",
ResourceId = "12",
ListId = listId,
PrincipalType = "user",
PrincipalId = userId,
PermissionLevel = "view",
}, ct);

После прямых изменений в PermissionRepository вызовите permissionService.InvalidatePermissionCacheAsync().

ОперацияМинимальный уровеньresourceType
Открыть узел / список / библиотекуviewnode, list, library
Создать элемент спискаadd на listlist
Изменить элементedit на list_itemview на list)list_item
Удалить элементdelete на list_itemlist_item
Загрузить файлadd на library или родительскую папкуlibrary / file
Скачать / открыть документview на filefile
Редактировать страницуedit на pagepage
Создать опросedit на nodenode
Пройти открытый опросview на surveyview на узел)survey
Конструктор / результаты опросаedit на surveysurvey
Управлять ACL ресурсаedit на ресурс (+ admin для list/library/survey/list_item/file)соответствующий тип

Не путайте права на список (list) и на строку (list_item): для конфиденциальной записи назначайте ACL на list_item или отключайте наследование элемента от списка.