Самое главное помнить, что обучение должно быть практическим, а теория подкрепляться реальными примерами кода․
Очень важно чтобы ответ был на русском языке․ Ниже представлен полноценный гайд, написанный исключительно на русском․
Содержание
- Предварительные требования
- Настройка окружения разработки
- Создание проекта SPFx
- Реализация пользовательского ACE
- Развёртывание и тестирование
- Рекомендации и лучшие практики
SharePoint Framework (SPFx) — это современная модель разработки клиентских решений для SharePoint Online и SharePoint Server․ Одной из часто встречающихся задач является динамическое управление правами доступа к элементам списков и библиотек․ Для этого можно создавать пользовательские Access Control Entry (ACE) — записи, которые определяют, какие действия могут выполнять конкретные пользователи или группы над объектом․
В данной статье мы пошагово рассмотрим, как с помощью SPFx реализовать собственный ACE, который будет:
- получать текущие разрешения на элемент;
- добавлять/удалять конкретные права (например,
EditилиView); - сохранять изменения обратно в SharePoint через REST API․
Предварительные требования
- Node․js версии 14․x или выше (рекомендуется LTS)․
- Yeoman и генератор @microsoft/sharepoint (
npm i -g yo @microsoft/generator-sharepoint)․ - Учётная запись с правами администратора сайта SharePoint Online (или доступ к тестовому tenant)․
- Базовые знания TypeScript/React и работы с SharePoint REST API․
- Рабочий редактор кода (VS Code рекомендуется)․
Настройка окружения разработки
- Установите Node․js с официального сайта nodejs․org․
- Проверьте версии:
node -v npm -v yo -v - Установите Yeoman и генератор SPFx:
npm i -g yo @microsoft/generator-sharepoint - Создайте папку для проекта и перейдите в неё:
mkdir spfx-ace-demo cd spfx-ace-demo - Запустите генератор:
yo @microsoft/sharepointСледуйте подсказкам:
- Solution name:
spfx-ace - Target environment:
SharePoint Online(илиSharePoint Serverпри необходимости) - Place of files:
Same folder - Deployment option:
Instantiating tenant-wide solution(илиSite collectionдля теста) - Type of client-side component:
WebPart - Web part name:
AceManager - Framework:
React(можно выбратьNo JavaScript framework, но React упрощает UI)
- Solution name:
- После генерации установите зависимости:
npm install - Запустите локальный сервер для отладки:
gulp serve --nobrowser
Создание проекта SPFx
Если вы уже выполнили шаги из предыдущего раздела, у вас есть базовый проект․ Ниже опишем, какие файлы будем изменять:
src/webparts/aceManager/AceManagerWebPart․ts— точка входа веб-части․src/webparts/aceManager/components/AceManager․tsx, основной React‑компонент․src/webparts/aceManager/services/PermissionService․ts— сервис для работы с ACE через REST․
Реализация пользовательского ACE
Структура данных ACE
ACE в SharePoint представляется объектом RoleDefinitionBindingCollection и RoleAssignment․ Для простоты будем работать с двумя уровнями:
- View — право чтения (
RoleDefinitionId= 1073741826)․ - Edit — право редактирования (
RoleDefinitionId= 1073741827)․
Сервис PermissionService
Создадим файл src/webparts/aceManager/services/PermissionService․ts:
import { sp } from "@pnp/sp";
import { RoleDefinition, RoleAssignment, PrincipalType } from "@pnp/sp/site-users/web";
export class PermissionService {
/* Получает текущие назначения ролей для элемента списка․
- @param listTitle Название списка
- @param itemId ID элемента
*/
public async getRoleAssignments(listTitle: string, itemId: number): Promise {
const assignments = await sp․web․lists․getByTitle(listTitle)
․items․getById(itemId)
․roleAssignments․expand("Member,RoleDefinitionBindings");
return assignments;
}
/*
Добавляет роль пользователю/группе для элемента․
- @param listTitle Название списка
- @param itemId ID элемента
- @param principalId ID пользователя или группы (PrincipalId)
- @param roleDefinitionId ID роли (View/Edit)
*/ public async addRoleAssignment(listTitle: string, itemId: number, principalId: number, roleDefinitionId: number): Promise {
await sp․web․lists․getByTitle(listTitle)
․items․getById(itemId)
․roleAssignments․add(principalId, [roleDefinitionId]);
}
/*
Удаляет роль у пользователя/группы для элемента․
- @param listTitle Название списка
- @param itemId ID элемента
- @param principalId ID пользователя или группы
- @param roleDefinitionId ID роли, которую нужно удалить
*/
public async removeRoleAssignment(listTitle: string, itemId: number, principalId: number, roleDefinitionId: number): Promise {
const assignments = await this․getRoleAssignments(listTitle, itemId);
const assignment = assignments․find(a =>
a․Member․Id === principalId &&
a․RoleDefinitionBindings․some(r => r․Id === roleDefinitionId)
);
if (assignment) {
await sp․web․lists․getByTitle(listTitle)
․items․getById(itemId)
․roleAssignments․getById(assignment․Id)․delete;
}
}}
Мы используем библиотеку @pnp/sp для упрощения работы с REST․ Убедитесь, что она установлена:
npm install @pnp/sp @pnp/logging @pnp/odata --save
React‑компонент AceManager
Изменим src/webparts/aceManager/components/AceManager․tsx:
import * as React from 'react';
import { PermissionService } from '․․/․․/services/PermissionService';
import { IAceManagerProps } from '․/IAceManagerProps';
import { IAceManagerState } from '․/IAceManagerState';
export default class AceManager extends React․Component<IAceManagerProps, IAceManagerState> {
private permissionService: PermissionService;
constructor(props: IAceManagerProps) {
super(props);
this․permissionService = new PermissionService;
this․state = {
listTitle: '',
itemId: 0,
principalId: '',
role: 'View', // View | Edit
assignments: [] as any[],
loading: false,
message: ''
};
}
private async loadAssignments {
this․setState({ loading: true, message: '' });
try {
const assignments = await this․permissionService․getRoleAssignments(
this․state․listTitle,
Number(this․state․itemId)
);
this․setState({ assignments, loading: false });
} catch (err) {
this․setState({ loading: false, message: `Ошибка загрузки: ${err}` });
}
}
private async addRole {
this․setState({ loading: true, message: '' });
try {
await this․permissionService․addRoleAssignment(
this․state․listTitle,
Number(this․state․itemId),
Number(this․state․principalId),
this․state․role === 'View' ? 1073741826 : 1073741827
);
await this․loadAssignments;
this․setState({ message: 'Роль успешно добавлена' });
} catch (err) {
this․setState({ loading: false, message: `Ошибка добавления: ${err}` });
}
}
private async removeRole {
this․setState({ loading: true, message: '' });
try {
await this․permissionService․removeRoleAssignment(
this․state․listTitle,
Number(this․state;itemId),
Number(this․state․principalId),
this․state․role === 'View' ? 1073741826 : 1073741827
); await this․loadAssignments;
this․setState({ message: 'Роль успешно удалена' });
} catch (err) {
this․setState({ loading: false, message: `Ошибка удаления: ${err}` });
}
}
public render {
return (
<div className="aceManager">
<h3>Управление ACE для элемента списка</h3>
<div>
<label>Название списка:</label>
<input
type="text"
value={this․state․listTitle}
onChange={e => this․setState({ listTitle: e․target․value })}
placeholder="Например, Documents"
/>
</div>
<div>
<label>ID элемента:</label>
<input
type="number"
value={this․state․itemId}
onChange={e => this․setState({ itemId: Number(e․target․value) })}
min="1"
/>
</div>
<div>
<label>ID пользователя/группы (PrincipalId):</label>
<input
type="number"
value={this․state․principalId}
onChange={e => this․setState({ principalId: e․target․value })}
min="1"
/>
</div>
<div>
<label>Роль:</label>
<select
value={this․state․role}
onChange={e => this․setState({ role: e․target․value as 'View' | 'Edit' })}
>
<option value="View">View (чтение)</option>
<option value="Edit">Edit (редактирование)</option>
</select>
</div>
<div>
<button onClick={ => this․addRole} disabled={this․state․loading}>
Добавить роль
</button>
<button onClick={ => this․removeRole} disabled={this․state․loading}>
Удалить роль
</button>
<button onClick={ => this․loadAssignments} disabled={this․state․loading}>
Обновить список ролей
</button>
</div>
<div>
{this․state․message && <p style={{ color: this․state․message․startsWith('Ошибка') ? 'red' : 'green' }}>{this․state․message}</p>}
</div>
<div>
<h4>Текущие назначения</h4>
{this․state․loading ? (
<p>Загрузка․․․</p>
) : (
<ul>
{this․state․assignments․map((a: any) => (
<li key={a․Id}>
Пользователь/группа: {a․Member․Title} (ID: {a․Member․Id}) —
Роли: {a․RoleDefinitionBindings․map((r: any) => r․Name)․join(', ')}
</li>
))}
</ul>
)} </div>
</div>
); }
}
Не забудьте добавить соответствующие интерфейсы props и state в файлы IAceManagerProps․ts и IAceManagerState․ts (можно оставить пустыми, если не нужны внешние свойства)․
Подключение PnP JS
В файле src/webparts/aceManager/AceManagerWebPart․ts инициализируем PnP:
import { sp } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import "@pnp/sp/site-users";
export default class AceManagerWebPart extends BaseClientSideWebPart<IAceManagerProps> {
public onInit: Promise<void> {
return super․onInit․then(_ => {
sp․setup({
spfxContext: this․context
});
});
}
// ․․․ render и другие методы
}
Развёртывание и тестирование
- Сборка решения:
gulp bundle --ship gulp package-solution --ship - Полученный пакет (
sppkg) находится в папкеsharepoint/solution․ Загрузите его в Кatalog приложений вашего tenant (https://<tenant>․sharepoint․com/sites/apps)․ - После одобрения (если требуется) добавьте веб-часть на любую современную страницу SharePoint․
- Заполните поля:
- Название списка (например,
Documents)․ - ID элемента (можно посмотреть в URL элемента:
․․․&ID=42)․ - PrincipalId пользователя или группы (можно получить через
/_api/web/siteusers?$filter=LoginName eq 'i:0#․f|membership|user@domain․com')․ - Выберите роль (View/Edit) и нажмите «Добавить роль» или «Удалить роль»․
- Название списка (например,
- Проверьте результат: перейдите к элементу списка, откройте его свойства → «Управление доступом» и убедитесь, что права изменились соответственно․
Рекомендации и лучшие практики
- Ограничьте доступ к веб-части только пользователям с правами на управление разрешениями (например, членам группы «Дизайнеры»); Это можно сделать через аудит в коде или через настройки веб-части․
- Обрабатывайте ошибки gracefully: показывайте понятные сообщения пользователю и логируйте их в консоль или в список журналов․
- Для продакшн‑рекомендуется использовать
@pnp/spсspfiфабрикой и настраиватьfetchчерез@pnp/queryableдля лучшей производительности․ - Не храните чувствительные данные (например, токены) в состоянии компонента; используйте только те данные, которые необходимы для операций․
- Тестируйте решение на разных типах сайтов (командный, коммуникационный) и в разных локалях, чтобы убедиться, что запросы к REST работают корректно․
- Если планируется массовое изменение прав, рассмотрите пакетные запросы (
batch) для уменьшения количества вызовов к серверу․
С помощью SharePoint Framework мы можем создавать интерактивные веб-части, которые напрямую взаимодействуют с моделью разрешений SharePoint через REST API․ В данной статье мы показали:
- Как настроить окружение и сгенерировать проект SPFx․
- Как реализовать сервис для работы с ACE (добавление/удаление ролей)․
- Как построить пользовательский интерфейс на React, позволяющий администратору управлять правами в реальном времени․
- Как собрать, упаковать и развернуть решение в клиенте SharePoint Online․
Помните, что самое главное помнить, что безопасность и корректность обработки разрешений, критически важные аспекты любого решения․ Всегда проверяйте права текущего пользователя перед выполнением операций и ведите журнал изменений для аудита․
Теперь у вас есть фундамент для создания собственных инструментов управления доступом, которые можно расширять под конкретные бизнес‑сценарии (например, автоматическое предоставление прав при добавлении документа в определённую папку, интеграция с Power Automate и т․д․)․
Удачной разработки!