Skip to content

Commit 5e7a4e1

Browse files
authored
Update casbin rbac verify to plugin (#513)
* Update casbin rbac verify to plugin * Update requirements * move sys api to casbin plugin * Update plugin toml and parse * Fix plugin router inject * Update api prefix to apis
1 parent 4d13424 commit 5e7a4e1

33 files changed

+277
-243
lines changed

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
# -*- coding: utf-8 -*-
33
from fastapi import APIRouter
44

5-
from backend.app.admin.api.v1.sys.api import router as api_router
6-
from backend.app.admin.api.v1.sys.casbin import router as casbin_router
75
from backend.app.admin.api.v1.sys.config import router as config_router
86
from backend.app.admin.api.v1.sys.data_rule import router as data_rule_router
97
from backend.app.admin.api.v1.sys.dept import router as dept_router
@@ -16,8 +14,6 @@
1614

1715
router = APIRouter(prefix='/sys')
1816

19-
router.include_router(api_router, prefix='/apis', tags=['系统API'])
20-
router.include_router(casbin_router, prefix='/casbin', tags=['系统Casbin权限'])
2117
router.include_router(config_router, prefix='/configs', tags=['系统配置'])
2218
router.include_router(dept_router, prefix='/depts', tags=['系统部门'])
2319
router.include_router(dict_data_router, prefix='/dict-datas', tags=['系统字典数据'])

backend/app/admin/model/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
from backend.app.admin.model.api import Api
4-
from backend.app.admin.model.casbin_rule import CasbinRule
53
from backend.app.admin.model.config import Config
64
from backend.app.admin.model.data_rule import DataRule
75
from backend.app.admin.model.dept import Dept

backend/common/security/permission.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def __init__(self, value: str):
2828
self.value = value
2929

3030
async def __call__(self, request: Request):
31-
if settings.PERMISSION_MODE == 'role-menu':
31+
if settings.RBAC_ROLE_MENU_MODE:
3232
if not isinstance(self.value, str):
3333
raise ServerError
3434
# 附加权限标识

backend/common/security/rbac.py

Lines changed: 61 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,74 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
import casbin
4-
import casbin_async_sqlalchemy_adapter
5-
63
from fastapi import Depends, Request
74

8-
from backend.app.admin.model import CasbinRule
95
from backend.common.enums import MethodType, StatusType
106
from backend.common.exception.errors import AuthorizationError, TokenError
117
from backend.common.security.jwt import DependsJwtAuth
128
from backend.core.conf import settings
13-
from backend.database.db import async_engine
14-
15-
16-
class RBAC:
17-
@staticmethod
18-
async def enforcer() -> casbin.AsyncEnforcer:
19-
"""
20-
获取 casbin 执行器
21-
22-
:return:
23-
"""
24-
# 模型定义:https://casbin.org/zh/docs/category/model
25-
_CASBIN_RBAC_MODEL_CONF_TEXT = """
26-
[request_definition]
27-
r = sub, obj, act
28-
29-
[policy_definition]
30-
p = sub, obj, act
31-
32-
[role_definition]
33-
g = _, _
34-
35-
[policy_effect]
36-
e = some(where (p.eft == allow))
37-
38-
[matchers]
39-
m = g(r.sub, p.sub) && (keyMatch(r.obj, p.obj) || keyMatch3(r.obj, p.obj)) && (r.act == p.act || p.act == "*")
40-
"""
41-
adapter = casbin_async_sqlalchemy_adapter.Adapter(async_engine, db_class=CasbinRule)
42-
model = casbin.AsyncEnforcer.new_model(text=_CASBIN_RBAC_MODEL_CONF_TEXT)
43-
enforcer = casbin.AsyncEnforcer(model, adapter)
44-
await enforcer.load_policy()
45-
return enforcer
46-
47-
async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> None:
48-
"""
49-
RBAC 权限校验(鉴权顺序很重要,谨慎修改)
50-
51-
:param request:
52-
:param _token:
53-
:return:
54-
"""
55-
path = request.url.path
56-
57-
# API 鉴权白名单
58-
if path in settings.TOKEN_REQUEST_PATH_EXCLUDE:
59-
return
9+
from backend.plugin.casbin.utils.rbac import casbin_verify
10+
11+
12+
async def rbac_verify(request: Request, _token: str = DependsJwtAuth) -> None:
13+
"""
14+
RBAC 权限校验(鉴权顺序很重要,谨慎修改)
15+
16+
:param request:
17+
:param _token:
18+
:return:
19+
"""
20+
path = request.url.path
21+
22+
# API 鉴权白名单
23+
if path in settings.TOKEN_REQUEST_PATH_EXCLUDE:
24+
return
25+
26+
# JWT 授权状态强制校验
27+
if not request.auth.scopes:
28+
raise TokenError
29+
30+
# 超级管理员免校验
31+
if request.user.is_superuser:
32+
return
6033

61-
# JWT 授权状态强制校验
62-
if not request.auth.scopes:
63-
raise TokenError
34+
# 检测用户角色
35+
user_roles = request.user.roles
36+
if not user_roles or all(status == 0 for status in user_roles):
37+
raise AuthorizationError(msg='用户未分配角色,请联系系统管理员')
6438

65-
# 超级管理员免校验
66-
if request.user.is_superuser:
39+
# 检测用户所属角色菜单
40+
if not any(len(role.menus) > 0 for role in user_roles):
41+
raise AuthorizationError(msg='用户未分配菜单,请联系系统管理员')
42+
43+
# 检测后台管理操作权限
44+
method = request.method
45+
if method != MethodType.GET or method != MethodType.OPTIONS:
46+
if not request.user.is_staff:
47+
raise AuthorizationError(msg='用户已被禁止后台管理操作,请联系系统管理员')
48+
49+
# RBAC 鉴权
50+
if settings.RBAC_ROLE_MENU_MODE:
51+
path_auth_perm = getattr(request.state, 'permission', None)
52+
53+
# 没有菜单操作权限标识不校验
54+
if not path_auth_perm:
6755
return
6856

69-
# 检测用户角色
70-
user_roles = request.user.roles
71-
if not user_roles or all(status == 0 for status in user_roles):
72-
raise AuthorizationError(msg='用户未分配角色,请联系系统管理员')
73-
74-
# 检测用户所属角色菜单
75-
if not any(len(role.menus) > 0 for role in user_roles):
76-
raise AuthorizationError(msg='用户未分配菜单,请联系系统管理员')
77-
78-
# 检测后台管理操作权限
79-
method = request.method
80-
if method != MethodType.GET or method != MethodType.OPTIONS:
81-
if not request.user.is_staff:
82-
raise AuthorizationError(msg='用户已被禁止后台管理操作,请联系系统管理员')
83-
84-
# RBAC 鉴权
85-
if settings.PERMISSION_MODE == 'role-menu':
86-
path_auth_perm = getattr(request.state, 'permission', None)
87-
88-
# 没有菜单操作权限标识不校验
89-
if not path_auth_perm:
90-
return
91-
92-
# 菜单鉴权白名单
93-
if path_auth_perm in settings.RBAC_ROLE_MENU_EXCLUDE:
94-
return
95-
96-
# 已分配菜单权限校验
97-
allow_perms = []
98-
for role in user_roles:
99-
for menu in role.menus:
100-
if menu.perms and menu.status == StatusType.enable:
101-
allow_perms.extend(menu.perms.split(','))
102-
if path_auth_perm not in allow_perms:
103-
raise AuthorizationError
104-
else:
105-
# casbin 鉴权白名单
106-
if (method, path) in settings.RBAC_CASBIN_EXCLUDE:
107-
return
108-
109-
# casbin 权限校验
110-
# 实现机制:backend/app/admin/api/v1/sys/casbin.py
111-
user_uuid = request.user.uuid
112-
enforcer = await self.enforcer()
113-
if not enforcer.enforce(user_uuid, path, method):
114-
raise AuthorizationError
115-
116-
117-
rbac: RBAC = RBAC()
57+
# 菜单鉴权白名单
58+
if path_auth_perm in settings.RBAC_ROLE_MENU_EXCLUDE:
59+
return
60+
61+
# 已分配菜单权限校验
62+
allow_perms = []
63+
for role in user_roles:
64+
for menu in role.menus:
65+
if menu.perms and menu.status == StatusType.enable:
66+
allow_perms.extend(menu.perms.split(','))
67+
if path_auth_perm not in allow_perms:
68+
raise AuthorizationError
69+
else:
70+
await casbin_verify(request)
71+
72+
11873
# RBAC 授权依赖注入
119-
DependsRBAC = Depends(rbac.rbac_verify)
74+
DependsRBAC = Depends(rbac_verify)

backend/core/conf.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,8 @@ class Settings(BaseSettings):
7575
JWT_USER_REDIS_PREFIX: str = 'fba:user'
7676
JWT_USER_REDIS_EXPIRE_SECONDS: int = 60 * 60 * 24 * 7
7777

78-
# Permission (RBAC)
79-
PERMISSION_MODE: Literal['casbin', 'role-menu'] = 'casbin'
80-
PERMISSION_REDIS_PREFIX: str = 'fba:permission'
81-
8278
# RBAC
83-
# Casbin
84-
RBAC_CASBIN_EXCLUDE: set[tuple[str, str]] = {
85-
('POST', f'{FASTAPI_API_V1_PATH}/auth/logout'),
86-
('POST', f'{FASTAPI_API_V1_PATH}/auth/token/new'),
87-
}
88-
89-
# Role-Menu
79+
RBAC_ROLE_MENU_MODE: bool = False
9080
RBAC_ROLE_MENU_EXCLUDE: list[str] = [
9181
'sys:monitor:redis',
9282
'sys:monitor:server',

backend/plugin/casbin/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-

backend/plugin/casbin/api/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-

backend/app/admin/api/v1/sys/api.py renamed to backend/plugin/casbin/api/v1/sys/api.py

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

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

7-
from backend.app.admin.schema.api import CreateApiParam, GetApiDetail, UpdateApiParam
8-
from backend.app.admin.service.api_service import api_service
97
from backend.common.pagination import DependsPagination, PageData, paging_data
108
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
119
from backend.common.security.jwt import DependsJwtAuth
1210
from backend.common.security.permission import RequestPermission
1311
from backend.common.security.rbac import DependsRBAC
1412
from backend.database.db import CurrentSession
13+
from backend.plugin.casbin.schema.api import CreateApiParam, GetApiDetail, UpdateApiParam
14+
from backend.plugin.casbin.service.api_service import api_service
1515

1616
router = APIRouter()
1717

0 commit comments

Comments
 (0)