diff --git a/backend/app/api/v1/casbin.py b/backend/app/api/v1/casbin.py
index 09fe81c3..0894929f 100644
--- a/backend/app/api/v1/casbin.py
+++ b/backend/app/api/v1/casbin.py
@@ -1,7 +1,93 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-from fastapi import APIRouter
+from typing import Annotated
+
+from fastapi import APIRouter, Query
+
+from backend.app.common.casbin_rbac import DependsRBAC
+from backend.app.common.jwt import DependsJwtAuth
+from backend.app.common.pagination import PageDepends, paging_data
+from backend.app.common.response.response_schema import response_base
+from backend.app.database.db_mysql import CurrentSession
+from backend.app.schemas.casbin_rule import (
+ CreatePolicy,
+ UpdatePolicy,
+ DeletePolicy,
+ CreateUserRole,
+ DeleteUserRole,
+ GetAllPolicy,
+)
+from backend.app.services.casbin_service import CasbinService
router = APIRouter()
-# TODO: 添加 casbin 相关接口
+
+@router.get('', summary='(模糊条件)分页获取所有 casbin 规则', dependencies=[DependsJwtAuth, PageDepends])
+async def get_all_casbin(
+ db: CurrentSession,
+ ptype: Annotated[str | None, Query()] = None,
+ sub: Annotated[str | None, Query()] = None,
+):
+ casbin_select = await CasbinService.get_casbin_list(ptype=ptype, sub=sub)
+ page_data = await paging_data(db, casbin_select, GetAllPolicy)
+ return await response_base.success(data=page_data)
+
+
+@router.get('/policies', summary='获取所有 P 规则', dependencies=[DependsJwtAuth])
+async def get_all_policies():
+ policies = await CasbinService.get_policy_list()
+ return await response_base.success(data=policies)
+
+
+@router.post('/policy', summary='添加基于角色(主)/用户(次)的访问权限', dependencies=[DependsRBAC])
+async def create_policy(p: CreatePolicy):
+ """
+ p 规则:
+
+ - 推荐添加基于角色的访问权限, 需配合添加 g 规则才能真正拥有访问权限,适合配置全局接口访问策略
+ **格式**: 角色 role + 访问路径 path + 访问方法 method
+
+ - 如果添加基于用户的访问权限, 不需配合添加 g 规则就能真正拥有权限,适合配置指定用户接口访问策略
+ **格式**: 用户 uuid + 访问路径 path + 访问方法 method
+ """
+ data = await CasbinService.create_policy(p=p)
+ return await response_base.success(data=data)
+
+
+@router.put('/policy', summary='更新基于角色(主)/用户(次)的访问权限', dependencies=[DependsRBAC])
+async def update_policy(old: UpdatePolicy, new: UpdatePolicy):
+ data = await CasbinService.update_policy(old=old, new=new)
+ return await response_base.success(data=data)
+
+
+@router.delete('/policy', summary='删除基于角色(主)/用户的访问权限', dependencies=[DependsRBAC])
+async def delete_policy(p: DeletePolicy):
+ data = await CasbinService.delete_policy(p=p)
+ return await response_base.success(data=data)
+
+
+@router.get('/groups', summary='获取所有 g 规则', dependencies=[DependsJwtAuth])
+async def get_all_groups():
+ data = await CasbinService.get_group_list()
+ return await response_base.success(data=data)
+
+
+@router.post('/group', summary='添加基于用户组的访问权限', dependencies=[DependsRBAC])
+async def create_group(g: CreateUserRole):
+ """
+ g 规则 (**依赖 p 规则**):
+
+ - 如果在 p 规则中添加了基于角色的访问权限, 则还需要在 g 规则中添加基于用户组的访问权限, 才能真正拥有访问权限
+ **格式**: 用户 uuid + 角色 role
+
+ - 如果在p策略中添加了基于用户的访问权限, 则不添加相应的 g 规则能直接拥有访问权限
+ 但是拥有的不是用户角色的所有权限, 而只是单一的对应的 p 规则所添加的访问权限
+ """
+ data = await CasbinService.create_group(g=g)
+ return await response_base.success(data=data)
+
+
+@router.delete('/group', summary='删除基于用户组的访问权限', dependencies=[DependsRBAC])
+async def delete_group(g: DeleteUserRole):
+ data = await CasbinService.delete_group(g=g)
+ return await response_base.success(data=data)
diff --git a/backend/app/api/v1/config.py b/backend/app/api/v1/config.py
index 50001a5e..c311c89e 100644
--- a/backend/app/api/v1/config.py
+++ b/backend/app/api/v1/config.py
@@ -58,5 +58,13 @@ async def get_all_route(request: Request):
data = []
for route in request.app.routes:
if isinstance(route, APIRoute):
- data.append({'path': route.path, 'name': route.name, 'summary': route.summary, 'methods': route.methods})
+ data.append(
+ {
+ 'path': route.path,
+ 'name': route.name,
+ 'summary': route.summary,
+ 'methods': route.methods,
+ 'dependencies': route.dependencies,
+ }
+ )
return await response_base.success(data={'route_list': data})
diff --git a/backend/app/common/casbin_rbac.py b/backend/app/common/casbin_rbac.py
index 0848f69d..31291cc0 100644
--- a/backend/app/common/casbin_rbac.py
+++ b/backend/app/common/casbin_rbac.py
@@ -14,7 +14,7 @@ from backend.app.models.sys_casbin_rule import CasbinRule
class RBAC:
@staticmethod
- async def get_casbin_enforcer() -> casbin.Enforcer:
+ def enforcer() -> casbin.Enforcer:
"""
获取 casbin 执行器
@@ -28,35 +28,34 @@ class RBAC:
async def rbac_verify(self, request: Request, _: str = DependsJwtAuth) -> None:
"""
- 权限校验,超级用户跳过校验,默认拥有所有权限
+ 权限校验
:param request:
:param _:
:return:
"""
- user_uuid = request.user.user_uuid
- user_roles = request.user.roles
- role_data_scope = [role.data_scope for role in user_roles]
super_user = request.user.is_superuser
- path = request.url.path
- method = request.method
-
if super_user:
return
- for ce in settings.CASBIN_EXCLUDE:
- if ce['method'] == method and ce['path'] == path:
- return
+ method = request.method
+ path = request.url.path
+ if (method, path) in settings.CASBIN_EXCLUDE:
+ return
- if 1 in set(role_data_scope):
+ user_roles = request.user.roles
+ data_scope = [role.data_scope for role in user_roles if role.data_scope == 1]
+ if data_scope:
return
# TODO: 通过 redis 做鉴权查询优化,减少数据库查询
- enforcer = await self.get_casbin_enforcer()
+ user_uuid = request.user.user_uuid
+ enforcer = self.enforcer()
if not enforcer.enforce(user_uuid, path, method):
raise AuthorizationError
-rbac = RBAC()
+RBAC = RBAC()
+RbacEnforcer = RBAC.enforcer()
# RBAC 依赖注入
-DependsRBAC = Depends(rbac.rbac_verify)
+DependsRBAC = Depends(RBAC.rbac_verify)
diff --git a/backend/app/core/conf.py b/backend/app/core/conf.py
index f75412b6..fd255fe4 100644
--- a/backend/app/core/conf.py
+++ b/backend/app/core/conf.py
@@ -98,12 +98,12 @@ class Settings(BaseSettings):
# Casbin
CASBIN_RBAC_MODEL_NAME: str = 'rbac_model.conf'
- CASBIN_EXCLUDE: list[dict[str, str]] = [
- {'method': 'POST', 'path': '/v1/auth/swagger_login'},
- {'method': 'POST', 'path': '/v1/auth/login'},
- {'method': 'POST', 'path': '/v1/auth/register'},
- {'method': 'POST', 'path': '/v1/auth/password/reset'},
- ]
+ CASBIN_EXCLUDE: set[tuple[str, str]] = {
+ ('POST', '/v1/auth/swagger_login'),
+ ('POST', '/v1/auth/login'),
+ ('POST', '/v1/auth/register'),
+ ('POST', '/v1/auth/password/reset'),
+ }
# Opera log
OPERA_LOG_EXCLUDE: list[str] = [
diff --git a/backend/app/crud/crud_casbin.py b/backend/app/crud/crud_casbin.py
index 6eaef35b..b940fdcd 100644
--- a/backend/app/crud/crud_casbin.py
+++ b/backend/app/crud/crud_casbin.py
@@ -1,13 +1,23 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
+from sqlalchemy import Select, select, and_
+
from backend.app.crud.base import CRUDBase
from backend.app.models import CasbinRule
from backend.app.schemas.casbin_rule import CreatePolicy, UpdatePolicy
class CRUDCasbin(CRUDBase[CasbinRule, CreatePolicy, UpdatePolicy]):
- # TODO: 添加 casbin 相关数据库操作
- pass
+ async def get_all_policy(self, ptype: str, sub: str) -> Select:
+ se = select(self.model).order_by(self.model.id)
+ where_list = []
+ if ptype:
+ where_list.append(self.model.ptype == ptype)
+ if sub:
+ where_list.append(self.model.v0.like(f'%{sub}%'))
+ if where_list:
+ se = se.where(and_(*where_list))
+ return se
CasbinDao: CRUDCasbin = CRUDCasbin(CasbinRule)
diff --git a/backend/app/schemas/api.py b/backend/app/schemas/api.py
index fb1f5353..d81a0160 100644
--- a/backend/app/schemas/api.py
+++ b/backend/app/schemas/api.py
@@ -9,7 +9,7 @@ from backend.app.common.enums import MethodType
class ApiBase(BaseModel):
name: str
- method: str = Field(..., description='请求方法')
+ method: str = Field(default=MethodType.GET, description='请求方法')
path: str = Field(..., description='api路径')
remark: str | None = None
diff --git a/backend/app/schemas/casbin_rule.py b/backend/app/schemas/casbin_rule.py
index 5f695ddc..accce4c0 100644
--- a/backend/app/schemas/casbin_rule.py
+++ b/backend/app/schemas/casbin_rule.py
@@ -5,12 +5,9 @@ from pydantic import BaseModel, Field, validator
from backend.app.common.enums import MethodType
-class RBACBase(BaseModel):
+class CreatePolicy(BaseModel):
sub: str = Field(..., description='用户uuid / 角色')
-
-
-class CreatePolicy(RBACBase):
- path: str = Field(..., description='api路径')
+ path: str = Field(..., description='api 路径')
method: str = Field(default=MethodType.GET, description='请求方法')
@validator('method')
@@ -32,15 +29,19 @@ class DeletePolicy(CreatePolicy):
class CreateUserRole(BaseModel):
- uuid: str = Field(..., description='用户uuid')
+ uuid: str = Field(..., description='用户 uuid')
role: str = Field(..., description='角色')
+class DeleteUserRole(CreateUserRole):
+ pass
+
+
class GetAllPolicy(BaseModel):
id: int
- ptype: str
- v0: str
- v1: str
+ ptype: str = Field(..., description='规则类型, p 或 g')
+ v0: str = Field(..., description='用户 uuid / 角色')
+ v1: str = Field(..., description='api 路径 / 角色')
v2: str | None = None
v3: str | None = None
v4: str | None = None
diff --git a/backend/app/services/casbin_service.py b/backend/app/services/casbin_service.py
index c763dac7..2e2bd91c 100644
--- a/backend/app/services/casbin_service.py
+++ b/backend/app/services/casbin_service.py
@@ -1,7 +1,62 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
+from sqlalchemy import Select
+
+from backend.app.common.casbin_rbac import RbacEnforcer
+from backend.app.common.exception import errors
+from backend.app.crud.crud_casbin import CasbinDao
+from backend.app.schemas.casbin_rule import CreatePolicy, UpdatePolicy, DeletePolicy, CreateUserRole, DeleteUserRole
class CasbinService:
- # TODO: 添加 casbin 相关服务
- pass
+ @staticmethod
+ async def get_casbin_list(*, ptype: str, sub: str) -> Select:
+ return await CasbinDao.get_all_policy(ptype, sub)
+
+ @staticmethod
+ async def get_policy_list():
+ data = await RbacEnforcer.get_policy()
+ return data
+
+ @staticmethod
+ async def create_policy(*, p: CreatePolicy):
+ data = await RbacEnforcer.add_policy(p.sub, p.path, p.method)
+ if not data:
+ raise errors.ForbiddenError(msg='权限已存在')
+ return data
+
+ @staticmethod
+ async def update_policy(*, old: UpdatePolicy, new: UpdatePolicy):
+ _p = await RbacEnforcer.has_named_policy('p', old.sub, old.path, old.method)
+ if not _p:
+ raise errors.NotFoundError(msg='权限不存在')
+ data = await RbacEnforcer.update_policy([old.sub, old.path, old.method], [new.sub, new.path, new.method])
+ return data
+
+ @staticmethod
+ async def delete_policy(*, p: DeletePolicy):
+ _p = await RbacEnforcer.has_named_policy('p', p.sub, p.path, p.method)
+ if not _p:
+ raise errors.NotFoundError(msg='权限不存在')
+ data = await RbacEnforcer.remove_policy(p.sub, p.path, p.method)
+ return data
+
+ @staticmethod
+ async def get_group_list():
+ data = await RbacEnforcer.get_grouping_policy()
+ return data
+
+ @staticmethod
+ async def create_group(*, g: CreateUserRole):
+ data = await RbacEnforcer.add_grouping_policy(g.uuid, g.role)
+ if not data:
+ raise errors.ForbiddenError(msg='权限已存在')
+ return data
+
+ @staticmethod
+ async def delete_group(*, g: DeleteUserRole):
+ _g = await RbacEnforcer.has_named_grouping_policy('g', g.uuid, g.role)
+ if not _g:
+ raise errors.NotFoundError(msg='权限不存在')
+ data = await RbacEnforcer.remove_grouping_policy(g.uuid, g.role)
+ return data