casbin多租户模型

This commit is contained in:
wuaho 2021-05-14 09:46:28 +08:00
parent a802cb2d12
commit d4b49cbb2b
19 changed files with 275 additions and 109 deletions

View File

@ -11,7 +11,7 @@ from .endpoints import query
api_router = APIRouter() api_router = APIRouter()
api_router.include_router(user.router, tags=["登录接口"], prefix='/user') api_router.include_router(user.router, tags=["用户接口"], prefix='/user')
api_router.include_router(project.router, tags=["项目接口"], prefix='/project') api_router.include_router(project.router, tags=["项目接口"], prefix='/project')
api_router.include_router(folder.router, tags=["文件夹接口"], prefix='/folder') api_router.include_router(folder.router, tags=["文件夹接口"], prefix='/folder')
api_router.include_router(space.router, tags=["空间接口"], prefix='/space') api_router.include_router(space.router, tags=["空间接口"], prefix='/space')

View File

@ -13,7 +13,8 @@ router = APIRouter()
@router.get("/api_list") @router.get("/api_list")
async def api_list(request: Request, current_user: schemas.UserDB = Depends(deps.get_current_user)) -> schemas.Msg: async def api_list(request: Request, game: str,
current_user: schemas.UserDB = Depends(deps.get_current_user)) -> schemas.Msg:
"""api 列表""" """api 列表"""
app = request.app app = request.app
data = [] data = []
@ -25,85 +26,90 @@ async def api_list(request: Request, current_user: schemas.UserDB = Depends(deps
@router.post("/add_role") @router.post("/add_role")
async def add_role(request: Request, data_in: schemas.CasbinRoleCreate, async def add_role(request: Request, game: str, data_in: schemas.CasbinRoleCreate,
db: AsyncIOMotorDatabase = Depends(get_database), db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user) current_user: schemas.UserDB = Depends(deps.get_current_user)
) -> schemas.Msg: ) -> schemas.Msg:
"""创建角色""" """创建角色"""
role = ( role_dom = game
'g', # 角色有的接口权限
'root', for obj in data_in.role_api:
data_in.role_name, casbin_enforcer.add_policy(data_in.role_name, role_dom, obj, '*')
None await crud.authority.create(db, 'p', data_in.role_name, role_dom, obj, '*')
)
await crud.authority.create(db, role) # 管理员默认拥有该角色 方便从db中读出
await crud.authority.create(db, 'g', settings.SUPERUSER_NAME, data_in.role_name, '*', '*',
role_name=data_in.role_name,
game=role_dom)
for item in data_in.role_api:
await crud.authority.create(db, (
'p',
data_in.role_name,
item,
'*'
))
return schemas.Msg(code=0, msg='ok') return schemas.Msg(code=0, msg='ok')
@router.post("/add_account") @router.post("/add_account")
async def add_account(request: Request, async def add_account(request: Request,
game: str,
data_in: schemas.AccountCreate, data_in: schemas.AccountCreate,
db: AsyncIOMotorDatabase = Depends(get_database), db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user) current_user: schemas.UserDB = Depends(deps.get_current_user)
) -> schemas.Msg: ) -> schemas.Msg:
"""创建账号 并设置角色""" """创建账号 并设置角色"""
account = schemas.UserCreate(name=data_in.name, password=settings.DEFAULT_PASSWORD) account = schemas.UserCreate(name=data_in.username, nickname=data_in.nickname, password=settings.DEFAULT_PASSWORD)
try: try:
await crud.user.create(db, account) await crud.user.create(db, account)
except pymongo.errors.DuplicateKeyError: except pymongo.errors.DuplicateKeyError:
return schemas.Msg(code=-1, msg='用户名已存在') return schemas.Msg(code=-1, msg='用户名已存在')
rule = (
'g', casbin_enforcer.add_grouping_policy(data_in.username, data_in.role_name, game)
data_in.name, await crud.authority.create(db, 'g', data_in.username, data_in.role_name, game)
data_in.role_name,
None
)
await crud.authority.create(db, rule)
return schemas.Msg(code=0, msg='ok') return schemas.Msg(code=0, msg='ok')
@router.get("/all_role") @router.get("/all_role")
async def all_role(request: Request, async def all_role(request: Request,
game: str,
db: AsyncIOMotorDatabase = Depends(get_database), db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user) current_user: schemas.UserDB = Depends(deps.get_current_user)
) -> schemas.Msg: ) -> schemas.Msg:
"""获取所有角色 和 角色权限""" """获取所有角色"""
routes = {} roles = await crud.authority.find_many(db, role_name={'$exists': 1}, game=game)
for item in request.app.routes: data = [{'role': item['v1'], 'name': item['role_name']} for item in roles]
routes[item.path] = item.description if hasattr(item, 'description') else item.name return schemas.Msg(code=0, msg='ok', data=data)
roles = casbin_enforcer.get_all_roles()
permissions = {}
for role in roles:
for _, path, _ in casbin_enforcer.get_permissions_for_user(role):
permissions.setdefault(role, [])
if path == '*':
permissions[role].clear()
permissions[role] = [{
'path': k,
'name': v
} for k, v in routes.items()]
break
if path in routes: # @router.get("/all_role")
permissions[role].append( # async def all_role(request: Request,
{ # db: AsyncIOMotorDatabase = Depends(get_database),
'path': path, # current_user: schemas.UserDB = Depends(deps.get_current_user)
'name': routes[path] # ) -> schemas.Msg:
} # """获取所有角色 和 角色权限"""
) # routes = {}
# for item in request.app.routes:
return schemas.Msg(code=0, msg='ok', data={'roles': roles, 'permissions': permissions}) # routes[item.path] = item.description if hasattr(item, 'description') else item.name
# roles = casbin_enforcer.get_all_roles()
# permissions = {}
# for role in roles:
# for _, path, _ in casbin_enforcer.get_permissions_for_user(role):
# permissions.setdefault(role, [])
# if path == '*':
# permissions[role].clear()
#
# permissions[role] = [{
# 'path': k,
# 'name': v
# } for k, v in routes.items()]
# break
#
# if path in routes:
# permissions[role].append(
# {
# 'path': path,
# 'name': routes[path]
# }
# )
#
# return schemas.Msg(code=0, msg='ok', data={'roles': roles, 'permissions': permissions})
@router.post("/set_role") @router.post("/set_role")
@ -115,13 +121,10 @@ async def set_role(request: Request,
"""设置账号角色""" """设置账号角色"""
casbin_enforcer.delete_user(data_id.name) casbin_enforcer.delete_user(data_id.name)
casbin_enforcer.add_role_for_user(data_id.name, data_id.role_name) casbin_enforcer.add_role_for_user(data_id.name, data_id.role_name)
crud.authority.update_upsert(db, {'prtype': 'g', 'v0': data_id.name}, v1=data_id.role_name) await crud.authority.update_one(db, {'ptype': 'g', 'v0': data_id.name}, dict(v1=data_id.role_name))
return schemas.Msg(code=0, msg='ok') return schemas.Msg(code=0, msg='ok')
# @router.get("/delete_user") # @router.get("/delete_user")
# async def delete_user(request: Request, # async def delete_user(request: Request,
# data_id: schemas.AccountDeleteUser, # data_id: schemas.AccountDeleteUser,

View File

@ -47,7 +47,8 @@ async def move(
""" """
移动看板 移动看板
""" """
res = await crud.dashboard.update_one(db, id=data_in.source_id, cat=data_in.cat, pid=data_in.dest_id) res = await crud.dashboard.update_one(db, {'_id': data_in.source_id},
{'$set': dict(cat=data_in.cat, pid=data_in.dest_id)})
if res.deleted_count == 0: if res.deleted_count == 0:
return schemas.Msg(code=-1, msg='error', data='删除失败') return schemas.Msg(code=-1, msg='error', data='删除失败')
return schemas.Msg(code=0, msg='ok', data='删除成功') return schemas.Msg(code=0, msg='ok', data='删除成功')
@ -59,7 +60,8 @@ async def add_report(data_in: schemas.AddReport,
current_user: schemas.UserDB = Depends(deps.get_current_user) current_user: schemas.UserDB = Depends(deps.get_current_user)
): ):
"""添加报表""" """添加报表"""
res = await crud.dashboard.update_one(db, id=data_in.id, **{'$push': {'reports': {'$each': data_in.report_ids}}}) res = await crud.dashboard.update_one(db, {'_id': data_in.id},
{'$push': {'reports': {'$each': data_in.report_ids}}})
return schemas.Msg(code=0, msg='ok', data='ok') return schemas.Msg(code=0, msg='ok', data='ok')
@ -70,7 +72,7 @@ async def del_report(data_in: schemas.DelReport,
): ):
"""删除报表""" """删除报表"""
for item in data_in.report_ids: for item in data_in.report_ids:
await crud.dashboard.update_one(db, id=data_in.id, **{'$pull': {'reports': item}}) await crud.dashboard.update_one(db, {'_id': data_in.id}, {'$pull': {'reports': item}})
return schemas.Msg(code=0, msg='ok', data='ok') return schemas.Msg(code=0, msg='ok', data='ok')

View File

@ -3,9 +3,11 @@ from fastapi import APIRouter, Depends, Request
from motor.motor_asyncio import AsyncIOMotorDatabase from motor.motor_asyncio import AsyncIOMotorDatabase
import crud, schemas import crud, schemas
from api import deps from api import deps
from core.config import settings
from db import get_database from db import get_database
from schemas.project import ProjectCreate from schemas.project import ProjectCreate
from utils import casbin_enforcer
router = APIRouter() router = APIRouter()
@ -22,8 +24,14 @@ async def create(
except pymongo.errors.DuplicateKeyError: except pymongo.errors.DuplicateKeyError:
return schemas.Msg(code=-1, msg='项目名已存在', data='项目名已存在') return schemas.Msg(code=-1, msg='项目名已存在', data='项目名已存在')
# todo 建默认文件夹 # todo 建默认文件夹
# schemas.FolderCreate
# await crud.folder.create(db, data_in, user_id=current_user.id) # 新建项目管理员权限
role_name = f'{data_in.game}_admin'
role_dom = data_in.game
casbin_enforcer.add_policy(role_name, role_dom, '*', '*')
await crud.authority.create(db, 'p', role_name, role_dom, '*', '*')
# 添加角色
await crud.authority.create(db, 'g', settings.SUPERUSER_NAME, role_name, '*', '*', role_name='项目管理员', game=role_dom)
return schemas.Msg(code=0, msg='创建成功') return schemas.Msg(code=0, msg='创建成功')
@ -38,6 +46,49 @@ async def read_project(request: Request,
return schemas.Msg(code=0, msg='ok', data=res) return schemas.Msg(code=0, msg='ok', data=res)
@router.post("/add_members")
async def add_members(request: Request,
game: str,
data_in: schemas.ProjectMember,
db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
):
"""项目添加成员"""
#
# await crud.project.add_members(db, data_in)
for item in data_in.members:
casbin_enforcer.add_grouping_policy(item.username, item.role, game)
await crud.authority.create(db, 'g', item.username, item.role, game)
return schemas.Msg(code=0, msg='ok', data=data_in)
@router.get("/members")
async def members(request: Request,
game: str,
db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
):
"""查看项目成员"""
res = await crud.authority.find_many(db, ptype='g', v2=game)
data = [{'name': 'root', 'role': '超级管理员'}]
data += [{'name': item['v0'], 'role': item['v1']} for item in res]
return schemas.Msg(code=0, msg='ok', data=data)
@router.post("/del_member")
async def members(request: Request,
game: str,
data_in: schemas.ProjectDelMember,
db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
):
"""删除项目成员"""
casbin_enforcer.delete_user(data_in.username)
await crud.authority.delete(db, ptype='g', v2=game, v0=data_in.username)
return schemas.Msg(code=0, msg='ok')
@router.post("/kanban") @router.post("/kanban")
async def read_kanban( async def read_kanban(
data_in: schemas.ProjectKanban, data_in: schemas.ProjectKanban,

View File

@ -13,6 +13,7 @@ router = APIRouter()
@router.post("/") @router.post("/")
async def read_table_struct( async def read_table_struct(
request: Request, request: Request,
game: str,
data_in: schemas.GetTable, data_in: schemas.GetTable,
rdb: Redis = Depends(get_redis_pool), rdb: Redis = Depends(get_redis_pool),
current_user: schemas.UserDB = Depends(deps.get_current_user) current_user: schemas.UserDB = Depends(deps.get_current_user)
@ -20,4 +21,3 @@ async def read_table_struct(
"""获取表结构""" """获取表结构"""
data = await rdb.get(f'{data_in.game}_{data_in.name}') data = await rdb.get(f'{data_in.game}_{data_in.name}')
return schemas.Msg(code=0, msg='ok', data=json.loads(data)) return schemas.Msg(code=0, msg='ok', data=json.loads(data))

View File

@ -35,6 +35,7 @@ async def login(
return { return {
'data': { 'data': {
'name': user.name, 'name': user.name,
'nickname': user.nickname,
'email': user.email, 'email': user.email,
'token': security.create_access_token( 'token': security.create_access_token(
expires_delta=access_token_expires, _id=str(user.id), email=user.email, expires_delta=access_token_expires, _id=str(user.id), email=user.email,
@ -45,6 +46,7 @@ async def login(
}, },
'access_token': security.create_access_token( 'access_token': security.create_access_token(
expires_delta=access_token_expires, _id=str(user.id), email=user.email, expires_delta=access_token_expires, _id=str(user.id), email=user.email,
nickname=user.nickname,
is_superuser=user.is_superuser, name=user.name is_superuser=user.is_superuser, name=user.name
), ),
"token_type": "bearer", "token_type": "bearer",
@ -62,16 +64,18 @@ def me(current_user: schemas.User = Depends(deps.get_current_user)) -> Any:
return current_user return current_user
@router.get("/all_user") @router.get("/all_account")
async def all_user(db: AsyncIOMotorDatabase = Depends(get_database)) -> Any: async def all_account(page: int = 1, limit: int = 10, db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.User = Depends(deps.get_current_user)
) -> Any:
""" """
获取所有用户 获取所有用户
""" """
users = await crud.user.find_many(db) page -= 1
data = [{ if page < 0:
"_id": "0", page = 0
"name": "所有用户" cursor = crud.user.find(db).skip(page * limit).limit(limit)
}]
data += [schemas.UserDB(**user) for user in users] data = [schemas.UserDB(**user) async for user in cursor]
return schemas.Msg(code=0, msg='ok', data=data) return schemas.Msg(code=0, msg='ok', data=data)

View File

@ -19,9 +19,10 @@ class Settings(BaseSettings):
DATABASE_URI = f'mongodb://{MDB_USER}:{MDB_PASSWORD}@{MDB_HOST}:{MDB_PORT}/admin' DATABASE_URI = f'mongodb://{MDB_USER}:{MDB_PASSWORD}@{MDB_HOST}:{MDB_PORT}/admin'
FIRST_EMAIL: str = '15392746632@qq.com' SUPERUSER_EMAIL: str = '15392746632@qq.com'
FIRST_SUPERUSER_PASSWORD: str = '123456' SUPERUSER_PASSWORD: str = '123456'
FIRST_NAME: str = 'root' SUPERUSER_NAME: str = 'root'
SUPERUSER_NICKNAME: str = 'root'
DEFAULT_PASSWORD = '123456' DEFAULT_PASSWORD = '123456'

View File

@ -7,27 +7,33 @@ class CRUDBase:
def __init__(self, coll_name): def __init__(self, coll_name):
self.coll_name = coll_name self.coll_name = coll_name
async def get(self, db, id: Union[ObjectId, str]): async def get(self, db, id: Union[ObjectId, str]) -> dict:
return (await db[self.coll_name].find_one({'_id': ObjectId(id)})) or dict() return (await db[self.coll_name].find_one({'_id': ObjectId(id)})) or dict()
async def read_have(self, db, user_id: str, **kwargs): async def read_have(self, db, user_id: str, **kwargs):
where = {'members': user_id} where = {'members': user_id}
where.update(kwargs) where.update(kwargs)
cursor = db[self.coll_name].find(where) cursor = db[self.coll_name].find(where)
return await cursor.to_list(length=999) return await cursor.to_list(length=9999)
async def find_many(self, db, **kwargs): async def find_many(self, db, **kwargs):
cursor = db[self.coll_name].find(kwargs) cursor = db[self.coll_name].find(kwargs)
return await cursor.to_list(length=999) return await cursor.to_list(length=9999)
def find(self, db, *args, **kwargs):
cursor = db[self.coll_name].find(*args, **kwargs)
return cursor
@staticmethod
async def to_list(cursor):
async for doc in cursor:
yield doc
async def delete(self, db, **kwargs): async def delete(self, db, **kwargs):
return await db[self.coll_name].delete_many(kwargs) return await db[self.coll_name].delete_many(kwargs)
async def update_one(self, db, id, **kwargs): async def update_one(self, db, filter, update, upsert=False):
return await db[self.coll_name].update_one({'_id': id}, kwargs) return await db[self.coll_name].update_one(filter, update, upsert)
async def update_upsert(self, db, where: dict, **kwargs):
return await db[self.coll_name].update_one(where, {'$set': kwargs}, upsert=True)
async def distinct(self, db, key, filter=None): async def distinct(self, db, key, filter=None):
return await db[self.coll_name].distinct(key, filter) return await db[self.coll_name].distinct(key, filter)

View File

@ -11,21 +11,29 @@ __all__ = 'authority',
class CRUDAuthority(CRUDBase): class CRUDAuthority(CRUDBase):
async def create(self, db: AsyncIOMotorDatabase, *args): async def create(self, db: AsyncIOMotorDatabase, *args, **kwargs):
casbin_model.add_policy(args[0], args[0], args[1:]) data = dict()
data = {'ptype': args[0], if len(args) > 0:
'v0': args[1], data['ptype'] = args[0]
'v1': args[2], if len(args) > 1:
'v2': args[3], data['v0'] = args[1]
} if len(args) > 2:
await self.update_upsert(db, data, **data) data['v1'] = args[2]
if len(args) > 3:
data['v2'] = args[3]
if len(args) > 4:
data['v3'] = args[4]
if len(args) > 5:
data['v4'] = args[5]
data.update(kwargs)
await self.update_one(db, data, {'$set': data}, upsert=True)
async def create_index(self, db: AsyncIOMotorDatabase): async def create_index(self, db: AsyncIOMotorDatabase):
await db[self.coll_name].create_index( await db[self.coll_name].create_index(
[('ptype', pymongo.DESCENDING), ('v0', pymongo.DESCENDING), ('v1', pymongo.DESCENDING), [('ptype', pymongo.DESCENDING), ('v0', pymongo.DESCENDING), ('v1', pymongo.DESCENDING),
('v2', pymongo.DESCENDING)], ('v2', pymongo.DESCENDING), ('v3', pymongo.DESCENDING)],
unique=True) unique=True)
authority = CRUDAuthority(settings.CASBIN_COLL) authority = CRUDAuthority(settings.CASBIN_COLL)

View File

@ -13,11 +13,16 @@ class CRUDProject(CRUDBase):
**obj_in.dict(), user_id=user_id, members=[user_id], **obj_in.dict(), user_id=user_id, members=[user_id],
_id=uuid.uuid1().hex _id=uuid.uuid1().hex
) )
await db[self.coll_name].insert_one(db_obj.dict(by_alias=True)) return await db[self.coll_name].insert_one(db_obj.dict(by_alias=True))
async def read_project(self, db: AsyncIOMotorDatabase, user_id: str): async def read_project(self, db: AsyncIOMotorDatabase, user_id: str):
return await self.read_have(db, user_id=user_id) return await self.read_have(db, user_id=user_id)
# async def add_members(self, db: AsyncIOMotorDatabase, obj_in: ProjectMember):
# p = await self.get(db, obj_in.project_id)
# members = list(set(p.get('members')) | set(obj_in.members))
# await self.update_one(db, {'_id': obj_in.project_id}, {'$set': {'members': members}})
async def create_index(self, db: AsyncIOMotorDatabase): async def create_index(self, db: AsyncIOMotorDatabase):
await db[self.coll_name].create_index('name', unique=True) await db[self.coll_name].create_index('name', unique=True)

View File

@ -21,6 +21,7 @@ class CRUDUser(CRUDBase):
hashed_password=get_password_hash(obj_in.password), hashed_password=get_password_hash(obj_in.password),
name=obj_in.name, name=obj_in.name,
is_superuser=obj_in.is_superuser, is_superuser=obj_in.is_superuser,
nickname=obj_in.nickname,
_id=uuid.uuid1().hex _id=uuid.uuid1().hex
) )
return await db[self.coll_name].insert_one(db_obj.dict(by_alias=True)) return await db[self.coll_name].insert_one(db_obj.dict(by_alias=True))

View File

@ -11,12 +11,13 @@ db = get_database()
async def create_superuser(): async def create_superuser():
user = await crud.user.get_by_user(db=db, name=settings.FIRST_NAME) user = await crud.user.get_by_user(db=db, name=settings.SUPERUSER_NAME)
if not user: if not user:
user_in = schemas.UserCreate( user_in = schemas.UserCreate(
name=settings.FIRST_NAME, name=settings.SUPERUSER_NAME,
email=settings.FIRST_EMAIL, email=settings.SUPERUSER_EMAIL,
password=settings.FIRST_SUPERUSER_PASSWORD, password=settings.SUPERUSER_PASSWORD,
nickname=settings.SUPERUSER_NICKNAME,
is_superuser=True, is_superuser=True,
) )
await crud.user.create(db, user_in) await crud.user.create(db, user_in)
@ -45,15 +46,10 @@ async def report_index():
async def authority_init(): async def authority_init():
await crud.authority.create_index(db) await crud.authority.create_index(db)
await crud.authority.create(db, 'p', 'admin', '*', '*') await crud.authority.create(db, 'p', 'anonymous', '*', '/docs', '*')
await crud.authority.create(db, 'g', 'root', 'admin', None) await crud.authority.create(db, 'p', 'anonymous', '*', '/openapi.json', '*')
await crud.authority.create(db, 'p', '*', '/docs', '*') await crud.authority.create(db, 'p', 'anonymous', '*', '/api/v1/user/login', '*')
await crud.authority.create(db, 'p', '*', '/openapi.json', '*') await crud.authority.create(db, 'p', 'anonymous', '*', '/docs', '*')
await crud.authority.create(db, 'p', '*', '/api/v1/user/login', '*')
await crud.authority.create(db, 'p', '*', '/api/v1/project/', '*')
await crud.authority.create(db, 'p', '*', '/api/v1/project/kanban', '*')
async def main(): async def main():

View File

@ -5,7 +5,7 @@ from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware from starlette.middleware.cors import CORSMiddleware
from starlette.authentication import AuthenticationBackend, AuthenticationError, AuthCredentials, BaseUser, SimpleUser from starlette.authentication import AuthenticationBackend, AuthenticationError, AuthCredentials, BaseUser, SimpleUser
from starlette.middleware.authentication import AuthenticationMiddleware from starlette.middleware.authentication import AuthenticationMiddleware
from fastapi_authz import CasbinMiddleware from middleware import CasbinMiddleware
from db import connect_to_mongo, close_mongo_connection, get_database from db import connect_to_mongo, close_mongo_connection, get_database
from db.ckdb_utils import connect_to_ck, close_ck_connection from db.ckdb_utils import connect_to_ck, close_ck_connection

1
middleware/__init__.py Normal file
View File

@ -0,0 +1 @@
from .casbin import CasbinMiddleware

70
middleware/casbin.py Normal file
View File

@ -0,0 +1,70 @@
from casbin.enforcer import Enforcer
from fastapi import HTTPException
from starlette.authentication import BaseUser
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.status import HTTP_403_FORBIDDEN
from starlette.types import ASGIApp, Receive, Scope, Send
import schemas
class CasbinMiddleware:
"""
Middleware for Casbin
"""
def __init__(
self,
app: ASGIApp,
enforcer: Enforcer,
) -> None:
"""
Configure Casbin Middleware
:param app:Retain for ASGI.
:param enforcer:Casbin Enforcer, must be initialized before FastAPI start.
"""
self.app = app
self.enforcer = enforcer
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] not in ("http", "websocket"):
await self.app(scope, receive, send)
return
if self._enforce(scope, receive):
await self.app(scope, receive, send)
return
else:
response = JSONResponse(
status_code=HTTP_403_FORBIDDEN,
content="没有操作权限"
)
await response(scope, receive, send)
return
def _enforce(self, scope: Scope, receive: Receive) -> bool:
"""
Enforce a request
:param user: user will be sent to enforcer
:param request: ASGI Request
:return: Enforce Result
"""
request = Request(scope, receive)
path = request.url.path
method = request.method
if 'user' not in scope:
raise RuntimeError("Casbin Middleware must work with an Authentication Middleware")
assert isinstance(request.user, BaseUser)
user = request.user.display_name if request.user.is_authenticated else 'anonymous'
dom = request.query_params.get('game', '0')
print(user, dom, path, method)
return self.enforcer.enforce(user, dom, path, method)

View File

@ -1,14 +1,14 @@
[request_definition] [request_definition]
r = sub, obj, act r = sub, dom, obj, act
[policy_definition] [policy_definition]
p = sub, obj, act p = sub, dom, obj, act
[role_definition] [role_definition]
g = _, _ g = _, _, _
[policy_effect] [policy_effect]
e = some(where (p.eft == allow)) e = some(where (p.eft == allow))
[matchers] [matchers]
m = (p.sub == "*" || g(r.sub, p.sub)) && (r.obj == p.obj || keyMatch(r.obj, p.obj)) && (p.act == "*" || r.act == p.act) m = g(r.sub, p.sub, r.dom) && (p.dom=="*" || r.dom == p.dom) && ( p.obj=="*" || r.obj == p.obj) && (p.act=="*" || r.act == p.act) || r.sub=="root"

View File

@ -22,8 +22,9 @@ class CasbinDB(BaseModel):
class AccountCreate(BaseModel): class AccountCreate(BaseModel):
name: str username: str
role_name: str role_name: str
nickname: str
class AccountDeleteUser(BaseModel): class AccountDeleteUser(BaseModel):

View File

@ -11,6 +11,20 @@ class ProjectBase(BaseModel):
name: str = None name: str = None
class MemberRole(BaseModel):
username: str
role: str
class ProjectMember(BaseModel):
members: List[MemberRole]
# project_id: str
class ProjectDelMember(BaseModel):
username: str
# 解析请求json 创建项目 # 解析请求json 创建项目
class ProjectCreate(ProjectBase): class ProjectCreate(ProjectBase):
name: str = Field(..., title='项目名') name: str = Field(..., title='项目名')

View File

@ -9,6 +9,7 @@ class UserBase(BaseModel):
email: Optional[EmailStr] = None email: Optional[EmailStr] = None
is_superuser: bool = False is_superuser: bool = False
name: Optional[str] = None name: Optional[str] = None
nickname: str
class User(UserBase): class User(UserBase):
@ -23,6 +24,7 @@ class UserLogin(BaseModel):
class UserCreate(UserBase): class UserCreate(UserBase):
password: str password: str
name: str name: str
nickname: str
# **************************************************************************** # ****************************************************************************
@ -33,6 +35,7 @@ class UserDB(DBBase):
email: EmailStr = None email: EmailStr = None
is_superuser: bool = False is_superuser: bool = False
name: str name: str
nickname: str
class UserDBRW(UserDB): class UserDBRW(UserDB):