docs: add strapi/permissions documentation (#23091)

This commit is contained in:
Jean-Sébastien Herbaux 2025-03-12 15:28:24 +01:00 committed by GitHub
parent 4d37f3e55d
commit 0affac04b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 4157 additions and 789 deletions

View File

@ -1,6 +1,6 @@
---
title: Introduction
slug: /permissions
slug: /permissions-rbac
tags:
- permissions
- RBAC

View File

@ -0,0 +1,134 @@
---
title: Introduction
slug: /permissions
tags:
- engine
- permissions
- actions
---
# @strapi/permissions
## Introduction
The `@strapi/permissions` package is a sophisticated permission management system designed to provide flexible, granular
control over access rights in Strapi systems.
Built on top of CASL's ability system, it extends the basic permission model with advanced features like parametrized
actions, conditional evaluation, and a hook system for custom behaviors.
It serves as the backbone for building advanced implementations in Strapi, enabling developers to design customized
permission systems tailored to specific Strapi business objectives and application demands like RBAC, users and permissions, or API tokens.
## Core Architecture
### Engine
```mermaid
graph TB
B[/Action Provider/]:::provider --> A([Permission Engine]):::engine
C[/Condition Provider/]:::provider --> A
A --> D{{Hook System}}:::hookSystem
A --> E([Ability Generator]):::generator
D --> H([Lifecycle Hooks]):::lifecycle
E --> I([CASL Integration]):::integration
H --> N([Permission Validation]):::hook
H --> O([Permission Formatting]):::hook
H --> P([Permission Evaluation]):::hook
```
### Domain
```mermaid
graph TB
N([Domain]):::namespace --> A([Permission]):::permission
A --> B([Action]):::action
A --> C([Subject]):::subject
A --> D([Conditions]):::conditions
A --> E([Properties]):::properties
A --> F([ActionParameters]):::actionParameters
```
## Key Features
### Dynamic Evaluation
Runtime permission checking at the time of the request, ensuring that permissions align with the most current context
and data.
_Example Use Case_: API endpoint access control, where user roles and data states impact access decisions dynamically.
### Parametrized Actions
Actions that leverage context-specific parameters to enable fine-grained control, allowing flexibility in defining
permissions.
_Example Use Case_: `publish?postId=123`, restricting operation to a specific post identified by its ID.
### Conditional Logic
Implementation of complex permission rules that consider different conditions, enabling nuanced access control tailored
to resource state or user data.
_Example Use Case_: Validate resource ownership by checking if the requesting user is the owner of a specific resource.
### Hook System
A modular mechanism to inject custom behaviors during various stages of the permission validation process, offering
extensibility and adaptability.
_Example Use Case_: Implement audit logging for permission evaluations or perform additional data validation before
granting access.
## Integration Example
```typescript
import { engine, domain } from '@strapi/permissions';
// 1. Define Providers
const providers = {
action: providerFactory(),
condition: providerFactory(),
};
// 2. Register Custom Conditions
providers.condition.register({
name: 'isOwner',
handler: (ctx) => ctx.user.id === ctx.resource.ownerId,
});
// 3. Create Engine
const permissionEngine = engine.new({ providers });
// 4. Define Permissions
const permissions = [
domain.permission.create({
action: 'read',
subject: 'article',
conditions: ['isPublished'],
}),
domain.permission.create({
action: 'update',
subject: 'article',
conditions: ['isOwner'],
properties: {
fields: ['title', 'content'],
},
}),
];
// 5. Generate Ability
const ability = await permissionEngine.generateAbility(permissions);
// 6. Evaluate Permission
const canReadArticle = ability.can('read', 'article');
```
---
```mdx-code-block
import DocCardList from '@theme/DocCardList';
import { useCurrentSidebarCategory } from '@docusaurus/theme-common';
<DocCardList items={useCurrentSidebarCategory().items} />
```

View File

@ -0,0 +1,192 @@
---
title: Permission Engine
description: Conceptual guide to the permission engine
tags:
- permissions
- engine
- hooks
---
# Permission Engine
The permission engine is the entity responsible for generating new ability instances based on provided permissions,
actions, and conditions.
This is the abstraction used by other Strapi systems to create their custom permissions' engine:
- [RBAC](https://github.com/strapi/strapi/blob/develop/packages/core/admin/server/src/services/permission/engine.ts)
- [Transfer Tokens](https://github.com/strapi/strapi/blob/develop/packages/core/admin/server/src/services/transfer/permission.ts)
- [API Tokens](https://github.com/strapi/strapi/blob/develop/packages/core/core/src/services/content-api/permissions/engine.ts)
## Usage
```typescript
import * as permissions from '@strapi/permissions';
const engine = permissions.engine.new({
providers: {
action: myActionProvider,
condition: myConditionProvider,
},
});
const permission = {
action: 'read',
subject: 'article',
conditions: ['isPublished'],
};
const ability = engine.generate([permission]);
console.log('Can read article?:', ability.can('read', 'article'));
```
## Ability Generation Diagram
```mermaid
sequenceDiagram
participant User
participant Engine
participant Hooks
participant Providers
participant Ability Builder
User ->> Engine: Generate Ability
loop permissions
Engine ->> Hooks: before-format::validate.permission
Hooks -->> Engine: Validation Result
Engine ->> Hooks: format.permission
Hooks -->> Engine: Formatted Permission
Engine ->> Hooks: after-format::validate.permission
Hooks -->> Engine: Validation Result
Engine ->> Hooks: before-evaluate.permission
alt permission has conditions
Engine ->> Providers: Resolve conditions
Providers -->> Engine: Resolved conditions
loop conditions
alt forbidden
note over Engine: skip permission
end
end
end
Engine ->> Hooks: before-register.permission
Engine ->> Ability Builder: Register permission in Ability
end
Engine -->> User: Ability
```
## Hook System
### Available Hooks
| Hook Name | Phase | Purpose |
| ---------------------------------- | ---------------- | ------------------------------------------------------------ |
| before-format::validate.permission | Pre-formatting | Initial validation |
| format.permission | Formatting | Transform permission |
| after-format::validate.permission | Post-formatting | Validate formatted permission |
| before-evaluate.permission | Pre-evaluation | Transform the permission after formatting and validation |
| before-register.permission | Pre-registration | Final modifications after evaluation but before registration |
### Hook Implementation Example
```typescript
const engine = permissions.engine.new({ providers });
// Validation hook
engine.on('before-format::validate.permission', ({ permission }) => {
if (permission.action === 'delete' && !permission.conditions.includes('isAdmin')) {
return false; // Prevent non-admin delete
}
return true;
});
// Format hook
engine.on('format.permission', ({ permission }) => {
// Add timestamp to tracking actions
if (permission.action.startsWith('track')) {
return {
...permission,
timestamp: Date.now(),
};
}
return permission;
});
```
## Engine Configuration Options
```typescript
interface EngineConfiguration {
providers: {
action: ActionProvider;
condition: ConditionProvider;
};
abilityBuilderFactory?: () => CustomAbilityBuilder;
}
```
### Action Provider
A provider instance that contains all available actions that can be used in permissions when generating abilities.
### Condition Provider
A provider instance mapping the conditions' name to their definitions.
Used to resolve conditions by name when evaluating and registering a permission during the ability creation process.
### Ability Builder Factory
A factory that is used to create an ability instance out of multiple permissions.
It must implement the `CustomAbilityBuilder` interface.
## Advanced Usage Examples
### Custom Ability Builder
```typescript
const customAbilityBuilder = () => {
const { can, build } = new AbilityBuilder(Ability);
return {
can(permission) {
// Custom permission logic
const { action, subject, properties = {}, condition } = permission;
return can(action, subject, properties.fields, condition);
},
build() {
return build({
// Custom matcher implementation
conditionsMatcher: (rule, context) => {
// Custom condition matching logic
return true / false;
},
});
},
};
};
const engine = permissions.engine.new({
providers,
abilityBuilderFactory: customAbilityBuilder,
});
```
### Conditional Permission Chain
```typescript
const permission = domain.permission.create({
action: 'update',
subject: 'article',
conditions: ['isOwner'],
});
engine.on('before-evaluate.permission', ({ permission }) => {
// Add dynamic conditions based on context
if (permission.action === 'update') {
permission.conditions.push('isNotLocked');
permission.conditions.push('isWithinBusinessHours');
}
});
```

View File

@ -0,0 +1,5 @@
{
"label": "Permissions",
"collapsible": true,
"collapsed": true
}

View File

@ -27,9 +27,9 @@
},
"dependencies": {
"@cmfcmf/docusaurus-search-local": "1.1.0",
"@docusaurus/core": "3.5.2",
"@docusaurus/preset-classic": "3.5.2",
"@docusaurus/theme-mermaid": "3.5.2",
"@docusaurus/core": "3.7.0",
"@docusaurus/preset-classic": "3.7.0",
"@docusaurus/theme-mermaid": "3.7.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^1.1.1",
"prism-react-renderer": "^2.1.0",
@ -37,7 +37,7 @@
"react-dom": "18.3.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.5.2",
"@docusaurus/module-type-aliases": "3.7.0",
"docusaurus-plugin-typedoc": "0.22.0",
"typedoc": "0.25.9",
"typedoc-plugin-markdown": "3.17.1",

File diff suppressed because it is too large Load Diff