Univer
Univer Sheet
Features
Permission

Permission Control

📊 Univer Sheet

Univer provides permission control capabilities, typically used to restrict user operations on workbooks, sheets, and ranges. When a user performs an operation without permission, the code execution can be halted and the user will be prompted about the missing permissions. For example, you can set area protection within a range, which allows setting whether other collaborators can edit, read, copy, filter, etc., within the protected range.

Note: Univer provides extendable foundational capabilities, not customized features. If you have more extensive and tailored requirements such as persistence or organizational structures, you need to implement the storage of permission rules and integrate organizational structures yourself. In this case, you need to write custom plugins to achieve this. Therefore, after setting permissions, you might find the permission list empty when viewing it, or user information returns empty when you check it. This is because this information needs to be fetched via API requests; it's custom logic that requires additional implementation on your part. You can refer to the third-party integration below.

Basic Example

Workbook Permissions

Currently, workbook-level permissions are directly modified through the API. We provide the Facade API, and you can use this API to set permissions for different functions of the workbook.

Taking edit permission as an example (other functions require replacing the permission points, which are listed at the bottom of the article and need to be imported from @univerjs/sheets), you can use the following code to set the entire workbook to be non-editable:

import { FUniver } from "@univerjs/facade";
 
const univerAPI = FUniver.newAPI(univer);

Then, you can use the univerAPI methods to get the FPermission object and set specific permissions:

const permission = univerAPI.getPermission();
// unitId is the workbook id, WorkbookEditablePermission is the permission point, import { WorkbookEditablePermission } from '@univerjs/sheets', false means the permission is not available
permission.setWorkbookPermissionPoint(unitId, WorkbookEditablePermission, false)

Worksheet Permissions Code Example

Worksheet and range-related permissions can be set via API and command modes. Here, we'll use worksheet edit permissions as an example. Other worksheet functions require replacing the permission points, which are listed at the bottom of the article.

API Method

const permission = univerAPI.getPermission();
// Create worksheet permission, unitId is the workbook id, subUnitId is the sheet id, the generated permission is a basic permission used for rendering permission areas
const permissionId = await permission.addWorksheetBasePermission(unitId, subUnitId);
permission.setWorksheetPermissionPoint(unitId, subUnitId, WorksheetEditPermission, false);

Command Mode

const accessor = univer.__getInjector();
const commandService = accessor.get(ICommandService);
// import { getSheetCommandTarget } from '@univerjs/sheets';
const target = getSheetCommandTarget(univerInstanceService);
if (!target) {
  return;
}
const { unitId, subUnitId } = target;
commandService.executeCommand(AddWorksheetProtectionMutation.id, {
  unitId,
  subUnitId,
  rule: {
    permissionId: "2sxcza1",
    name: "sheet",
    unitType: 2,
    unitId,
    subUnitId,
  },
});
 
const permissionService = accessor.get(IPermissionService);
permissionService.updatePermissionPoint(new WorksheetEditPermission(unitId, subUnitId).id, false);

Parameters in this command: permissionId is a unique ID you generate to store the permission, unitType is the sheet type (you can use the UnitObject enum from the repository), unitId is the workbook id, and subUnitId is the sheet id.

Delete Worksheet Permissions

To delete existing worksheet permissions, you can use command mode to drive DeleteWorksheetProtectionMutation,withunitIdandsubUnitId` as parameters.

DeleteWorksheetProtectionMutationParams {
  unitId: string;
  subUnitId: string;
}

You can also use the API to delete all permissions settings for a worksheet:

const permission = univerAPI.getPermission();
permission.removeWorksheetPermission(unitId, subUnitId);

Custom Range Permissions Code Example

Range permissions can also be set via API and command modes. Here, we'll use range edit permissions as an example. Other range functions require replacing the permission points, which are listed at the bottom of the article.

API Method

const permission = univerAPI.getPermission();
const res = await permission.addRangeBasePermission(unitId, subUnitId, ranges);
// The response differs from worksheet permissions as a sheet may have multiple range protections, so ruleId is used for storing the unique permission rule, and permissionId is for combining permission points.
const { permissionId, ruleId } = res;
permission.setRangeProtectionPermissionPoint(unitId, subUnitId, permissionId, RangeProtectionPermissionEditPoint, false);

Command Mode

const accessor = univer.__getInjector();
const commandService = accessor.get(ICommandService);
const sheetSelectionManagerService = accessor.get(SheetsSelectionsService);
// import { getSheetCommandTarget } from '@univerjs/sheets';
const target = getSheetCommandTarget(univerInstanceService);
if (!target) {
  return;
}
const { unitId, subUnitId } = target;
const ranges = sheetSelectionManagerService.getCurrentSelections().map(selection => selection.range);
commandService.executeCommand(AddRangeProtectionMutation.id, {
  unitId,
  subUnitId,
  rules: [{
    permissionId: "3xtfxG1",
    name: "sheet1",
    unitType: 3,
    unitId,
    subUnitId,
    ranges,
    id: 'rule1'
  }],
});
 
const permissionService = accessor.get(IPermissionService);
// Here, RangeProtectionPermissionEditPoint's third parameter is the generated permissionId, false means not editable
permissionService.updatePermissionPoint(new RangeProtectionPermissionEditPoint(unitId, subUnitId, "3xtfxG1").id, false);

Here, the parameters are similar to those above, with additional ranges parameter for selected areas, and id parameter for uniquely identifying the rule, used for deletion.

Delete Range Protection Permissions

To delete existing custom range protections, use command mode to drive DeleteRangeProtectionMutation, with unitId, subUnitId, and the ruleIds to be deleted.

DeleteRangeProtectionMutationParams {
  unitId: string;
  subUnitId: string;
  ruleIds: string[];
}

You can also use the API to delete all permission settings for a selection.

const permission = univerAPI.getPermission();
permission.removeRangeProtection(unitId, subUnitId, ruleIds);

Extended usage

Here we take WorkbookEditablePermission as an example and add permission verification in your own plug-in. Other points are similar.

import { WorkbookEditablePermission } from '@univerjs/sheets';
import { IPermissionService } from '@univerjs/core';
 
class YourService {
  constructor(@IPermissionService private _permissionService: IPermissionService) {
 
  }
 
  setWorkbookNotEditable() {
    this._permissionService.updatePermissionPoint(new WorkbookEditablePermission('unitId').id, false);
  }
 
  setWorkbookEditable() {
    this._permissionService.updatePermissionPoint(new WorkbookEditablePermission('unitId').id, true);
  }
}

You can also extend and modify other permission points to achieve permission control for different functions. For a specific list of points, please refer to the bottom of the article.

How to extend the permission point

import { IPermissionService, IPermissionPoint } from '@univerjs/core';
 
export class CustomPermissionPoint implements IPermissionPoint {
  type = UnitObject.Unkonwn; // your type
  subType = UnitAction.View; // your subType
  status = PermissionStatus.INIT;
  value = true; // Initial values
  id: string;
  constructor(unitId: string, subUnitId: string, customId: string) {
    // The id attribute needs to be guaranteed to be unique throughout `IPermissionService`.
    this.id = `${unitId}.${subUnitId}.${customId}`;
  }
}
 
class YourService {
  constructor(@IPermissionService private _permissionService: IPermissionService) {
    this._init()
  }
 
  _init() {
    this._permissionService.addPermissionPoint(new CustomPermissionPoint('unitId','subUnitId','my-id'));
  }
}
 
// How to use it elsewhere
class ConsumeService {
  constructor(@IPermissionService private _permissionService: IPermissionService) {
  }
 
  doSomething() {
   const point = this._permissionService.getPermissionPoint(new CustomPermissionPoint('unitId','subUnitId','my-id').id);
   console.log(point.value);
  }
 
  bindEvent() {
    // This will get an RX object, allowing you to listen for changes to the current permissions and make a list of changes
    const pount$ = this._permissionService.getPermissionPoint$(new CustomPermissionPoint('unitId','subUnitId','my-id').id);
    console.log(pount$);
  }
}

Integration of Third-Party Authorization Service(Advanced Usage)

🚨

It is recommended to avoid mixing custom permission access with the Permission Facade API, as it allows for more granular control over permission points.

The logic for determining permissions is typically handled by an external service, which involves a communication process. In the frontend SDK implementation, we use the AuthzIoLocalService (opens in a new tab) to handle this logic.

In a production environment, we need to replace this implementation with a backend service. The frontend needs to implement the corresponding request functions based on the IAuthzIoService (opens in a new tab) interface for runtime replacement.

Permission

Here is a simple example demonstrating the addition and deletion of protected range permissions for two predefined roles (Owner/Reader). The Owner has editing/viewing permissions for protected ranges, while the Reader cannot edit or view the contents of cells within protected ranges.

import { IAuthzIoService, Injector, Univer } from '@univerjs/core';
 
import { IAuthzIoService, Injector, Univer, Inject, generateRandomId, IResourceManagerService, createDefaultUser, isDevRole, UserManagerService } from '@univerjs/core';
import { ObjectScope, UnitAction, UnitObject, UnitRole, UniverType } from '@univerjs/protocol';
import type { IActionInfo, IAllowedRequest, IBatchAllowedResponse, ICollaborator, ICreateRequest, ICreateRequest_SelectRangeObject, IListPermPointRequest, IPermissionPoint, IPutCollaboratorsRequest, IUnitRoleKV, IUpdatePermPointRequest } from '@univerjs/protocol';
 
class YourAuthzService implements IAuthzIoService {
    private _permissionMap: Map<string, ICreateRequest_SelectRangeObject & { objectType: UnitObject }> = new Map([]);
 
    constructor(
        @IResourceManagerService private _resourceManagerService: IResourceManagerService,
        @Inject(UserManagerService) private _userManagerService: UserManagerService
    ) {
        this._initSnapshot();
        this._initDefaultUser();
    }
 
    private _initDefaultUser() {
        const currentUser = this._userManagerService.getCurrentUser();
        const currentUserIsValid = currentUser && currentUser.userID;
        if (!currentUserIsValid) {
            this._userManagerService.setCurrentUser(createDefaultUser(UnitRole.Owner));
        }
    }
 
    private _getRole(type: UnitRole) {
        const user = this._userManagerService.getCurrentUser();
        if (!user) {
            return false;
        }
        return isDevRole(user.userID, type);
    }
 
    private _initSnapshot() {
        this._resourceManagerService.registerPluginResource({
            toJson: (_unitId: string) => {
                const obj = [...this._permissionMap.keys()].reduce((r, k) => {
                    const v = this._permissionMap.get(k);
                    r[k] = v!;
                    return r;
                }, {} as Record<string, ICreateRequest_SelectRangeObject & { objectType: UnitObject }>);
                return JSON.stringify(obj);
            },
            parseJson: (json: string) => {
                return JSON.parse(json);
            },
            pluginName: 'SHEET_AuthzIoMockService_PLUGIN',
            businesses: [UniverType.UNIVER_SHEET, UniverType.UNIVER_DOC, UniverType.UNIVER_SLIDE],
            onLoad: (_unitId, resource) => {
                for (const key in resource) {
                    this._permissionMap.set(key, resource[key]);
                }
            },
            onUnLoad: () => {
                this._permissionMap.clear();
            },
        });
    }
 
    async create(config: ICreateRequest): Promise<string> {
        const permissionId = generateRandomId(8);
        if (config.objectType === UnitObject.SelectRange && config.selectRangeObject) {
            this._permissionMap.set(permissionId, { ...config.selectRangeObject, objectType: config.objectType });
        }
        return permissionId;
    }
 
    async batchAllowed(config: IAllowedRequest[]): Promise<IBatchAllowedResponse['objectActions']> {
        const selectionRangeConfig = config.filter((c) => c.objectType === UnitObject.SelectRange);
        if (selectionRangeConfig.length) {
            const currentUser = this._userManagerService.getCurrentUser();
            const res = [] as IBatchAllowedResponse['objectActions'];
            selectionRangeConfig.forEach((c) => {
                res.push({
                    unitID: c.unitID,
                    objectID: c.objectID,
                    actions: c.actions.map((action) => {
                        if (isDevRole(currentUser.userID, UnitRole.Owner)) {
                            return { action, allowed: true };
                        }
                        return { action, allowed: false };
                    }),
                });
            });
            return res;
        }
        return Promise.resolve([]);
    }
 
    async list(config: IListPermPointRequest): Promise<IPermissionPoint[]> {
        const result: IPermissionPoint[] = [];
        config.objectIDs.forEach((objectID) => {
            const rule = this._permissionMap.get(objectID);
            if (rule) {
                const item = {
                    objectID,
                    unitID: config.unitID,
                    objectType: rule!.objectType,
                    name: rule!.name,
                    shareOn: false,
                    shareRole: UnitRole.Owner,
                    shareScope: -1,
                    scope: {
                        read: ObjectScope.AllCollaborator,
                        edit: ObjectScope.AllCollaborator,
                    },
                    creator: createDefaultUser(UnitRole.Owner),
                    strategies: [
                        {
                            action: UnitAction.View,
                            role: UnitRole.Owner,
                        },
                        {
                            action: UnitAction.Edit,
                            role: UnitRole.Owner,
                        },
                    ],
                    actions: config.actions.map((a) => {
                        return { action: a, allowed: this._getRole(UnitRole.Owner) };
                    }),
                };
                result.push(item);
            }
        });
        return result;
    }
 
    async listCollaborators(): Promise<ICollaborator[]> {
        // List the existing collaborators
        return [];
    }
 
    async allowed(_config: IAllowedRequest): Promise<IActionInfo[]> {
        // Because this is a mockService for handling permissions, we will not write real logic in it. We will only return an empty array to ensure that the permissions originally set by the user are not modified.
        // If you want to achieve persistence of permissions, you can modify the logic here.
        return Promise.resolve([]);
    }
 
    async listRoles(): Promise<{ roles: IUnitRoleKV[]; actions: UnitAction[] }> {
        return {
            roles: [],
            actions: [],
        };
    }
 
    async update(config: IUpdatePermPointRequest): Promise<void> {
        // Update bit information
    }
 
    async updateCollaborator(): Promise<void> {
        // Update collaborator information
        return undefined;
    }
 
    async createCollaborator(): Promise<void> {
        // Create new collaborator information
        return undefined;
    }
 
    async deleteCollaborator(): Promise<void> {
        return undefined;
    }
 
    async putCollaborators(config: IPutCollaboratorsRequest): Promise<void> {
        return undefined;
    }
}
 
export class YourPlugin extends Plugin {
  override onStarting(injector: Injector): void {
    injector.add([IAuthzIoService, { useClass: YourAuthzService }]);
  }
}
 
// By setting the override option to [[IAuthzIoService, null]], you can instruct Univer not to register the built-in IAuthzIoService.
// This way, Univer will use the service provided by YourAuthzService as the implementation of the authorization service.
const univer = new Univer({
  override: [[IAuthzIoService, null]],
});
 
univer.registerPlugin(YourPlugin);

List of permission point bits

To access the specific code related to permission point at the given URL, you can refer to the code (opens in a new tab).

In the case where the permission control of the workbook intersects with the worksheet/range, all permissions must be set to true in order to use them.

Workbook Permissions

PermissionDescription
WorkbookEditablePermissionCan edit
WorkbookPrintPermissionCan print
WorkbookCommentPermissionCan comment
WorkbookViewPermissionCan view
WorkbookCopyPermissionCan copy
WorkbookExportPermissionCan export
WorkbookManageCollaboratorPermissionCan manage collaborators
WorkbookCreateSheetPermissionCan create worksheets
WorkbookDeleteSheetPermissionCan delete worksheets
WorkbookRenameSheetPermissionCan rename worksheets
WorkbookHideSheetPermissionCan hide worksheets
WorkbookDuplicateSheetPermissionCan duplicate worksheets
WorkbookSharePermissionCan share
WorkbookMoveSheetPermissionCan move worksheets
WorksheetViewHistoryPermissionCan view history
WorksheetRecoverHistoryPermissionCan recover history

Worksheet Permissions

PermissionDescription
WorksheetCopyPermissionCan copy
WorksheetDeleteColumnPermissionCan delete columns
WorksheetDeleteRowPermissionCan delete rows
WorksheetFilterPermissionCan filter
WorksheetInsertColumnPermissionCan insert columns
WorksheetInsertHyperlinkPermissionCan use hyperlinks
WorksheetInsertRowPermissionCan insert rows
WorksheetPivotTablePermissionCan use pivot tables
WorksheetSetCellStylePermissionCan edit cell styles
WorksheetSetCellValuePermissionCan edit cell values
WorksheetSetColumnStylePermissionCan set column styles
WorksheetSetRowStylePermissionCan set row styles
WorksheetSortPermissionCan sort
WorksheetViewPermissionCan view
WorksheetEditPermissionCan edit

Range Protection

PermissionDescription
RangeProtectionPermissionViewPointCan view content of protected ranges
RangeProtectionPermissionEditPointCan edit protected ranges

Copyright © 2021-2024 DreamNum Co,Ltd. All Rights Reserved.