Skip to content

Commit 5c7d665

Browse files
authored
Reconstruct RBAC authentication logic (#264)
* Reconstruct RBAC authentication logic * fix typo * Migrate casbin sqla Adapter to redis * Delete casbin model conf file * Add permission dependencies * Add request permission depends on execution condition * Update openapi authorization method * Add request permission identity * Add request permission dependency description * Migrate casbin redis adapter to sqla * Update menu model and add function * Fix menu permission identification * Update user partial interface permissions * Update menu table SQL * Add role menu permission description to README * fix README typo * Simplify permission dependency injection * Fix menu authorization store * Fix interface permission dependency order * Update role menu permission flag * Update the background permission logic of the interface
1 parent f39d11e commit 5c7d665

40 files changed

+654
-220
lines changed

README.md

+5-9
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ See a preview of some of the screenshots
5353
- [x] Global SQLAlchemy 2.0 syntax
5454
- [x] Pydantic v1 and v2 (different branches)
5555
- [x] Casbin RBAC access control model
56+
- [x] Role menu RBAC access control model
5657
- [x] Celery asynchronous tasks
5758
- [x] JWT middleware whitelist authentication
5859
- [x] Global customizable time zone time
@@ -218,18 +219,13 @@ Execute unittests via pytest
218219
- [Ruff](https://beta.ruff.rs/docs/)
219220
- ...
220221

221-
## 互动
222+
## Interactivity
222223

223224
We only have one current channel.
224225

225-
<table>
226-
<tr>
227-
<td style="text-align: center;"><a href="https://t.me/+ZlPhIFkPp7E4NGI1"> Jump </a></td>
228-
</tr>
229-
<tr>
230-
<td> Telegram </td>
231-
</tr>
232-
</table>
226+
| [Jump](https://t.me/+ZlPhIFkPp7E4NGI1) |
227+
|----------------------------------------|
228+
| Telegram |
233229

234230
## Sponsor us
235231

README.zh-CN.md

+4-8
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
4747
- [x] 全局 SQLAlchemy 2.0 语法
4848
- [x] Pydantic v1 和 v2 (不同分支)
4949
- [x] Casbin RBAC 访问控制模型
50+
- [x] 角色菜单 RBAC 访问控制模型
5051
- [x] Celery 异步任务
5152
- [x] JWT 中间件白名单认证
5253
- [x] 全局自定义时区时间
@@ -216,14 +217,9 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
216217

217218
有且仅有当前一个频道,请注意辨别真伪
218219

219-
<table>
220-
<tr>
221-
<td style="text-align: center;"><a href="https://t.me/+ZlPhIFkPp7E4NGI1">直链跳转</a></td>
222-
</tr>
223-
<tr>
224-
<td>Telegram(科学上网)</td>
225-
</tr>
226-
</table>
220+
| [直链跳转](https://t.me/+ZlPhIFkPp7E4NGI1) |
221+
|----------------------------------------|
222+
| Telegram(科学上网) |
227223

228224
## 赞助我们
229225

backend/app/api/v1/api.py

+35-6
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
# -*- coding: utf-8 -*-
33
from typing import Annotated
44

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

77
from backend.app.common.jwt import DependsJwtAuth
8-
from backend.app.common.pagination import PageDepends, paging_data
8+
from backend.app.common.pagination import DependsPagination, paging_data
9+
from backend.app.common.permission import RequestPermission
910
from backend.app.common.rbac import DependsRBAC
1011
from backend.app.common.response.response_schema import response_base
1112
from backend.app.database.db_mysql import CurrentSession
@@ -27,7 +28,14 @@ async def get_api(pk: int):
2728
return await response_base.success(data=api)
2829

2930

30-
@router.get('', summary='(模糊条件)分页获取所有接口', dependencies=[DependsJwtAuth, PageDepends])
31+
@router.get(
32+
'',
33+
summary='(模糊条件)分页获取所有接口',
34+
dependencies=[
35+
DependsJwtAuth,
36+
DependsPagination,
37+
],
38+
)
3139
async def get_api_list(
3240
db: CurrentSession,
3341
name: Annotated[str | None, Query()] = None,
@@ -39,21 +47,42 @@ async def get_api_list(
3947
return await response_base.success(data=page_data)
4048

4149

42-
@router.post('', summary='创建接口', dependencies=[DependsRBAC])
50+
@router.post(
51+
'',
52+
summary='创建接口',
53+
dependencies=[
54+
Depends(RequestPermission('sys:api:add')),
55+
DependsRBAC,
56+
],
57+
)
4358
async def create_api(obj: CreateApi):
4459
await ApiService.create(obj=obj)
4560
return await response_base.success()
4661

4762

48-
@router.put('/{pk}', summary='更新接口', dependencies=[DependsRBAC])
63+
@router.put(
64+
'/{pk}',
65+
summary='更新接口',
66+
dependencies=[
67+
Depends(RequestPermission('sys:api:edit')),
68+
DependsRBAC,
69+
],
70+
)
4971
async def update_api(pk: int, obj: UpdateApi):
5072
count = await ApiService.update(pk=pk, obj=obj)
5173
if count > 0:
5274
return await response_base.success()
5375
return await response_base.fail()
5476

5577

56-
@router.delete('', summary='(批量)删除接口', dependencies=[DependsRBAC])
78+
@router.delete(
79+
'',
80+
summary='(批量)删除接口',
81+
dependencies=[
82+
Depends(RequestPermission('sys:api:del')),
83+
DependsRBAC,
84+
],
85+
)
5786
async def delete_api(pk: Annotated[list[int], Query(...)]):
5887
count = await ApiService.delete(pk=pk)
5988
if count > 0:

backend/app/api/v1/auth/auth.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616
router = APIRouter()
1717

1818

19-
@router.post('/swagger_login', summary='swagger 表单登录', description='form 格式登录,仅用于 swagger 文档调试接口')
19+
@router.post(
20+
'/swagger_login',
21+
summary='swagger 表单登录',
22+
description='form 格式登录,用于 swagger 文档调试以及获取 JWT Auth',
23+
deprecated=True,
24+
)
2025
async def swagger_user_login(form_data: OAuth2PasswordRequestForm = Depends()) -> GetSwaggerToken:
2126
token, user = await AuthService().swagger_login(form_data=form_data)
2227
return GetSwaggerToken(access_token=token, user=user) # type: ignore
@@ -25,7 +30,7 @@ async def swagger_user_login(form_data: OAuth2PasswordRequestForm = Depends()) -
2530
@router.post(
2631
'/login',
2732
summary='用户登录',
28-
description='json 格式登录, 仅支持在第三方api工具调试接口, 例如: postman',
33+
description='json 格式登录, 仅支持在第三方api工具调试, 例如: postman',
2934
dependencies=[Depends(RateLimiter(times=5, minutes=1))],
3035
)
3136
async def user_login(request: Request, obj: AuthLogin, background_tasks: BackgroundTasks):

backend/app/api/v1/casbin.py

+110-19
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
# -*- coding: utf-8 -*-
33
from typing import Annotated
44

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

77
from backend.app.common.jwt import DependsJwtAuth
8-
from backend.app.common.pagination import PageDepends, paging_data
8+
from backend.app.common.pagination import DependsPagination, paging_data
9+
from backend.app.common.permission import RequestPermission
910
from backend.app.common.rbac import DependsRBAC
1011
from backend.app.common.response.response_schema import response_base
1112
from backend.app.database.db_mysql import CurrentSession
1213
from backend.app.schemas.casbin_rule import (
1314
CreatePolicy,
1415
CreateUserRole,
1516
DeleteAllPolicies,
16-
DeleteAllUserRoles,
1717
DeletePolicy,
1818
DeleteUserRole,
1919
GetAllPolicy,
@@ -24,11 +24,18 @@
2424
router = APIRouter()
2525

2626

27-
@router.get('', summary='(模糊条件)分页获取所有权限规则', dependencies=[DependsJwtAuth, PageDepends])
27+
@router.get(
28+
'',
29+
summary='(模糊条件)分页获取所有权限规则',
30+
dependencies=[
31+
DependsJwtAuth,
32+
DependsPagination,
33+
],
34+
)
2835
async def get_all_casbin(
2936
db: CurrentSession,
30-
ptype: Annotated[str | None, Query()] = None,
31-
sub: Annotated[str | None, Query()] = None,
37+
ptype: Annotated[str | None, Query(description='规则类型, p / g')] = None,
38+
sub: Annotated[str | None, Query(description='用户 uuid / 角色')] = None,
3239
):
3340
casbin_select = await CasbinService.get_casbin_list(ptype=ptype, sub=sub)
3441
page_data = await paging_data(db, casbin_select, GetAllPolicy)
@@ -47,7 +54,14 @@ async def get_role_policies(role: Annotated[str, Path(description='角色ID')]):
4754
return await response_base.success(data=policies)
4855

4956

50-
@router.post('/policy', summary='添加P权限规则', dependencies=[DependsRBAC])
57+
@router.post(
58+
'/policy',
59+
summary='添加P权限规则',
60+
dependencies=[
61+
Depends(RequestPermission('casbin:p:add')),
62+
DependsRBAC,
63+
],
64+
)
5165
async def create_policy(p: CreatePolicy):
5266
"""
5367
p 规则:
@@ -62,37 +76,79 @@ async def create_policy(p: CreatePolicy):
6276
return await response_base.success(data=data)
6377

6478

65-
@router.post('/policies', summary='添加多组P权限规则', dependencies=[DependsRBAC])
79+
@router.post(
80+
'/policies',
81+
summary='添加多组P权限规则',
82+
dependencies=[
83+
Depends(RequestPermission('casbin:p:group:add')),
84+
DependsRBAC,
85+
],
86+
)
6687
async def create_policies(ps: list[CreatePolicy]):
6788
data = await CasbinService.create_policies(ps=ps)
6889
return await response_base.success(data=data)
6990

7091

71-
@router.put('/policy', summary='更新P权限规则', dependencies=[DependsRBAC])
92+
@router.put(
93+
'/policy',
94+
summary='更新P权限规则',
95+
dependencies=[
96+
Depends(RequestPermission('casbin:p:edit')),
97+
DependsRBAC,
98+
],
99+
)
72100
async def update_policy(old: UpdatePolicy, new: UpdatePolicy):
73101
data = await CasbinService.update_policy(old=old, new=new)
74102
return await response_base.success(data=data)
75103

76104

77-
@router.put('/policies', summary='更新多组P权限规则', dependencies=[DependsRBAC])
105+
@router.put(
106+
'/policies',
107+
summary='更新多组P权限规则',
108+
dependencies=[
109+
Depends(RequestPermission('casbin:p:group:edit')),
110+
DependsRBAC,
111+
],
112+
)
78113
async def update_policies(old: list[UpdatePolicy], new: list[UpdatePolicy]):
79114
data = await CasbinService.update_policies(old=old, new=new)
80115
return await response_base.success(data=data)
81116

82117

83-
@router.delete('/policy', summary='删除P权限规则', dependencies=[DependsRBAC])
118+
@router.delete(
119+
'/policy',
120+
summary='删除P权限规则',
121+
dependencies=[
122+
Depends(RequestPermission('casbin:p:del')),
123+
DependsRBAC,
124+
],
125+
)
84126
async def delete_policy(p: DeletePolicy):
85127
data = await CasbinService.delete_policy(p=p)
86128
return await response_base.success(data=data)
87129

88130

89-
@router.delete('/policies', summary='删除多组P权限规则', dependencies=[DependsRBAC])
131+
@router.delete(
132+
'/policies',
133+
summary='删除多组P权限规则',
134+
dependencies=[
135+
Depends(RequestPermission('casbin:p:group:del')),
136+
DependsRBAC,
137+
],
138+
)
90139
async def delete_policies(ps: list[DeletePolicy]):
91140
data = await CasbinService.delete_policies(ps=ps)
92141
return await response_base.success(data=data)
93142

94143

95-
@router.delete('/policies/all', summary='删除所有P权限规则', dependencies=[DependsRBAC])
144+
@router.delete(
145+
'/policies/all',
146+
summary='删除所有P权限规则',
147+
dependencies=[
148+
Depends(RequestPermission('casbin:p:empty')),
149+
DependsRBAC,
150+
],
151+
)
96152
async def delete_all_policies(sub: DeleteAllPolicies):
97153
count = await CasbinService.delete_all_policies(sub=sub)
98154
if count > 0:
@@ -106,7 +162,14 @@ async def get_all_groups():
106162
return await response_base.success(data=data)
107163

108164

109-
@router.post('/group', summary='添加G权限规则', dependencies=[DependsRBAC])
165+
@router.post(
166+
'/group',
167+
summary='添加G权限规则',
168+
dependencies=[
169+
Depends(RequestPermission('casbin:g:add')),
170+
DependsRBAC,
171+
],
172+
)
110173
async def create_group(g: CreateUserRole):
111174
"""
112175
g 规则 (**依赖 p 规则**):
@@ -121,26 +184,54 @@ async def create_group(g: CreateUserRole):
121184
return await response_base.success(data=data)
122185

123186

124-
@router.post('/groups', summary='添加多组G权限规则', dependencies=[DependsRBAC])
187+
@router.post(
188+
'/groups',
189+
summary='添加多组G权限规则',
190+
dependencies=[
191+
Depends(RequestPermission('casbin:g:group:add')),
192+
DependsRBAC,
193+
],
194+
)
125195
async def create_groups(gs: list[CreateUserRole]):
126196
data = await CasbinService.create_groups(gs=gs)
127197
return await response_base.success(data=data)
128198

129199

130-
@router.delete('/group', summary='删除G权限规则', dependencies=[DependsRBAC])
200+
@router.delete(
201+
'/group',
202+
summary='删除G权限规则',
203+
dependencies=[
204+
Depends(RequestPermission('casbin:g:del')),
205+
DependsRBAC,
206+
],
207+
)
131208
async def delete_group(g: DeleteUserRole):
132209
data = await CasbinService.delete_group(g=g)
133210
return await response_base.success(data=data)
134211

135212

136-
@router.delete('/groups', summary='删除多组G权限规则', dependencies=[DependsRBAC])
213+
@router.delete(
214+
'/groups',
215+
summary='删除多组G权限规则',
216+
dependencies=[
217+
Depends(RequestPermission('casbin:g:group:del')),
218+
DependsRBAC,
219+
],
220+
)
137221
async def delete_groups(gs: list[DeleteUserRole]):
138222
data = await CasbinService.delete_groups(gs=gs)
139223
return await response_base.success(data=data)
140224

141225

142-
@router.delete('/groups/all', summary='删除所有G权限规则', dependencies=[DependsRBAC])
143-
async def delete_all_groups(uuid: DeleteAllUserRoles):
226+
@router.delete(
227+
'/groups/all',
228+
summary='删除所有G权限规则',
229+
dependencies=[
230+
Depends(RequestPermission('casbin:g:empty')),
231+
DependsRBAC,
232+
],
233+
)
234+
async def delete_all_groups(uuid: str):
144235
count = await CasbinService.delete_all_groups(uuid=uuid)
145236
if count > 0:
146237
return await response_base.success()

0 commit comments

Comments
 (0)