diff --git a/backend/core/conf.py b/backend/core/conf.py index 4de1e3a7d..73ffbbbc6 100644 --- a/backend/core/conf.py +++ b/backend/core/conf.py @@ -77,7 +77,7 @@ class Settings(BaseSettings): JWT_USER_REDIS_EXPIRE_SECONDS: int = 60 * 60 * 24 * 7 # 7 天 # RBAC - RBAC_ROLE_MENU_MODE: bool = False + RBAC_ROLE_MENU_MODE: bool = True RBAC_ROLE_MENU_EXCLUDE: list[str] = [ 'sys:monitor:redis', 'sys:monitor:server', diff --git a/backend/plugin/casbin/__init__.py b/backend/plugin/casbin/__init__.py deleted file mode 100644 index 56fafa58b..000000000 --- a/backend/plugin/casbin/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- diff --git a/backend/plugin/casbin/api/__init__.py b/backend/plugin/casbin/api/__init__.py deleted file mode 100644 index 56fafa58b..000000000 --- a/backend/plugin/casbin/api/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- diff --git a/backend/plugin/casbin/api/v1/__init__.py b/backend/plugin/casbin/api/v1/__init__.py deleted file mode 100644 index 56fafa58b..000000000 --- a/backend/plugin/casbin/api/v1/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- diff --git a/backend/plugin/casbin/api/v1/sys/__init__.py b/backend/plugin/casbin/api/v1/sys/__init__.py deleted file mode 100644 index 56fafa58b..000000000 --- a/backend/plugin/casbin/api/v1/sys/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- diff --git a/backend/plugin/casbin/api/v1/sys/api.py b/backend/plugin/casbin/api/v1/sys/api.py deleted file mode 100644 index 6bf968be5..000000000 --- a/backend/plugin/casbin/api/v1/sys/api.py +++ /dev/null @@ -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() diff --git a/backend/plugin/casbin/api/v1/sys/casbin.py b/backend/plugin/casbin/api/v1/sys/casbin.py deleted file mode 100644 index 88069adad..000000000 --- a/backend/plugin/casbin/api/v1/sys/casbin.py +++ /dev/null @@ -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() diff --git a/backend/plugin/casbin/conf.py b/backend/plugin/casbin/conf.py deleted file mode 100644 index ee031e466..000000000 --- a/backend/plugin/casbin/conf.py +++ /dev/null @@ -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() diff --git a/backend/plugin/casbin/crud/__init__.py b/backend/plugin/casbin/crud/__init__.py deleted file mode 100644 index 56fafa58b..000000000 --- a/backend/plugin/casbin/crud/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- diff --git a/backend/plugin/casbin/crud/crud_api.py b/backend/plugin/casbin/crud/crud_api.py deleted file mode 100644 index 343aab9b0..000000000 --- a/backend/plugin/casbin/crud/crud_api.py +++ /dev/null @@ -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) diff --git a/backend/plugin/casbin/crud/crud_casbin.py b/backend/plugin/casbin/crud/crud_casbin.py deleted file mode 100644 index 3a658010d..000000000 --- a/backend/plugin/casbin/crud/crud_casbin.py +++ /dev/null @@ -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) diff --git a/backend/plugin/casbin/model/__init__.py b/backend/plugin/casbin/model/__init__.py deleted file mode 100644 index 62f1f2c85..000000000 --- a/backend/plugin/casbin/model/__init__.py +++ /dev/null @@ -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 diff --git a/backend/plugin/casbin/model/api.py b/backend/plugin/casbin/model/api.py deleted file mode 100644 index 45127b869..000000000 --- a/backend/plugin/casbin/model/api.py +++ /dev/null @@ -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='备注') diff --git a/backend/plugin/casbin/model/casbin_rule.py b/backend/plugin/casbin/model/casbin_rule.py deleted file mode 100644 index 7569f2d4e..000000000 --- a/backend/plugin/casbin/model/casbin_rule.py +++ /dev/null @@ -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'' diff --git a/backend/plugin/casbin/plugin.toml b/backend/plugin/casbin/plugin.toml deleted file mode 100644 index c499bbe54..000000000 --- a/backend/plugin/casbin/plugin.toml +++ /dev/null @@ -1,10 +0,0 @@ -[app] -include = 'admin' - -[api.api] -prefix = '/apis' -tags = '系统API' - -[api.casbin] -prefix = '/casbin' -tags = '系统Casbin权限' diff --git a/backend/plugin/casbin/requirements.txt b/backend/plugin/casbin/requirements.txt deleted file mode 100644 index ac0585416..000000000 --- a/backend/plugin/casbin/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -casbin>=1.38.0 -casbin_async_sqlalchemy_adapter>=1.7.0 diff --git a/backend/plugin/casbin/schema/__init__.py b/backend/plugin/casbin/schema/__init__.py deleted file mode 100644 index 56fafa58b..000000000 --- a/backend/plugin/casbin/schema/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- diff --git a/backend/plugin/casbin/schema/api.py b/backend/plugin/casbin/schema/api.py deleted file mode 100644 index 330360094..000000000 --- a/backend/plugin/casbin/schema/api.py +++ /dev/null @@ -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='更新时间') diff --git a/backend/plugin/casbin/schema/casbin_rule.py b/backend/plugin/casbin/schema/casbin_rule.py deleted file mode 100644 index f5a74e4f9..000000000 --- a/backend/plugin/casbin/schema/casbin_rule.py +++ /dev/null @@ -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='预留字段') diff --git a/backend/plugin/casbin/service/__init__.py b/backend/plugin/casbin/service/__init__.py deleted file mode 100644 index 56fafa58b..000000000 --- a/backend/plugin/casbin/service/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- diff --git a/backend/plugin/casbin/service/api_service.py b/backend/plugin/casbin/service/api_service.py deleted file mode 100644 index 47af85b08..000000000 --- a/backend/plugin/casbin/service/api_service.py +++ /dev/null @@ -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() diff --git a/backend/plugin/casbin/service/casbin_service.py b/backend/plugin/casbin/service/casbin_service.py deleted file mode 100644 index a700548b5..000000000 --- a/backend/plugin/casbin/service/casbin_service.py +++ /dev/null @@ -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() diff --git a/backend/plugin/casbin/utils/__init__.py b/backend/plugin/casbin/utils/__init__.py deleted file mode 100644 index 56fafa58b..000000000 --- a/backend/plugin/casbin/utils/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- diff --git a/backend/plugin/casbin/utils/rbac.py b/backend/plugin/casbin/utils/rbac.py deleted file mode 100644 index 1eb81f787..000000000 --- a/backend/plugin/casbin/utils/rbac.py +++ /dev/null @@ -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