This commit is contained in:
wuaho 2021-05-08 19:13:48 +08:00
parent 2fb9b4573c
commit 224d9816fa
24 changed files with 336 additions and 55 deletions

View File

@ -5,6 +5,7 @@ from .endpoints import folder
from .endpoints import space
from .endpoints import dashboard
from .endpoints import report
from .endpoints import authority
api_router = APIRouter()
@ -14,3 +15,5 @@ api_router.include_router(folder.router, tags=["文件夹接口"], prefix='/fold
api_router.include_router(space.router, tags=["空间接口"], prefix='/space')
api_router.include_router(dashboard.router, tags=["看板接口"], prefix='/dashboard')
api_router.include_router(report.router, tags=["报表接口"], prefix='/report')
api_router.include_router(authority.router, tags=["权限管理接口"], prefix='/authority')

View File

@ -0,0 +1,132 @@
import pymongo
from fastapi import APIRouter, Depends, Request
from motor.motor_asyncio import AsyncIOMotorDatabase
import crud, schemas
from core.config import settings
from core.security import get_password_hash
from db import get_database
from api import deps
from utils import casbin_enforcer
router = APIRouter()
@router.get("/api_list")
async def api_list(request: Request, current_user: schemas.UserDB = Depends(deps.get_current_user)) -> schemas.Msg:
"""api 列表"""
app = request.app
data = []
for r in app.routes:
path = r.path
name = r.description if hasattr(r, 'description') else r.name
data.append({'api': path, 'name': name})
return schemas.Msg(code=0, msg='ok', data=data)
@router.post("/add_role")
async def add_role(request: Request, data_in: schemas.CasbinRoleCreate,
db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
) -> schemas.Msg:
"""创建角色"""
role = (
'g',
'root',
data_in.role_name,
None
)
await crud.authority.create(db, role)
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')
@router.post("/add_account")
async def add_account(request: Request,
data_in: schemas.AccountCreate,
db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
) -> schemas.Msg:
"""创建账号 并设置角色"""
account = schemas.UserCreate(name=data_in.name, password=settings.DEFAULT_PASSWORD)
try:
await crud.user.create(db, account)
except pymongo.errors.DuplicateKeyError:
return schemas.Msg(code=-1, msg='用户名已存在')
rule = (
'g',
data_in.name,
data_in.role_name,
None
)
await crud.authority.create(db, rule)
return schemas.Msg(code=0, msg='ok')
@router.get("/all_role")
async def all_role(request: Request,
db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
) -> schemas.Msg:
"""获取所有角色 和 角色权限"""
routes = {}
for item in request.app.routes:
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")
async def set_role(request: Request,
data_id: schemas.AccountSetRole,
db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
) -> schemas.Msg:
"""设置账号角色"""
casbin_enforcer.delete_user(data_id.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)
return schemas.Msg(code=0, msg='ok')
# @router.get("/delete_user")
# async def delete_user(request: Request,
# data_id: schemas.AccountDeleteUser,
# db: AsyncIOMotorDatabase = Depends(get_database),
# current_user: schemas.UserDB = Depends(deps.get_current_user)
# ) -> schemas.Msg:
# pass
# return schemas.Msg(code=0, msg='暂时没有')

View File

@ -15,11 +15,11 @@ async def create(
db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
) -> schemas.Msg:
"""创建看板"""
try:
await crud.dashboard.create(db, data_in, user_id=current_user.id)
except pymongo.errors.DuplicateKeyError:
return schemas.Msg(code=-1, msg='error', data='看板已存在')
# todo 建默认文件夹
return schemas.Msg(code=-1, msg='看板已存在', data='看板已存在')
return schemas.Msg(code=0, msg='ok', data='创建成功')
@ -30,7 +30,7 @@ async def delete(
db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
) -> schemas.Msg:
# 删除Dashboard 自己创建的
"""删除看板"""
del_dashboard = await crud.dashboard.delete(db, _id=data_in.id, user_id=current_user.id)
if del_dashboard.deleted_count == 0:
@ -39,7 +39,7 @@ async def delete(
@router.post("/move")
async def delete(
async def move(
data_in: schemas.DashboardMove,
db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
@ -58,12 +58,13 @@ async def add_report(data_in: schemas.AddReport,
db: AsyncIOMotorDatabase = Depends(get_database),
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}}})
return schemas.Msg(code=0, msg='ok', data='ok')
@router.post("/del_report")
async def add_report(data_in: schemas.DelReport,
async def del_report(data_in: schemas.DelReport,
db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
):
@ -74,7 +75,7 @@ async def add_report(data_in: schemas.DelReport,
@router.get("/")
async def add_report(_id: str,
async def dashboards(_id: str,
db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
):

View File

@ -19,7 +19,7 @@ async def create(
try:
await crud.folder.create(db, data_in, user_id=current_user.id)
except pymongo.errors.DuplicateKeyError:
return schemas.Msg(code=-1, msg='error', data='文件夹已存在')
return schemas.Msg(code=-1, msg='文件夹已存在', data='文件夹已存在')
# todo 建默认文件夹
return schemas.Msg(code=0, msg='ok', data='创建成功')

View File

@ -65,7 +65,8 @@ async def read_kanban(
# 我的空间
where = {
'project_id': data_in.id,
'$or': [{'rw_members': current_user.id}, {'r_members': current_user.id}]
'members._id': current_user.id
# '$or': [{'rw_members': current_user.id}, {'r_members': current_user.id}]
}
spaces = await crud.space.find_many(db, **where)
# 空间 文件夹 看板
@ -75,7 +76,7 @@ async def read_kanban(
'children': [],
'_id': item['_id']
})
res['spaces'][-1]['authority'] = 'rw' if current_user.id in item['rw_members'] else 'r'
res['spaces'][-1]['authority'] = 'rw' if current_user.id in item['members'] else 'r'
for f in await crud.folder.find_many(db, pid=item['_id']):
res['spaces'][-1]['children'].append({

View File

@ -17,10 +17,16 @@ async def create(
) -> schemas.Msg:
"""创建空间"""
try:
await crud.space.create(db, data_in, user_id=current_user.id)
if data_in.is_all_member:
data_in.members.clear()
users = await crud.user.find_many(db)
for user in users:
if user['_id'] == current_user.id:
continue
data_in.members.append(schemas.space.Member(**user, authority=data_in.authority))
await crud.space.create(db, data_in, user=current_user)
except pymongo.errors.DuplicateKeyError:
return schemas.Msg(code=-1, msg='空间已存在', data='空间已存在')
# todo 建默认文件夹
return schemas.Msg(code=0, msg='创建成功', data='创建成功')

View File

@ -23,6 +23,8 @@ class Settings(BaseSettings):
FIRST_SUPERUSER_PASSWORD: str = '123456'
FIRST_NAME: str = 'root'
DEFAULT_PASSWORD = '123456'
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
SECRET_KEY: str = 'ZaFX6EypK6PtuhGv11q4DLRvAb0csiLx4dbKUwLwCe8'

View File

@ -4,3 +4,4 @@ from .crud_folder import folder
from .crud_space import space
from .crud_dashboard import dashboard
from .crud_report import report
from .crud_authority import authority

View File

@ -25,3 +25,9 @@ class CRUDBase:
async def update_one(self, db, id, **kwargs):
return await db[self.coll_name].update_one({'_id': id}, kwargs)
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):
return await db[self.coll_name].distinct(key, filter)

31
crud/crud_authority.py Normal file
View File

@ -0,0 +1,31 @@
import pymongo
from motor.motor_asyncio import AsyncIOMotorDatabase
from core.config import settings
from crud.base import CRUDBase
from schemas import *
from utils import *
__all__ = 'authority',
class CRUDAuthority(CRUDBase):
async def create(self, db: AsyncIOMotorDatabase, *args):
casbin_model.add_policy(args[0], args[0], args[1:])
data = {'ptype': args[0],
'v0': args[1],
'v1': args[2],
'v2': args[3],
}
await self.update_upsert(db, data, **data)
async def create_index(self, db: AsyncIOMotorDatabase):
await db[self.coll_name].create_index(
[('ptype', pymongo.DESCENDING), ('v0', pymongo.DESCENDING), ('v1', pymongo.DESCENDING),
('v2', pymongo.DESCENDING)],
unique=True)
authority = CRUDAuthority(settings.CASBIN_COLL)

View File

@ -9,12 +9,11 @@ __all__ = 'space',
class CRUDSpace(CRUDBase):
async def create(self, db: AsyncIOMotorDatabase, obj_in: SpaceCreate, user_id: str):
async def create(self, db: AsyncIOMotorDatabase, obj_in: SpaceCreate, user: UserDB):
obj_in.members.append({'_id': user.id, 'name': user.name, 'authority': 'rw'})
db_obj = SpaceDB(
**obj_in.dict(), user_id=user_id,
rw_members=[user_id],
**obj_in.dict(by_alias=True), user_id=user.id,
_id=uuid.uuid1().hex
)
await db[self.coll_name].insert_one(db_obj.dict(by_alias=True))

View File

@ -34,7 +34,7 @@ class CRUDUser(CRUDBase):
return user_obj
async def create_index(self, db: AsyncIOMotorDatabase):
await db[self.coll_name].create_index('username', unique=True)
await db[self.coll_name].create_index('name', unique=True)
user = CRUDUser('user')

View File

@ -38,10 +38,24 @@ async def space_index():
async def dashboard_index():
await crud.dashboard.create_index(db)
async def report_index():
await crud.report.create_index(db)
async def authority_init():
await crud.authority.create_index(db)
await crud.authority.create(db, 'p', 'admin', '*', '*')
await crud.authority.create(db, 'g', 'root', 'admin', None)
await crud.authority.create(db, 'p', '*', '/docs', '*')
await crud.authority.create(db, 'p', '*', '/openapi.json', '*')
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():
await create_superuser()
await project_index()
@ -49,6 +63,7 @@ async def main():
await space_index()
await dashboard_index()
await report_index()
await authority_init()
loop = asyncio.get_event_loop()

13
db/init_menu.py Normal file
View File

@ -0,0 +1,13 @@
data = [
{'title': '用户接口', 'powerarr': [
{'title': '获取所有用户', 'path': '/api/v1/all_user'},
]
},
{'title': '项目接口', 'powerarr': [
{'title': '创建项目', 'path': '/api/v1/project/create'},
]
},
{'title': '文件夹接口', 'powerarr': [
{'title': '创建项目', 'path': '/api/v1/project/create'},
]},
]

45
main.py
View File

@ -5,13 +5,15 @@ import uvicorn
from fastapi import FastAPI
import casbin
from api.deps import get_current_user2
from core.config import settings
from starlette.middleware.cors import CORSMiddleware
from starlette.authentication import AuthenticationBackend, AuthenticationError, SimpleUser, AuthCredentials
from starlette.authentication import AuthenticationBackend, AuthenticationError, AuthCredentials, BaseUser, SimpleUser
from starlette.middleware.authentication import AuthenticationMiddleware
from fastapi_authz import CasbinMiddleware
from db import connect_to_mongo, close_mongo_connection
from db import connect_to_mongo, close_mongo_connection, get_database
from utils import *
app = FastAPI(title=settings.PROJECT_NAME)
@ -24,31 +26,50 @@ if settings.BACKEND_CORS_ORIGINS:
allow_headers=["*"],
)
app.add_event_handler("startup", connect_to_mongo)
app.add_event_handler("shutdown", close_mongo_connection)
class CurrentUser(BaseUser):
def __init__(self, username: str, user_id: str) -> None:
self.username = username
self.id = user_id
@property
def is_authenticated(self) -> bool:
return True
@property
def display_name(self) -> str:
return self.username
@property
def identity(self) -> str:
return ''
class BasicAuth(AuthenticationBackend):
async def authenticate(self, request):
if "Authorization" not in request.headers:
return None
auth = request.headers["Authorization"]
if len(auth) < 20:
return None
try:
scheme, credentials = auth.split()
decoded = base64.b64decode(credentials).decode("ascii")
user = get_current_user2(auth.split(' ')[1])
except (ValueError, UnicodeDecodeError, binascii.Error):
raise AuthenticationError("Invalid basic auth credentials")
username, _, password = decoded.partition(":")
return AuthCredentials(["authenticated"]), SimpleUser(username)
return AuthCredentials(["authenticated"]), CurrentUser(user.name, user.id)
# enforcer = casbin.Enforcer('rbac_model.conf', 'rbac_policy.csv')
# app.add_middleware(CasbinMiddleware, enforcer=enforcer)
# app.add_middleware(AuthenticationMiddleware, backend=BasicAuth())
app.add_middleware(CasbinMiddleware, enforcer=casbin_enforcer)
app.add_middleware(AuthenticationMiddleware, backend=BasicAuth())
from api.api_v1.api import api_router
app.include_router(api_router, prefix=settings.API_V1_STR)
if __name__ == '__main__':
uvicorn.run(app='main:app', host="0.0.0.0", port=8889, reload=True, debug=True)
uvicorn.run(app='main2:app', host="0.0.0.0", port=8889, reload=True, debug=True)

View File

@ -13,7 +13,7 @@ from starlette.middleware.authentication import AuthenticationMiddleware
from fastapi_authz import CasbinMiddleware
from db import connect_to_mongo, close_mongo_connection, get_database
from utils import Adapter
from utils import *
app = FastAPI(title=settings.PROJECT_NAME)
@ -64,17 +64,12 @@ class BasicAuth(AuthenticationBackend):
return AuthCredentials(["authenticated"]), CurrentUser(user.name, user.id)
enforcer = casbin.Enforcer('rbac_model.conf', Adapter(settings.DATABASE_URI,settings.MDB_DB))
app.add_middleware(CasbinMiddleware, enforcer=enforcer)
app.add_middleware(CasbinMiddleware, enforcer=casbin_enforcer)
app.add_middleware(AuthenticationMiddleware, backend=BasicAuth())
from api.api_v1.api import api_router
app.include_router(api_router, prefix=settings.API_V1_STR)
if __name__ == '__main__':
uvicorn.run(app='main2:app', host="0.0.0.0", port=8899, reload=True, debug=True)

View File

@ -3,19 +3,19 @@ import casbin
from core.config import settings
from pymongo import MongoClient
from utils import Adapter
from utils import *
client = MongoClient(settings.DATABASE_URI)
db = client[settings.MDB_DB]
collection = db[settings.CASBIN_COLL]
adapter = Adapter(settings.DATABASE_URI, settings.MDB_DB)
enforcer = casbin.Enforcer('rbac_model.conf', adapter)
model = enforcer.get_model()
model.add_policy('g', 'g', ['root', 'superAdmin', ])
model.add_policy('g', 'g', ['legu', 'admin'])
adapter.save_policy(model)
res = enforcer.enforce('alice', 'data1', 'read')
# casbin_model.add_policy('g', 'g', ['root', 'superAdmin', ])
# casbin_model.add_policy('g', 'g', ['legu', 'admin'])
# casbin_enforcer.add_role_for_user('user', 'role')
res = casbin_enforcer.delete_user('user')
print(res)
casbin_adapter.save_policy(casbin_model)

View File

@ -4,4 +4,5 @@ from .project import *
from .folder import *
from .space import *
from .dashboard import *
from .report import *
from .report import *
from .authotity import *

35
schemas/authotity.py Normal file
View File

@ -0,0 +1,35 @@
from enum import Enum
from typing import List
from pydantic import BaseModel
class Ptype(str, Enum):
p = 'p'
g = 'g'
class CasbinRoleCreate(BaseModel):
role_name: str
role_api: List
class CasbinDB(BaseModel):
ptype: Ptype
v0: str
v1: str
v2: str
class AccountCreate(BaseModel):
name: str
role_name: str
class AccountDeleteUser(BaseModel):
name: str
class AccountSetRole(BaseModel):
name: str
role_name: str

View File

@ -12,10 +12,23 @@ class SpaceBase(BaseModel):
name: str = None
class Authority(str, Enum):
rw = 'rw'
r = 'r'
class Member(DBBase):
name: str
authority: Authority
# 解析请求json 创建项目
class SpaceCreate(SpaceBase):
name: str
project_id: str
members: List[Member] = []
is_all_member: bool = False
authority: Authority = 'r'
class SpaceDelete(DBBase):
@ -28,6 +41,5 @@ class SpaceDB(DBBase):
name: str
user_id: str
project_id: str
rw_members: List[str] = []
r_members: List[str] = []
members: List[Member] = []
create_date: datetime = datetime.now()

View File

@ -21,8 +21,8 @@ class UserLogin(BaseModel):
class UserCreate(UserBase):
email: EmailStr
password: str
name: str
# ****************************************************************************
@ -30,13 +30,10 @@ class UserCreate(UserBase):
class UserDB(DBBase):
email: EmailStr
is_superuser: bool
email: EmailStr = None
is_superuser: bool = False
name: str
class UserDBRW(UserDB):
hashed_password: str

View File

@ -1 +1 @@
from .adapter import Adapter
from .adapter import *

View File

@ -2,6 +2,10 @@ import casbin
from casbin import persist
from pymongo import MongoClient
from core.config import settings
__all__ = 'casbin_adapter', 'casbin_enforcer', 'casbin_model'
class CasbinRule:
'''
@ -82,7 +86,7 @@ class Adapter(persist.Adapter):
line.v4 = rule[4]
if len(rule) > 5:
line.v5 = rule[5]
self._collection.update_one(line.dict(), {'$set':line.dict()}, upsert=True)
self._collection.update_one(line.dict(), {'$set': line.dict()}, upsert=True)
def save_policy(self, model):
'''
@ -110,3 +114,8 @@ class Adapter(persist.Adapter):
delete policy rules for matching filters from mongodb
"""
pass
casbin_adapter = Adapter(settings.DATABASE_URI, settings.MDB_DB)
casbin_enforcer = casbin.Enforcer('rbac_model.conf', casbin_adapter)
casbin_model = casbin_enforcer.get_model()

View File

@ -1,3 +1,4 @@
from casbin import persist