Skip to content

Reconstruct RBAC authentication logic #264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ See a preview of some of the screenshots
- [x] Global SQLAlchemy 2.0 syntax
- [x] Pydantic v1 and v2 (different branches)
- [x] Casbin RBAC access control model
- [x] Role menu RBAC access control model
- [x] Celery asynchronous tasks
- [x] JWT middleware whitelist authentication
- [x] Global customizable time zone time
Expand Down Expand Up @@ -218,18 +219,13 @@ Execute unittests via pytest
- [Ruff](https://beta.ruff.rs/docs/)
- ...

## 互动
## Interactivity

We only have one current channel.

<table>
<tr>
<td style="text-align: center;"><a href="https://t.me/+ZlPhIFkPp7E4NGI1"> Jump </a></td>
</tr>
<tr>
<td> Telegram </td>
</tr>
</table>
| [Jump](https://t.me/+ZlPhIFkPp7E4NGI1) |
|----------------------------------------|
| Telegram |

## Sponsor us

Expand Down
12 changes: 4 additions & 8 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
- [x] 全局 SQLAlchemy 2.0 语法
- [x] Pydantic v1 和 v2 (不同分支)
- [x] Casbin RBAC 访问控制模型
- [x] 角色菜单 RBAC 访问控制模型
- [x] Celery 异步任务
- [x] JWT 中间件白名单认证
- [x] 全局自定义时区时间
Expand Down Expand Up @@ -216,14 +217,9 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三

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

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

## 赞助我们

Expand Down
41 changes: 35 additions & 6 deletions backend/app/api/v1/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
# -*- coding: utf-8 -*-
from typing import Annotated

from fastapi import APIRouter, Query
from fastapi import APIRouter, Depends, Query

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


@router.get('', summary='(模糊条件)分页获取所有接口', dependencies=[DependsJwtAuth, PageDepends])
@router.get(
'',
summary='(模糊条件)分页获取所有接口',
dependencies=[
DependsJwtAuth,
DependsPagination,
],
)
async def get_api_list(
db: CurrentSession,
name: Annotated[str | None, Query()] = None,
Expand All @@ -39,21 +47,42 @@ async def get_api_list(
return await response_base.success(data=page_data)


@router.post('', summary='创建接口', dependencies=[DependsRBAC])
@router.post(
'',
summary='创建接口',
dependencies=[
Depends(RequestPermission('sys:api:add')),
DependsRBAC,
],
)
async def create_api(obj: CreateApi):
await ApiService.create(obj=obj)
return await response_base.success()


@router.put('/{pk}', summary='更新接口', dependencies=[DependsRBAC])
@router.put(
'/{pk}',
summary='更新接口',
dependencies=[
Depends(RequestPermission('sys:api:edit')),
DependsRBAC,
],
)
async def update_api(pk: int, obj: UpdateApi):
count = await ApiService.update(pk=pk, obj=obj)
if count > 0:
return await response_base.success()
return await response_base.fail()


@router.delete('', summary='(批量)删除接口', dependencies=[DependsRBAC])
@router.delete(
'',
summary='(批量)删除接口',
dependencies=[
Depends(RequestPermission('sys:api:del')),
DependsRBAC,
],
)
async def delete_api(pk: Annotated[list[int], Query(...)]):
count = await ApiService.delete(pk=pk)
if count > 0:
Expand Down
9 changes: 7 additions & 2 deletions backend/app/api/v1/auth/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
router = APIRouter()


@router.post('/swagger_login', summary='swagger 表单登录', description='form 格式登录,仅用于 swagger 文档调试接口')
@router.post(
'/swagger_login',
summary='swagger 表单登录',
description='form 格式登录,用于 swagger 文档调试以及获取 JWT Auth',
deprecated=True,
)
async def swagger_user_login(form_data: OAuth2PasswordRequestForm = Depends()) -> GetSwaggerToken:
token, user = await AuthService().swagger_login(form_data=form_data)
return GetSwaggerToken(access_token=token, user=user) # type: ignore
Expand All @@ -25,7 +30,7 @@ async def swagger_user_login(form_data: OAuth2PasswordRequestForm = Depends()) -
@router.post(
'/login',
summary='用户登录',
description='json 格式登录, 仅支持在第三方api工具调试接口, 例如: postman',
description='json 格式登录, 仅支持在第三方api工具调试, 例如: postman',
dependencies=[Depends(RateLimiter(times=5, minutes=1))],
)
async def user_login(request: Request, obj: AuthLogin, background_tasks: BackgroundTasks):
Expand Down
129 changes: 110 additions & 19 deletions backend/app/api/v1/casbin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
# -*- coding: utf-8 -*-
from typing import Annotated

from fastapi import APIRouter, Path, Query
from fastapi import APIRouter, Depends, Path, Query

from backend.app.common.jwt import DependsJwtAuth
from backend.app.common.pagination import PageDepends, paging_data
from backend.app.common.pagination import DependsPagination, paging_data
from backend.app.common.permission import RequestPermission
from backend.app.common.rbac import DependsRBAC
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,
CreateUserRole,
DeleteAllPolicies,
DeleteAllUserRoles,
DeletePolicy,
DeleteUserRole,
GetAllPolicy,
Expand All @@ -24,11 +24,18 @@
router = APIRouter()


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


@router.post('/policy', summary='添加P权限规则', dependencies=[DependsRBAC])
@router.post(
'/policy',
summary='添加P权限规则',
dependencies=[
Depends(RequestPermission('casbin:p:add')),
DependsRBAC,
],
)
async def create_policy(p: CreatePolicy):
"""
p 规则:
Expand All @@ -62,37 +76,79 @@ async def create_policy(p: CreatePolicy):
return await response_base.success(data=data)


@router.post('/policies', summary='添加多组P权限规则', dependencies=[DependsRBAC])
@router.post(
'/policies',
summary='添加多组P权限规则',
dependencies=[
Depends(RequestPermission('casbin:p:group:add')),
DependsRBAC,
],
)
async def create_policies(ps: list[CreatePolicy]):
data = await CasbinService.create_policies(ps=ps)
return await response_base.success(data=data)


@router.put('/policy', summary='更新P权限规则', dependencies=[DependsRBAC])
@router.put(
'/policy',
summary='更新P权限规则',
dependencies=[
Depends(RequestPermission('casbin:p:edit')),
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.put('/policies', summary='更新多组P权限规则', dependencies=[DependsRBAC])
@router.put(
'/policies',
summary='更新多组P权限规则',
dependencies=[
Depends(RequestPermission('casbin:p:group:edit')),
DependsRBAC,
],
)
async def update_policies(old: list[UpdatePolicy], new: list[UpdatePolicy]):
data = await CasbinService.update_policies(old=old, new=new)
return await response_base.success(data=data)


@router.delete('/policy', summary='删除P权限规则', dependencies=[DependsRBAC])
@router.delete(
'/policy',
summary='删除P权限规则',
dependencies=[
Depends(RequestPermission('casbin:p:del')),
DependsRBAC,
],
)
async def delete_policy(p: DeletePolicy):
data = await CasbinService.delete_policy(p=p)
return await response_base.success(data=data)


@router.delete('/policies', summary='删除多组P权限规则', dependencies=[DependsRBAC])
@router.delete(
'/policies',
summary='删除多组P权限规则',
dependencies=[
Depends(RequestPermission('casbin:p:group:del')),
DependsRBAC,
],
)
async def delete_policies(ps: list[DeletePolicy]):
data = await CasbinService.delete_policies(ps=ps)
return await response_base.success(data=data)


@router.delete('/policies/all', summary='删除所有P权限规则', dependencies=[DependsRBAC])
@router.delete(
'/policies/all',
summary='删除所有P权限规则',
dependencies=[
Depends(RequestPermission('casbin:p:empty')),
DependsRBAC,
],
)
async def delete_all_policies(sub: DeleteAllPolicies):
count = await CasbinService.delete_all_policies(sub=sub)
if count > 0:
Expand All @@ -106,7 +162,14 @@ async def get_all_groups():
return await response_base.success(data=data)


@router.post('/group', summary='添加G权限规则', dependencies=[DependsRBAC])
@router.post(
'/group',
summary='添加G权限规则',
dependencies=[
Depends(RequestPermission('casbin:g:add')),
DependsRBAC,
],
)
async def create_group(g: CreateUserRole):
"""
g 规则 (**依赖 p 规则**):
Expand All @@ -121,26 +184,54 @@ async def create_group(g: CreateUserRole):
return await response_base.success(data=data)


@router.post('/groups', summary='添加多组G权限规则', dependencies=[DependsRBAC])
@router.post(
'/groups',
summary='添加多组G权限规则',
dependencies=[
Depends(RequestPermission('casbin:g:group:add')),
DependsRBAC,
],
)
async def create_groups(gs: list[CreateUserRole]):
data = await CasbinService.create_groups(gs=gs)
return await response_base.success(data=data)


@router.delete('/group', summary='删除G权限规则', dependencies=[DependsRBAC])
@router.delete(
'/group',
summary='删除G权限规则',
dependencies=[
Depends(RequestPermission('casbin:g:del')),
DependsRBAC,
],
)
async def delete_group(g: DeleteUserRole):
data = await CasbinService.delete_group(g=g)
return await response_base.success(data=data)


@router.delete('/groups', summary='删除多组G权限规则', dependencies=[DependsRBAC])
@router.delete(
'/groups',
summary='删除多组G权限规则',
dependencies=[
Depends(RequestPermission('casbin:g:group:del')),
DependsRBAC,
],
)
async def delete_groups(gs: list[DeleteUserRole]):
data = await CasbinService.delete_groups(gs=gs)
return await response_base.success(data=data)


@router.delete('/groups/all', summary='删除所有G权限规则', dependencies=[DependsRBAC])
async def delete_all_groups(uuid: DeleteAllUserRoles):
@router.delete(
'/groups/all',
summary='删除所有G权限规则',
dependencies=[
Depends(RequestPermission('casbin:g:empty')),
DependsRBAC,
],
)
async def delete_all_groups(uuid: str):
count = await CasbinService.delete_all_groups(uuid=uuid)
if count > 0:
return await response_base.success()
Expand Down
Loading