mirror of
https://github.com/strapi/strapi.git
synced 2025-09-25 16:29:34 +00:00
docs: add strapi/permissions documentation (#23091)
This commit is contained in:
parent
4d37f3e55d
commit
0affac04b6
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Introduction
|
||||
slug: /permissions
|
||||
slug: /permissions-rbac
|
||||
tags:
|
||||
- permissions
|
||||
- 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": {
|
||||
"@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",
|
||||
|
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