Update the default RBAC solution (#593)

This commit is contained in:
Wu Clan
2025-04-27 14:54:39 +08:00
committed by GitHub
parent 5842127c2b
commit 9b3425d801
24 changed files with 1 additions and 1057 deletions
-2
View File
@@ -1,2 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-2
View File
@@ -1,2 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-2
View File
@@ -1,2 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
@@ -1,2 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-91
View File
@@ -1,91 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Annotated
from fastapi import APIRouter, Depends, Path, Query, Request
from backend.common.pagination import DependsPagination, PageData, paging_data
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
from backend.common.security.jwt import DependsJwtAuth
from backend.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
from backend.database.db import CurrentSession
from backend.plugin.casbin.schema.api import CreateApiParam, GetApiDetail, UpdateApiParam
from backend.plugin.casbin.service.api_service import api_service
router = APIRouter()
@router.get('/all', summary='获取所有接口', dependencies=[DependsJwtAuth])
async def get_all_apis() -> ResponseSchemaModel[list[GetApiDetail]]:
data = await api_service.get_all()
return response_base.success(data=data)
@router.get('/{pk}', summary='获取接口详情', dependencies=[DependsJwtAuth])
async def get_api(pk: Annotated[int, Path(description='API ID')]) -> ResponseSchemaModel[GetApiDetail]:
api = await api_service.get(pk=pk)
return response_base.success(data=api)
@router.get(
'',
summary='分页获取所有接口',
dependencies=[
DependsJwtAuth,
DependsPagination,
],
)
async def get_pagination_apis(
request: Request,
db: CurrentSession,
name: Annotated[str | None, Query(description='API 名称')] = None,
method: Annotated[str | None, Query(description='请求方法')] = None,
path: Annotated[str | None, Query(description='API 路径')] = None,
) -> ResponseSchemaModel[PageData[GetApiDetail]]:
api_select = await api_service.get_select(request=request, name=name, method=method, path=path)
page_data = await paging_data(db, api_select)
return response_base.success(data=page_data)
@router.post(
'',
summary='创建接口',
dependencies=[
Depends(RequestPermission('sys:api:add')),
DependsRBAC,
],
)
async def create_api(obj: CreateApiParam) -> ResponseModel:
await api_service.create(obj=obj)
return response_base.success()
@router.put(
'/{pk}',
summary='更新接口',
dependencies=[
Depends(RequestPermission('sys:api:edit')),
DependsRBAC,
],
)
async def update_api(pk: Annotated[int, Path(description='API ID')], obj: UpdateApiParam) -> ResponseModel:
count = await api_service.update(pk=pk, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()
@router.delete(
'',
summary='批量删除接口',
dependencies=[
Depends(RequestPermission('sys:api:del')),
DependsRBAC,
],
)
async def delete_api(pk: Annotated[list[int], Query(description='API ID 列表')]) -> ResponseModel:
count = await api_service.delete(pk=pk)
if count > 0:
return response_base.success()
return response_base.fail()
-218
View File
@@ -1,218 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Annotated
from uuid import UUID
from fastapi import APIRouter, Depends, Query
from backend.common.pagination import DependsPagination, PageData, paging_data
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
from backend.common.security.jwt import DependsJwtAuth
from backend.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
from backend.database.db import CurrentSession
from backend.plugin.casbin.schema.casbin_rule import (
CreateGroupParam,
CreatePolicyParam,
DeleteAllPoliciesParam,
DeleteGroupParam,
DeletePolicyParam,
GetPolicyDetail,
UpdatePoliciesParam,
UpdatePolicyParam,
)
from backend.plugin.casbin.service.casbin_service import casbin_service
router = APIRouter()
@router.get(
'',
summary='分页获取所有权限策略',
dependencies=[
DependsJwtAuth,
DependsPagination,
],
)
async def get_pagination_casbin(
db: CurrentSession,
ptype: Annotated[str | None, Query(description='策略类型:p / g')] = None,
sub: Annotated[str | None, Query(description='用户 UUID / 角色 ID')] = None,
) -> ResponseSchemaModel[PageData[GetPolicyDetail]]:
casbin_select = await casbin_service.get_casbin_list(ptype=ptype, sub=sub)
page_data = await paging_data(db, casbin_select)
return response_base.success(data=page_data)
@router.get('/policies', summary='获取所有 P 权限策略', dependencies=[DependsJwtAuth])
async def get_all_policies(
role: Annotated[int | None, Query(description='角色 ID')] = None,
) -> ResponseSchemaModel[list[list[str]]]:
policies = await casbin_service.get_policy_list(role=role)
return response_base.success(data=policies)
@router.post(
'/policy',
summary='添加 P 权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:add')),
DependsRBAC,
],
)
async def create_policy(p: CreatePolicyParam) -> ResponseSchemaModel[bool]:
data = await casbin_service.create_policy(p=p)
return response_base.success(data=data)
@router.post(
'/policies',
summary='添加多组 P 权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:group:add')),
DependsRBAC,
],
)
async def create_policies(ps: list[CreatePolicyParam]) -> ResponseSchemaModel[bool]:
data = await casbin_service.create_policies(ps=ps)
return response_base.success(data=data)
@router.put(
'/policy',
summary='更新 P 权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:edit')),
DependsRBAC,
],
)
async def update_policy(obj: UpdatePolicyParam) -> ResponseSchemaModel[bool]:
data = await casbin_service.update_policy(obj=obj)
return response_base.success(data=data)
@router.put(
'/policies',
summary='更新多组 P 权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:group:edit')),
DependsRBAC,
],
)
async def update_policies(obj: UpdatePoliciesParam) -> ResponseSchemaModel[bool]:
data = await casbin_service.update_policies(obj=obj)
return response_base.success(data=data)
@router.delete(
'/policy',
summary='删除 P 权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:del')),
DependsRBAC,
],
)
async def delete_policy(p: DeletePolicyParam) -> ResponseSchemaModel[bool]:
data = await casbin_service.delete_policy(p=p)
return response_base.success(data=data)
@router.delete(
'/policies',
summary='删除多组 P 权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:group:del')),
DependsRBAC,
],
)
async def delete_policies(ps: list[DeletePolicyParam]) -> ResponseSchemaModel[bool]:
data = await casbin_service.delete_policies(ps=ps)
return response_base.success(data=data)
@router.delete(
'/policies/all',
summary='删除所有 P 权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:empty')),
DependsRBAC,
],
)
async def delete_all_policies(sub: DeleteAllPoliciesParam) -> ResponseModel:
count = await casbin_service.delete_all_policies(sub=sub)
if count > 0:
return response_base.success()
return response_base.fail()
@router.get('/groups', summary='获取所有 G 权限策略', dependencies=[DependsJwtAuth])
async def get_all_groups() -> ResponseSchemaModel[list[list[str]]]:
data = await casbin_service.get_group_list()
return response_base.success(data=data)
@router.post(
'/group',
summary='添加 G 权限策略',
dependencies=[
Depends(RequestPermission('casbin:g:add')),
DependsRBAC,
],
)
async def create_group(g: CreateGroupParam) -> ResponseSchemaModel[bool]:
data = await casbin_service.create_group(g=g)
return response_base.success(data=data)
@router.post(
'/groups',
summary='添加多组 G 权限策略',
dependencies=[
Depends(RequestPermission('casbin:g:group:add')),
DependsRBAC,
],
)
async def create_groups(gs: list[CreateGroupParam]) -> ResponseSchemaModel[bool]:
data = await casbin_service.create_groups(gs=gs)
return response_base.success(data=data)
@router.delete(
'/group',
summary='删除 G 权限策略',
dependencies=[
Depends(RequestPermission('casbin:g:del')),
DependsRBAC,
],
)
async def delete_group(g: DeleteGroupParam) -> ResponseSchemaModel[bool]:
data = await casbin_service.delete_group(g=g)
return response_base.success(data=data)
@router.delete(
'/groups',
summary='删除多组 G 权限策略',
dependencies=[
Depends(RequestPermission('casbin:g:group:del')),
DependsRBAC,
],
)
async def delete_groups(gs: list[DeleteGroupParam]) -> ResponseSchemaModel[bool]:
data = await casbin_service.delete_groups(gs=gs)
return response_base.success(data=data)
@router.delete(
'/groups/all',
summary='删除所有 G 权限策略',
dependencies=[
Depends(RequestPermission('casbin:g:empty')),
DependsRBAC,
],
)
async def delete_all_groups(uuid: Annotated[UUID, Query()]) -> ResponseModel:
count = await casbin_service.delete_all_groups(uuid=uuid)
if count > 0:
return response_base.success()
return response_base.fail()
-26
View File
@@ -1,26 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from functools import lru_cache
from pydantic_settings import BaseSettings
from backend.core.conf import settings
class CasbinSettings(BaseSettings):
"""Casbin 配置"""
# RBAC
RBAC_CASBIN_EXCLUDE: set[tuple[str, str]] = {
('POST', f'{settings.FASTAPI_API_V1_PATH}/auth/logout'),
('POST', f'{settings.FASTAPI_API_V1_PATH}/auth/token/new'),
}
@lru_cache
def get_casbin_settings() -> CasbinSettings:
"""获取 Casbin 配置"""
return CasbinSettings()
casbin_settings = get_casbin_settings()
-2
View File
@@ -1,2 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-99
View File
@@ -1,99 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Sequence
from fastapi import Request
from sqlalchemy import Select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy_crud_plus import CRUDPlus
from backend.common.security.permission import filter_data_permission
from backend.plugin.casbin.model import Api
from backend.plugin.casbin.schema.api import CreateApiParam, UpdateApiParam
class CRUDApi(CRUDPlus[Api]):
"""API 数据库操作类"""
async def get(self, db: AsyncSession, pk: int) -> Api | None:
"""
获取 API
:param db: 数据库会话
:param pk: API ID
:return:
"""
return await self.select_model(db, pk)
async def get_list(self, request: Request, name: str = None, method: str = None, path: str = None) -> Select:
"""
获取 API 列表
:param request: FastAPI 请求对象
:param name: API 名称
:param method: 请求方法
:param path: API 路径
:return:
"""
filters = {}
if name is not None:
filters.update(name__like=f'%{name}%')
if method is not None:
filters.update(method=method)
if path is not None:
filters.update(path__like=f'%{path}%')
stmt = await self.select_order('created_time', 'desc', **filters)
return stmt.where(filter_data_permission(request))
async def get_all(self, db: AsyncSession) -> Sequence[Api]:
"""
获取所有 API
:param db: 数据库会话
:return:
"""
return await self.select_models(db)
async def get_by_name(self, db: AsyncSession, name: str) -> Api | None:
"""
通过名称获取 API
:param db: 数据库会话
:param name: API 名称
:return:
"""
return await self.select_model_by_column(db, name=name)
async def create(self, db: AsyncSession, obj: CreateApiParam) -> None:
"""
创建 API
:param db: 数据库会话
:param obj: 创建 API 参数
:return:
"""
await self.create_model(db, obj)
async def update(self, db: AsyncSession, pk: int, obj: UpdateApiParam) -> int:
"""
更新 API
:param db: 数据库会话
:param pk: API ID
:param obj: 更新 API 参数
:return:
"""
return await self.update_model(db, pk, obj)
async def delete(self, db: AsyncSession, pk: list[int]) -> int:
"""
删除 API
:param db: 数据库会话
:param pk: API ID 列表
:return:
"""
return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk)
api_dao: CRUDApi = CRUDApi(Api)
-51
View File
@@ -1,51 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from uuid import UUID
from sqlalchemy import Select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy_crud_plus import CRUDPlus
from backend.plugin.casbin.model import CasbinRule
from backend.plugin.casbin.schema.casbin_rule import DeleteAllPoliciesParam
class CRUDCasbin(CRUDPlus[CasbinRule]):
"""Casbin 规则数据库操作类"""
async def get_list(self, ptype: str, sub: str) -> Select:
"""
获取策略列表
:param ptype: 策略类型
:param sub: 用户 UUID / 角色 ID
:return:
"""
return await self.select_order('id', 'desc', ptype=ptype, v0__like=f'%{sub}%')
async def delete_policies_by_sub(self, db: AsyncSession, sub: DeleteAllPoliciesParam) -> int:
"""
删除角色所有 P 策略
:param db: 数据库会话
:param sub: 删除所有 P 策略参数
:return:
"""
filters = [sub.role]
if sub.uuid:
filters.append(sub.uuid)
return await self.delete_model_by_column(db, allow_multiple=True, v0__mor={'eq': filters})
async def delete_groups_by_uuid(self, db: AsyncSession, uuid: UUID) -> int:
"""
删除用户所有 G 策略
:param db: 数据库会话
:param uuid: 用户 UUID
:return:
"""
return await self.delete_model_by_column(db, allow_multiple=True, v0=str(uuid))
casbin_dao: CRUDCasbin = CRUDCasbin(CasbinRule)
-4
View File
@@ -1,4 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from backend.plugin.casbin.model.api import Api
from backend.plugin.casbin.model.casbin_rule import CasbinRule
-20
View File
@@ -1,20 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sqlalchemy import String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column
from backend.common.model import Base, id_key
class Api(Base):
"""API 表"""
__tablename__ = 'sys_api'
id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(50), unique=True, comment='API 名称')
method: Mapped[str] = mapped_column(String(16), comment='请求方法')
path: Mapped[str] = mapped_column(String(500), comment='API 路径')
remark: Mapped[str | None] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='备注')
@@ -1,34 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sqlalchemy import String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column
from backend.common.model import MappedBase, id_key
class CasbinRule(MappedBase):
"""Casbin 规则表"""
__tablename__ = 'sys_casbin_rule'
id: Mapped[id_key]
ptype: Mapped[str] = mapped_column(String(255), comment='策略类型: p / g')
v0: Mapped[str] = mapped_column(String(255), comment='用户 UUID / 角色 ID')
v1: Mapped[str] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='API 路径 / 角色名称')
v2: Mapped[str | None] = mapped_column(String(255), comment='请求方法')
v3: Mapped[str | None] = mapped_column(String(255), comment='预留字段')
v4: Mapped[str | None] = mapped_column(String(255), comment='预留字段')
v5: Mapped[str | None] = mapped_column(String(255), comment='预留字段')
def __str__(self) -> str:
arr = [self.ptype]
for v in (self.v0, self.v1, self.v2, self.v3, self.v4, self.v5):
if v is None:
break
arr.append(v)
return ', '.join(arr)
def __repr__(self) -> str:
return f'<CasbinRule {self.id}: "{str(self)}">'
-10
View File
@@ -1,10 +0,0 @@
[app]
include = 'admin'
[api.api]
prefix = '/apis'
tags = '系统API'
[api.casbin]
prefix = '/casbin'
tags = '系统Casbin权限'
-2
View File
@@ -1,2 +0,0 @@
casbin>=1.38.0
casbin_async_sqlalchemy_adapter>=1.7.0
-2
View File
@@ -1,2 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-35
View File
@@ -1,35 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from datetime import datetime
from pydantic import ConfigDict, Field
from backend.common.enums import MethodType
from backend.common.schema import SchemaBase
class ApiSchemaBase(SchemaBase):
"""API 基础模型"""
name: str = Field(description='API 名称')
method: MethodType = Field(MethodType.GET, description='请求方法')
path: str = Field(description='API 路径')
remark: str | None = Field(None, description='备注')
class CreateApiParam(ApiSchemaBase):
"""创建 API 参数"""
class UpdateApiParam(ApiSchemaBase):
"""更新 API 参数"""
class GetApiDetail(ApiSchemaBase):
"""API 详情"""
model_config = ConfigDict(from_attributes=True)
id: int = Field(description='API ID')
created_time: datetime = Field(description='创建时间')
updated_time: datetime | None = Field(None, description='更新时间')
@@ -1,65 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pydantic import ConfigDict, Field
from backend.common.enums import MethodType
from backend.common.schema import SchemaBase
class CreatePolicyParam(SchemaBase):
"""创建 P 策略参数"""
sub: str = Field(description='用户 UUID / 角色 ID')
path: str = Field(description='API 路径')
method: MethodType = Field(MethodType.GET, description='请求方法')
class UpdatePolicyParam(SchemaBase):
"""更新 P 策略参数"""
old: CreatePolicyParam = Field(description='原策略')
new: CreatePolicyParam = Field(description='新策略')
class UpdatePoliciesParam(SchemaBase):
"""批量更新策略参数"""
old: list[CreatePolicyParam] = Field(description='原策略列表')
new: list[CreatePolicyParam] = Field(description='新策略列表')
class DeletePolicyParam(CreatePolicyParam):
"""删除策略参数"""
class DeleteAllPoliciesParam(SchemaBase):
"""删除所有策略参数"""
uuid: str | None = Field(None, description='用户 UUID')
role: str = Field(description='角色')
class CreateGroupParam(SchemaBase):
"""创建 G 策略参数"""
uuid: str = Field(description='用户 UUID')
role: str = Field(description='角色')
class DeleteGroupParam(CreateGroupParam):
"""删除 G 策略参数"""
class GetPolicyDetail(SchemaBase):
"""策略详情"""
model_config = ConfigDict(from_attributes=True)
id: int = Field(description='规则 ID')
ptype: str = Field(description='规则类型, p / g')
v0: str = Field(description='用户 UUID / 角色 ID')
v1: str = Field(description='API 路径 / 角色')
v2: str | None = Field(None, description='请求方法')
v3: str | None = Field(None, description='预留字段')
v4: str | None = Field(None, description='预留字段')
v5: str | None = Field(None, description='预留字段')
@@ -1,2 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
@@ -1,95 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Sequence
from fastapi import Request
from sqlalchemy import Select
from backend.common.exception import errors
from backend.database.db import async_db_session
from backend.plugin.casbin.crud.crud_api import api_dao
from backend.plugin.casbin.model import Api
from backend.plugin.casbin.schema.api import CreateApiParam, UpdateApiParam
class ApiService:
"""API 服务类"""
@staticmethod
async def get(*, pk: int) -> Api:
"""
获取 API
:param pk: API ID
:return:
"""
async with async_db_session() as db:
api = await api_dao.get(db, pk)
if not api:
raise errors.NotFoundError(msg='接口不存在')
return api
@staticmethod
async def get_select(*, request: Request, name: str = None, method: str = None, path: str = None) -> Select:
"""
获取 API 查询对象
:param request: 请求对象
:param name: API 名称
:param method: 请求方法
:param path: API 路径
:return:
"""
return await api_dao.get_list(request=request, name=name, method=method, path=path)
@staticmethod
async def get_all() -> Sequence[Api]:
"""获取所有 API"""
async with async_db_session() as db:
apis = await api_dao.get_all(db)
return apis
@staticmethod
async def create(*, obj: CreateApiParam) -> None:
"""
创建 API
:param obj: 创建 API 参数
:return:
"""
async with async_db_session.begin() as db:
api = await api_dao.get_by_name(db, obj.name)
if api:
raise errors.ForbiddenError(msg='接口已存在')
await api_dao.create(db, obj)
@staticmethod
async def update(*, pk: int, obj: UpdateApiParam) -> int:
"""
更新 API
:param pk: API ID
:param obj: 更新 API 参数
:return:
"""
async with async_db_session.begin() as db:
api = await api_dao.get(db, pk)
if not api:
raise errors.NotFoundError(msg='接口不存在')
count = await api_dao.update(db, pk, obj)
return count
@staticmethod
async def delete(*, pk: list[int]) -> int:
"""
删除 API
:param pk: API ID 列表
:return:
"""
async with async_db_session.begin() as db:
count = await api_dao.delete(db, pk)
return count
api_service: ApiService = ApiService()
@@ -1,232 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from uuid import UUID
from sqlalchemy import Select
from backend.common.exception import errors
from backend.database.db import async_db_session
from backend.plugin.casbin.crud.crud_casbin import casbin_dao
from backend.plugin.casbin.schema.casbin_rule import (
CreateGroupParam,
CreatePolicyParam,
DeleteAllPoliciesParam,
DeleteGroupParam,
DeletePolicyParam,
UpdatePoliciesParam,
UpdatePolicyParam,
)
from backend.plugin.casbin.utils.rbac import casbin_enforcer
class CasbinService:
"""Casbin 权限服务类"""
@staticmethod
async def get_casbin_list(*, ptype: str, sub: str) -> Select:
"""
获取 Casbin 规则列表
:param ptype: 策略类型
:param sub: 用户 UUID / 角色 ID
:return:
"""
return await casbin_dao.get_list(ptype, sub)
@staticmethod
async def get_policy_list(*, role: int | None) -> list:
"""
获取 P 策略列表
:param role: 角色ID
:return:
"""
enforcer = await casbin_enforcer()
if role is not None:
data = enforcer.get_filtered_named_policy('p', 0, str(role))
else:
data = enforcer.get_policy()
return data
@staticmethod
async def create_policy(*, p: CreatePolicyParam) -> bool:
"""
创建 P 策略
:param p: 策略参数
:return:
"""
enforcer = await casbin_enforcer()
data = await enforcer.add_policy(p.sub, p.path, p.method)
if not data:
raise errors.ForbiddenError(msg='权限已存在')
return data
@staticmethod
async def create_policies(*, ps: list[CreatePolicyParam]) -> bool:
"""
批量创建 P 策略
:param ps: 策略参数列表
:return:
"""
enforcer = await casbin_enforcer()
data = await enforcer.add_policies([list(p.model_dump().values()) for p in ps])
if not data:
raise errors.ForbiddenError(msg='权限已存在')
return data
@staticmethod
async def update_policy(*, obj: UpdatePolicyParam) -> bool:
"""
更新 P 策略
:param obj: 更新 P 策略参数
:return:
"""
old_obj = obj.old
new_obj = obj.new
enforcer = await casbin_enforcer()
_p = enforcer.has_policy(old_obj.sub, old_obj.path, old_obj.method)
if not _p:
raise errors.NotFoundError(msg='权限不存在')
data = await enforcer.update_policy(
[old_obj.sub, old_obj.path, old_obj.method],
[new_obj.sub, new_obj.path, new_obj.method],
)
return data
@staticmethod
async def update_policies(*, obj: UpdatePoliciesParam) -> bool:
"""
批量更新 P 策略
:param obj: 更新 P 策略参数
:return:
"""
enforcer = await casbin_enforcer()
data = await enforcer.update_policies(
[list(o.model_dump().values()) for o in obj.old],
[list(n.model_dump().values()) for n in obj.new],
)
return data
@staticmethod
async def delete_policy(*, p: DeletePolicyParam) -> bool:
"""
删除 P 策略
:param p: 删除参数
:return:
"""
enforcer = await casbin_enforcer()
_p = enforcer.has_policy(p.sub, p.path, p.method)
if not _p:
raise errors.NotFoundError(msg='权限不存在')
data = await enforcer.remove_policy(p.sub, p.path, p.method)
return data
@staticmethod
async def delete_policies(*, ps: list[DeletePolicyParam]) -> bool:
"""
批量删除 P 策略
:param ps: 删除参数列表
:return:
"""
enforcer = await casbin_enforcer()
data = await enforcer.remove_policies([list(p.model_dump().values()) for p in ps])
if not data:
raise errors.NotFoundError(msg='权限不存在')
return data
@staticmethod
async def delete_all_policies(*, sub: DeleteAllPoliciesParam) -> int:
"""
删除所有 P 策略
:param sub: 删除参数
:return:
"""
async with async_db_session.begin() as db:
count = await casbin_dao.delete_policies_by_sub(db, sub)
return count
@staticmethod
async def get_group_list() -> list:
"""获取 G 策略列表"""
enforcer = await casbin_enforcer()
data = enforcer.get_grouping_policy()
return data
@staticmethod
async def create_group(*, g: CreateGroupParam) -> bool:
"""
创建 G 策略
:param g: 创建 G 策略参数
:return:
"""
enforcer = await casbin_enforcer()
data = await enforcer.add_grouping_policy(g.uuid, g.role)
if not data:
raise errors.ForbiddenError(msg='权限已存在')
return data
@staticmethod
async def create_groups(*, gs: list[CreateGroupParam]) -> bool:
"""
批量创建 G 策略
:param gs: 创建参数列表
:return:
"""
enforcer = await casbin_enforcer()
data = await enforcer.add_grouping_policies([list(g.model_dump().values()) for g in gs])
if not data:
raise errors.ForbiddenError(msg='权限已存在')
return data
@staticmethod
async def delete_group(*, g: DeleteGroupParam) -> bool:
"""
删除 G 策略
:param g: 删除参数
:return:
"""
enforcer = await casbin_enforcer()
_g = enforcer.has_grouping_policy(g.uuid, g.role)
if not _g:
raise errors.NotFoundError(msg='权限不存在')
data = await enforcer.remove_grouping_policy(g.uuid, g.role)
return data
@staticmethod
async def delete_groups(*, gs: list[DeleteGroupParam]) -> bool:
"""
批量删除 G 策略
:param gs: 删除参数列表
:return: 是否成功
"""
enforcer = await casbin_enforcer()
data = await enforcer.remove_grouping_policies([list(g.model_dump().values()) for g in gs])
if not data:
raise errors.NotFoundError(msg='权限不存在')
return data
@staticmethod
async def delete_all_groups(*, uuid: UUID) -> int:
"""
删除所有 G 策略
:param uuid: 用户uuid
:return: 删除数量
"""
async with async_db_session.begin() as db:
count = await casbin_dao.delete_groups_by_uuid(db, uuid)
return count
casbin_service: CasbinService = CasbinService()
-2
View File
@@ -1,2 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-58
View File
@@ -1,58 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import casbin
import casbin_async_sqlalchemy_adapter
from fastapi import Request
from backend.common.exception.errors import AuthorizationError
from backend.database.db import async_engine
from backend.plugin.casbin.conf import casbin_settings
from backend.plugin.casbin.model import CasbinRule
async def casbin_enforcer() -> casbin.AsyncEnforcer:
"""获取 casbin 执行器"""
# 模型定义:https://casbin.org/zh/docs/category/model
_CASBIN_RBAC_MODEL_CONF_TEXT = """
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && (keyMatch(r.obj, p.obj) || keyMatch3(r.obj, p.obj)) && (r.act == p.act || p.act == "*")
"""
adapter = casbin_async_sqlalchemy_adapter.Adapter(async_engine, db_class=CasbinRule)
model = casbin.AsyncEnforcer.new_model(text=_CASBIN_RBAC_MODEL_CONF_TEXT)
enforcer = casbin.AsyncEnforcer(model, adapter)
await enforcer.load_policy()
return enforcer
async def casbin_verify(request: Request) -> None:
"""
Casbin 权限校验
:param request: FastAPI 请求对象
:return:
"""
method = request.method
path = request.url.path
# casbin 鉴权白名单
if (method, path) in casbin_settings.RBAC_CASBIN_EXCLUDE:
return
# casbin 权限校验
user_uuid = request.user.uuid
enforcer = await casbin_enforcer()
if not enforcer.enforce(user_uuid, path, method):
raise AuthorizationError