Skip to content

Commit bd45022

Browse files
authored
Update role-based data permissions (#465)
* Add department data operation permissions * Update sys models * Add role department many-to-many relationship * Format RBAC code * update codes * add update role depts api * add comments * Update the implementation * update dept arg * debug front * Update the model file naming * Refactor the data perms * Add data permission rule * Optimize the details * Implement data filtering * finish
1 parent b29a3cb commit bd45022

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1529
-570
lines changed

backend/app/admin/api/v1/sys/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from backend.app.admin.api.v1.sys.api import router as api_router
66
from backend.app.admin.api.v1.sys.casbin import router as casbin_router
77
from backend.app.admin.api.v1.sys.config import router as config_router
8+
from backend.app.admin.api.v1.sys.data_rule import router as data_rule_router
9+
from backend.app.admin.api.v1.sys.data_rule_type import router as data_rule_type_router
810
from backend.app.admin.api.v1.sys.dept import router as dept_router
911
from backend.app.admin.api.v1.sys.dict_data import router as dict_data_router
1012
from backend.app.admin.api.v1.sys.dict_type import router as dict_type_router
@@ -23,3 +25,5 @@
2325
router.include_router(menu_router, prefix='/menus', tags=['系统目录'])
2426
router.include_router(role_router, prefix='/roles', tags=['系统角色'])
2527
router.include_router(user_router, prefix='/users', tags=['系统用户'])
28+
router.include_router(data_rule_router, prefix='/data-rules', tags=['系统数据权限规则'])
29+
router.include_router(data_rule_type_router, prefix='/data-rule-types', tags=['系统数据权限类型'])

backend/app/admin/api/v1/sys/api.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# -*- coding: utf-8 -*-
33
from typing import Annotated
44

5-
from fastapi import APIRouter, Depends, Path, Query
5+
from fastapi import APIRouter, Depends, Path, Query, Request
66

77
from backend.app.admin.schema.api import CreateApiParam, GetApiListDetails, UpdateApiParam
88
from backend.app.admin.service.api_service import api_service
@@ -37,12 +37,13 @@ async def get_api(pk: Annotated[int, Path(...)]) -> ResponseModel:
3737
],
3838
)
3939
async def get_pagination_apis(
40+
request: Request,
4041
db: CurrentSession,
4142
name: Annotated[str | None, Query()] = None,
4243
method: Annotated[str | None, Query()] = None,
4344
path: Annotated[str | None, Query()] = None,
4445
) -> ResponseModel:
45-
api_select = await api_service.get_select(name=name, method=method, path=path)
46+
api_select = await api_service.get_select(request=request, name=name, method=method, path=path)
4647
page_data = await paging_data(db, api_select, GetApiListDetails)
4748
return response_base.success(data=page_data)
4849

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
from typing import Annotated
4+
5+
from fastapi import APIRouter, Depends, Path, Query
6+
7+
from backend.app.admin.schema.data_rule import CreateDataRuleParam, GetDataRuleListDetails, UpdateDataRuleParam
8+
from backend.app.admin.service.data_rule_service import data_rule_service
9+
from backend.common.pagination import DependsPagination, paging_data
10+
from backend.common.response.response_schema import ResponseModel, response_base
11+
from backend.common.security.jwt import DependsJwtAuth
12+
from backend.common.security.permission import RequestPermission
13+
from backend.common.security.rbac import DependsRBAC
14+
from backend.database.db_mysql import CurrentSession
15+
from backend.utils.serializers import select_as_dict
16+
17+
router = APIRouter()
18+
19+
20+
@router.get('/models', summary='获取支持过滤的数据库模型', dependencies=[DependsJwtAuth])
21+
async def get_data_rule_models() -> ResponseModel:
22+
models = await data_rule_service.get_models()
23+
return response_base.success(data=models)
24+
25+
26+
@router.get('/model/{model}/columns', summary='获取支持过滤的数据库模型列', dependencies=[DependsJwtAuth])
27+
async def get_data_rule_model_columns(model: Annotated[str, Path()]) -> ResponseModel:
28+
models = await data_rule_service.get_columns(model=model)
29+
return response_base.success(data=models)
30+
31+
32+
@router.get('/{pk}', summary='获取数据权限规则详情', dependencies=[DependsJwtAuth])
33+
async def get_data_rule(pk: Annotated[int, Path(...)]) -> ResponseModel:
34+
data_rule = await data_rule_service.get(pk=pk)
35+
data = GetDataRuleListDetails(**select_as_dict(data_rule))
36+
return response_base.success(data=data)
37+
38+
39+
@router.get(
40+
'',
41+
summary='(模糊条件)分页获取所有数据权限规则',
42+
dependencies=[
43+
DependsJwtAuth,
44+
DependsPagination,
45+
],
46+
)
47+
async def get_pagination_data_rule(db: CurrentSession, name: Annotated[str | None, Query()] = None) -> ResponseModel:
48+
data_rule_select = await data_rule_service.get_select(name=name)
49+
page_data = await paging_data(db, data_rule_select, GetDataRuleListDetails)
50+
return response_base.success(data=page_data)
51+
52+
53+
@router.post(
54+
'',
55+
summary='创建数据权限规则',
56+
dependencies=[
57+
Depends(RequestPermission('data:rule:add')),
58+
DependsRBAC,
59+
],
60+
)
61+
async def create_data_rule(obj: CreateDataRuleParam) -> ResponseModel:
62+
await data_rule_service.create(obj=obj)
63+
return response_base.success()
64+
65+
66+
@router.put(
67+
'/{pk}',
68+
summary='更新数据权限规则',
69+
dependencies=[
70+
Depends(RequestPermission('data:rule:edit')),
71+
DependsRBAC,
72+
],
73+
)
74+
async def update_data_rule(pk: Annotated[int, Path(...)], obj: UpdateDataRuleParam) -> ResponseModel:
75+
count = await data_rule_service.update(pk=pk, obj=obj)
76+
if count > 0:
77+
return response_base.success()
78+
return response_base.fail()
79+
80+
81+
@router.delete(
82+
'',
83+
summary='(批量)删除数据权限规则',
84+
dependencies=[
85+
Depends(RequestPermission('data:rule:del')),
86+
DependsRBAC,
87+
],
88+
)
89+
async def delete_data_rule(pk: Annotated[list[int], Query(...)]) -> ResponseModel:
90+
count = await data_rule_service.delete(pk=pk)
91+
if count > 0:
92+
return response_base.success()
93+
return response_base.fail()
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
from typing import Annotated
4+
5+
from fastapi import APIRouter, Depends, Path, Query
6+
7+
from backend.app.admin.schema.data_rule_type import (
8+
CreateDataRuleTypeParam,
9+
GetDataRuleTypeListDetails,
10+
UpdateDataRuleTypeParam,
11+
)
12+
from backend.app.admin.service.data_rule_type_service import data_rule_type_service
13+
from backend.common.pagination import DependsPagination, paging_data
14+
from backend.common.response.response_schema import ResponseModel, response_base
15+
from backend.common.security.jwt import DependsJwtAuth
16+
from backend.common.security.permission import RequestPermission
17+
from backend.common.security.rbac import DependsRBAC
18+
from backend.database.db_mysql import CurrentSession
19+
from backend.utils.serializers import select_as_dict
20+
21+
router = APIRouter()
22+
23+
24+
@router.get('/{pk}', summary='获取数据权限规则类型详情', dependencies=[DependsJwtAuth])
25+
async def get_data_rule_type(pk: Annotated[int, Path(...)]) -> ResponseModel:
26+
data_rule_type = await data_rule_type_service.get(pk=pk)
27+
data = GetDataRuleTypeListDetails(**select_as_dict(data_rule_type))
28+
return response_base.success(data=data)
29+
30+
31+
@router.get(
32+
'',
33+
summary='(模糊条件)分页获取所有数据权限规则类型',
34+
dependencies=[
35+
DependsJwtAuth,
36+
DependsPagination,
37+
],
38+
)
39+
async def get_pagination_data_rule_type(db: CurrentSession) -> ResponseModel:
40+
data_rule_type_select = await data_rule_type_service.get_select()
41+
page_data = await paging_data(db, data_rule_type_select, GetDataRuleTypeListDetails)
42+
return response_base.success(data=page_data)
43+
44+
45+
@router.post(
46+
'',
47+
summary='创建数据权限规则类型',
48+
dependencies=[
49+
Depends(RequestPermission('data:rule:type:add')),
50+
DependsRBAC,
51+
],
52+
)
53+
async def create_data_rule_type(obj: CreateDataRuleTypeParam) -> ResponseModel:
54+
await data_rule_type_service.create(obj=obj)
55+
return response_base.success()
56+
57+
58+
@router.put(
59+
'/{pk}',
60+
summary='更新数据权限规则类型',
61+
dependencies=[
62+
Depends(RequestPermission('data:rule:type:edit')),
63+
DependsRBAC,
64+
],
65+
)
66+
async def update_data_rule_type(pk: Annotated[int, Path(...)], obj: UpdateDataRuleTypeParam) -> ResponseModel:
67+
count = await data_rule_type_service.update(pk=pk, obj=obj)
68+
if count > 0:
69+
return response_base.success()
70+
return response_base.fail()
71+
72+
73+
@router.delete(
74+
'',
75+
summary='(批量)删除数据权限规则类型',
76+
dependencies=[
77+
Depends(RequestPermission('data:rule:type:del')),
78+
DependsRBAC,
79+
],
80+
)
81+
async def delete_data_rule_type(pk: Annotated[list[int], Query(...)]) -> ResponseModel:
82+
count = await data_rule_type_service.delete(pk=pk)
83+
if count > 0:
84+
return response_base.success()
85+
return response_base.fail()

backend/app/admin/api/v1/sys/role.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44

55
from fastapi import APIRouter, Depends, Path, Query, Request
66

7-
from backend.app.admin.schema.role import CreateRoleParam, GetRoleListDetails, UpdateRoleMenuParam, UpdateRoleParam
7+
from backend.app.admin.schema.role import (
8+
CreateRoleParam,
9+
GetRoleListDetails,
10+
UpdateRoleMenuParam,
11+
UpdateRoleParam,
12+
UpdateRoleRuleParam,
13+
)
814
from backend.app.admin.service.menu_service import menu_service
915
from backend.app.admin.service.role_service import role_service
1016
from backend.common.pagination import DependsPagination, paging_data
@@ -27,7 +33,7 @@ async def get_all_roles() -> ResponseModel:
2733

2834
@router.get('/{pk}/all', summary='获取用户所有角色', dependencies=[DependsJwtAuth])
2935
async def get_user_all_roles(pk: Annotated[int, Path(...)]) -> ResponseModel:
30-
roles = await role_service.get_user_roles(pk=pk)
36+
roles = await role_service.get_by_user(pk=pk)
3137
data = select_list_serialize(roles)
3238
return response_base.success(data=data)
3339

@@ -109,6 +115,23 @@ async def update_role_menus(
109115
return response_base.fail()
110116

111117

118+
@router.put(
119+
'/{pk}/rule',
120+
summary='更新角色数据权限规则',
121+
dependencies=[
122+
Depends(RequestPermission('sys:role:rule:edit')),
123+
DependsRBAC,
124+
],
125+
)
126+
async def update_role_rules(
127+
request: Request, pk: Annotated[int, Path(...)], rule_ids: UpdateRoleRuleParam
128+
) -> ResponseModel:
129+
count = await role_service.update_role_rule(request=request, pk=pk, rule_ids=rule_ids)
130+
if count > 0:
131+
return response_base.success()
132+
return response_base.fail()
133+
134+
112135
@router.delete(
113136
'',
114137
summary='(批量)删除角色',

backend/app/admin/crud/crud_api.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
# -*- coding: utf-8 -*-
33
from typing import Sequence
44

5+
from fastapi import Request
56
from sqlalchemy import Select
67
from sqlalchemy.ext.asyncio import AsyncSession
78
from sqlalchemy_crud_plus import CRUDPlus
89

910
from backend.app.admin.model import Api
1011
from backend.app.admin.schema.api import CreateApiParam, UpdateApiParam
12+
from backend.common.security.permission import filter_data_permission
1113

1214

1315
class CRUDApi(CRUDPlus[Api]):
@@ -21,10 +23,11 @@ async def get(self, db: AsyncSession, pk: int) -> Api | None:
2123
"""
2224
return await self.select_model(db, pk)
2325

24-
async def get_list(self, name: str = None, method: str = None, path: str = None) -> Select:
26+
async def get_list(self, request: Request, name: str = None, method: str = None, path: str = None) -> Select:
2527
"""
2628
获取 API 列表
2729
30+
:param request:
2831
:param name:
2932
:param method:
3033
:param path:
@@ -37,7 +40,8 @@ async def get_list(self, name: str = None, method: str = None, path: str = None)
3740
filters.update(method=method)
3841
if path is not None:
3942
filters.update(path__like=f'%{path}%')
40-
return await self.select_order('created_time', 'desc', **filters)
43+
stmt = await self.select_order('created_time', 'desc', **filters)
44+
return stmt.where(filter_data_permission(request))
4145

4246
async def get_all(self, db: AsyncSession) -> Sequence[Api]:
4347
"""
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
from typing import Sequence
4+
5+
from sqlalchemy import Select, desc, select
6+
from sqlalchemy.ext.asyncio import AsyncSession
7+
from sqlalchemy_crud_plus import CRUDPlus
8+
9+
from backend.app.admin.model import DataRule
10+
from backend.app.admin.schema.data_rule import CreateDataRuleParam, UpdateDataRuleParam
11+
12+
13+
class CRUDDataRule(CRUDPlus[DataRule]):
14+
async def get(self, db: AsyncSession, pk: int) -> DataRule | None:
15+
"""
16+
获取数据权限规则
17+
18+
:param db:
19+
:param pk:
20+
:return:
21+
"""
22+
return await self.select_model(db, pk)
23+
24+
async def get_list(self, name: str = None) -> Select:
25+
"""
26+
获取数据权限规则列表
27+
28+
:return:
29+
"""
30+
stmt = select(self.model).order_by(desc(self.model.created_time))
31+
where_list = []
32+
if name is not None:
33+
where_list.append(self.model.name.like(f'%{name}%'))
34+
if where_list:
35+
stmt = stmt.where(*where_list)
36+
return stmt
37+
38+
async def get_by_name(self, db: AsyncSession, name: str):
39+
"""
40+
通过 name 获取数据权限规则
41+
42+
:param db:
43+
:param name:
44+
:return:
45+
"""
46+
return await self.select_model_by_column(db, name=name)
47+
48+
async def get_all(self, db: AsyncSession) -> Sequence[DataRule]:
49+
"""
50+
获取所有数据权限规则
51+
52+
:param db:
53+
:return:
54+
"""
55+
return await self.select_models(db)
56+
57+
async def create(self, db: AsyncSession, obj_in: CreateDataRuleParam) -> None:
58+
"""
59+
创建数据权限规则
60+
61+
:param db:
62+
:param obj_in:
63+
:return:
64+
"""
65+
await self.create_model(db, obj_in)
66+
67+
async def update(self, db: AsyncSession, pk: int, obj_in: UpdateDataRuleParam) -> int:
68+
"""
69+
更新数据权限规则
70+
71+
:param db:
72+
:param pk:
73+
:param obj_in:
74+
:return:
75+
"""
76+
return await self.update_model(db, pk, obj_in)
77+
78+
async def delete(self, db: AsyncSession, pk: list[int]) -> int:
79+
"""
80+
删除数据权限规则
81+
82+
:param db:
83+
:param pk:
84+
:return:
85+
"""
86+
return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk)
87+
88+
89+
data_rule_dao: CRUDDataRule = CRUDDataRule(DataRule)

0 commit comments

Comments
 (0)