mirror of
https://github.com/strapi/strapi.git
synced 2025-09-26 08:52:26 +00:00
docs: add strapi/permissions documentation (#23091)
This commit is contained in:
parent
4d37f3e55d
commit
0affac04b6
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Introduction
|
title: Introduction
|
||||||
slug: /permissions
|
slug: /permissions-rbac
|
||||||
tags:
|
tags:
|
||||||
- permissions
|
- permissions
|
||||||
- RBAC
|
- RBAC
|
||||||
|
134
docs/docs/docs/01-core/permissions/00-intro.md
Normal file
134
docs/docs/docs/01-core/permissions/00-intro.md
Normal 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} />
|
||||||
|
```
|
192
docs/docs/docs/01-core/permissions/01-engine.md
Normal file
192
docs/docs/docs/01-core/permissions/01-engine.md
Normal 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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
5
docs/docs/docs/01-core/permissions/_category_.json
Normal file
5
docs/docs/docs/01-core/permissions/_category_.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"label": "Permissions",
|
||||||
|
"collapsible": true,
|
||||||
|
"collapsed": true
|
||||||
|
}
|
@ -27,9 +27,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cmfcmf/docusaurus-search-local": "1.1.0",
|
"@cmfcmf/docusaurus-search-local": "1.1.0",
|
||||||
"@docusaurus/core": "3.5.2",
|
"@docusaurus/core": "3.7.0",
|
||||||
"@docusaurus/preset-classic": "3.5.2",
|
"@docusaurus/preset-classic": "3.7.0",
|
||||||
"@docusaurus/theme-mermaid": "3.5.2",
|
"@docusaurus/theme-mermaid": "3.7.0",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"prism-react-renderer": "^2.1.0",
|
"prism-react-renderer": "^2.1.0",
|
||||||
@ -37,7 +37,7 @@
|
|||||||
"react-dom": "18.3.0"
|
"react-dom": "18.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "3.5.2",
|
"@docusaurus/module-type-aliases": "3.7.0",
|
||||||
"docusaurus-plugin-typedoc": "0.22.0",
|
"docusaurus-plugin-typedoc": "0.22.0",
|
||||||
"typedoc": "0.25.9",
|
"typedoc": "0.25.9",
|
||||||
"typedoc-plugin-markdown": "3.17.1",
|
"typedoc-plugin-markdown": "3.17.1",
|
||||||
|
4605
docs/yarn.lock
4605
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user