Data Permission
About 466 wordsAbout 2 min
Data permission is designed to attach permissions to data. Common implementations include "own data only", "department data only", and so on. These are data permissions. You can give different roles different data permissions to isolate users from data.
Common Approaches
Caution
fba has removed this integration. The code is kept only as an example.
def filter_data_permission(request_user: GetUserInfoWithRelationDetail, model: Any) -> ColumnElement[bool]:
"""
过滤数据权限,控制用户可见数据范围
:param request_user: 请求用户
:param model: 需要进行数据过滤的 sqlalchemy 模型
:return:
"""
# 超级管理员可查看所有数据
if request_user.is_superuser:
return or_(1 == 1)
# 无角色只能查看自己数据
if not request_user.roles:
return or_(getattr(model, 'created_by') == request_user.id if hasattr(model, 'created_by') else 1 == 0)
data_scope = min(role.data_scope for role in request_user.roles if role.status == 1)
user_dept_id = user.dept_id
# 全部数据权限
if data_scope == 0:
return or_(1 == 1)
# 自定义数据权限
elif data_scope == 1:
dept_ids = select(role_dept.c.dept_id).where(
role_dept.c.role_id.in_(role.id for role in request_user.roles if role.status == 1)
)
return or_(getattr(model, 'dept_id').in_(dept_ids) if hasattr(model, 'dept_id') else 1 == 0)
# 部门及以下数据权限
elif data_scope == 2:
child_dept_ids = select(Dept.id).where(or_(Dept.id == user_dept_id, Dept.parent_id == user_dept_id))
return or_(getattr(model, 'dept_id').in_(child_dept_ids) if hasattr(model, 'dept_id') else 1 == 0)
# 本部门数据权限
elif data_scope == 3:
return or_(getattr(model, 'dept_id') == user_dept_id if hasattr(model, 'dept_id') else 1 == 0)
# 仅本人数据权限
elif data_scope == 4:
return or_(getattr(model, 'created_by') == request_user.id if hasattr(model, 'created_by') else 1 == 0)
# 默认
else:
return or_(1 == 0)Drawbacks
This common style of data permission can cover many everyday scenarios, but it has serious drawbacks. Data filtering is implemented by concatenating SQL statements, and these fixed permissions hard-code the requirements.
For example, business tables must include dept_id and created_by fields. Without those fields, you cannot control data permission via SQL.
Built-in Approach
fba includes a flexible data permission solution that combines DataScope and DataRule for dynamic, configurable data filtering — without requiring business tables to have specific fields.
Architecture
Data permission is implemented through this relationship chain:
User → Role → DataScope → DataRuleData Scope
A DataScope is a logical group of data rules. One data scope can contain multiple data rules.
Data Rule
A DataRule defines a concrete filter condition. Each rule consists of a model, column, operator, expression, and value.
Template Variables
To support dynamic scenarios, data rules support template variables that are resolved to actual values at runtime.
Model Template Variables
Used in the data rule model field:
| Variable | Description |
|---|---|
__ALL__ | Match all models |
Column Template Variables
Used in the data rule column field:
| Variable | Description |
|---|---|
__dept_id__ | Department ID (resolved to the model's dept_id field) |
__created_by__ | Creator (resolved to the model's created_by field) |
Value Template Variables
Used in the data rule value field:
| Variable | Description |
|---|---|
${user_id} | Current logged-in user ID |
${dept_id} | Current logged-in user's department ID |
${now} | Current time |
Usage
API Dependency Injection
Inject data permission filter conditions into APIs via the DataPermissionFilter class, passing the model class that needs filtering:
from backend.common.security.permission import DataPermissionFilter
@router.get('')
async def get_dept_tree(
db: CurrentSession,
data_filter: Annotated[ColumnElement[bool], Depends(DataPermissionFilter(Dept))],
) -> ResponseSchemaModel[list[GetDeptTree]]:
dept = await dept_service.get_tree(db=db, data_filter=data_filter, ...)
return response_base.success(data=dept)CRUD Data Filtering
In the CRUD layer, pass data_filter as a query filter condition:
async def get_all(
self,
db: AsyncSession,
data_filter: ColumnElement[bool],
...
) -> Sequence[Dept]:
return await self.select_models_order(db, 'sort', 'asc', data_filter, **filters)Filter Flow
User requests API
↓
FastAPI resolves Depends(DataPermissionFilter)
↓
filter_data_permission() runs
├── Super admin → no filter, view all data
├── Role is_filter_scopes=False → no filter
├── Data permission filtering enabled but no available rules → no visible data
└── Rules exist → build SQLAlchemy conditions
├── Resolve model template variables (__ALL__, etc.)
├── Resolve column template variables (__dept_id__, etc.)
├── Resolve value template variables (${user_id}, etc.)
├── Build conditions by expression (==, !=, >, in, etc.)
└── Combine conditions by operator (AND / OR)
↓
Return ColumnElement[bool] filter condition
↓
CRUD layer queries the database with the filter
