diff --git a/api/api_v1/api.py b/api/api_v1/api.py index a86a6dd..d00bbc4 100644 --- a/api/api_v1/api.py +++ b/api/api_v1/api.py @@ -8,6 +8,7 @@ from .endpoints import report from .endpoints import authority from .endpoints import table_struct from .endpoints import query +from .endpoints import data_auth api_router = APIRouter() @@ -19,6 +20,7 @@ api_router.include_router(dashboard.router, tags=["看板接口"], prefix='/dash api_router.include_router(report.router, tags=["报表接口"], prefix='/report') api_router.include_router(authority.router, tags=["权限管理接口"], prefix='/authority') +api_router.include_router(data_auth.router, tags=["数据权限"], prefix='/data_auth') api_router.include_router(table_struct.router, tags=["表结构"], prefix='/table_struct') diff --git a/api/api_v1/endpoints/authority.py b/api/api_v1/endpoints/authority.py index a1c5940..4aba895 100644 --- a/api/api_v1/endpoints/authority.py +++ b/api/api_v1/endpoints/authority.py @@ -7,6 +7,7 @@ from core.security import get_password_hash from db import get_database from api import deps +from db.ckdb import CKDrive, get_ck_db from utils import casbin_enforcer router = APIRouter() @@ -32,6 +33,61 @@ async def api_list(request: Request, return schemas.Msg(code=0, msg='ok', data=res) +@router.post('/set_data_auth') +async def set_data_auth(request: Request, + data_id: schemas.DataAuthSet, + game: str = Depends(deps.get_game_project), + db: AsyncIOMotorDatabase = Depends(get_database), + current_user: schemas.UserDB = Depends(deps.get_current_user) + ) -> schemas.Msg: + """设置用户数据权限""" + await crud.authority.set_data_auth(db, data_id, game=game) + return schemas.Msg(code=0, msg='ok', data=data_id) + + +@router.get('/get_user_data_auth') +async def get_user_data_auth(request: Request, + game: str = Depends(deps.get_game_project), + db: AsyncIOMotorDatabase = Depends(get_database), + ck: CKDrive = Depends(get_ck_db), + current_user: schemas.UserDB = Depends(deps.get_current_user) + ) -> schemas.Msg: + """获取当前用户数据权限""" + + data_auth = await crud.authority.get_data_auth(db, username=request.user.name, game=game) + if not data_auth: + values = await ck.distinct(game, 'event', '#event_name') + return schemas.Msg(code=0, msg='ok', data={ + 'data': values, + 'game': game, + 'name': '全部事件' + }) + data_auth_id = data_auth['data_auth_id'] + data = await crud.data_auth.get(data_auth_id) + return schemas.Msg(code=0, msg='ok', data=data) + + +@router.get('/get_users_data_auth') +async def get_users_data_auth(request: Request, + game: str = Depends(deps.get_game_project), + db: AsyncIOMotorDatabase = Depends(get_database), + ck: CKDrive = Depends(get_ck_db), + current_user: schemas.UserDB = Depends(deps.get_current_user) + ) -> schemas.Msg: + """获取当前项目所有用户数据权限""" + # data_auth = await crud.authority.get_data_auth(db, username=request.user.name, game=game) + # if not data_auth: + # values = await ck.distinct(game, 'event', '#event_name') + # return schemas.Msg(code=0, msg='ok', data={ + # 'data': values, + # 'game': game, + # 'name': '全部事件' + # }) + # data_auth_id = data_auth['data_auth_id'] + # data = await crud.data_auth.get(data_auth_id) + return schemas.Msg(code=0, msg='ok') + + @router.post("/add_role") async def add_role(request: Request, data_in: schemas.CasbinRoleCreate, @@ -40,6 +96,10 @@ async def add_role(request: Request, current_user: schemas.UserDB = Depends(deps.get_current_user) ) -> schemas.Msg: """创建角色""" + + # 不允许角色名和用户名一样 + if await crud.user.get_by_user(db, name=data_in.role_name): + return schemas.Msg(code=-1, msg='请改个名字') role_dom = game api_dict = dict() for r in request.app.routes: @@ -66,6 +126,11 @@ async def add_sys_role(request: Request, ) -> schemas.Msg: """创建系统角色""" api_dict = dict() + + # 不允许角色名和用户名一样 + if await crud.user.get_by_user(db, name=data_in.role_name): + return schemas.Msg(code=-1, msg='请改个名字') + for r in request.app.routes: api_dict[r.path] = r.description if hasattr(r, 'description') else r.name # 角色有的接口权限 @@ -89,6 +154,9 @@ async def add_account(request: Request, db: AsyncIOMotorDatabase = Depends(get_database), current_user: schemas.UserDB = Depends(deps.get_current_user) ) -> schemas.Msg: + roles = casbin_enforcer.get_all_roles() + if set(data_in.accounts) & set(roles): + return schemas.Msg(code=-1, msg='已存在', data=list(set(data_in.accounts) & set(roles))) """创建账号 并设置角色""" for item in data_in.accounts: account = schemas.UserCreate(name=item.username, password=settings.DEFAULT_PASSWORD) @@ -103,20 +171,6 @@ async def add_account(request: Request, return schemas.Msg(code=0, msg='ok') -@router.get("/data_authority") -async def data_authority(request: Request, - db: AsyncIOMotorDatabase = Depends(get_database), - game: str = Depends(deps.get_game_project), - current_user: schemas.UserDB = Depends(deps.get_current_user) - ) -> schemas.Msg: - """获取数据权限""" - - # todo 这是假数据 - data = [{'title': '全部事件', 'check_event_num': 100, 'total_event_num': 100, 'update_time': '2021-05-12 18:49:19'}] - - return schemas.Msg(code=0, msg='ok', data=data) - - @router.get("/all_role") async def all_role(request: Request, db: AsyncIOMotorDatabase = Depends(get_database), @@ -125,18 +179,30 @@ async def all_role(request: Request, ) -> schemas.Msg: """获取所有角色""" + app = request.app + api_data = {} + for r in app.routes: + title = r.tags[0] if hasattr(r, 'description') else None + if not title: + continue + api_data[r.path] = { + 'api': r.path, + 'title': title, + 'name': r.description if hasattr(r, 'description') else r.name + } + """获取域内所有角色""" roles = await crud.authority.find_many(db, role_name={'$exists': 1}, game=game) - dom_data = [{'role': item['v1'], 'name': item['role_name']} for item in roles] + dom_data = [{'role': item['v1'], 'name': item['role_name'], 'id': str(item['_id'])} for item in roles] for item in dom_data: - q = await crud.authority.get_role_dom_authority(db, item['role'], game) + q = await crud.authority.get_role_dom_authority(db, item['role'], game, api_data) item['authority'] = q # 获取系统角色 roles = await crud.authority.find_many(db, role_name={'$exists': 1}, game='*') - sys_data = [{'role': item['v1'], 'name': item['role_name']} for item in roles] + sys_data = [{'role': item['v1'], 'name': item['role_name'], 'id': str(item['_id'])} for item in roles] for item in sys_data: - q = await crud.authority.get_role_dom_authority(db, item['role'], dom='*') + q = await crud.authority.get_role_dom_authority(db, item['role'], dom='*', api_data=api_data) item['authority'] = q data = { diff --git a/api/api_v1/endpoints/data_auth.py b/api/api_v1/endpoints/data_auth.py new file mode 100644 index 0000000..9cb5424 --- /dev/null +++ b/api/api_v1/endpoints/data_auth.py @@ -0,0 +1,39 @@ +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.post('/add_data_auth') +async def add_data_auth(request: Request, + data_id: schemas.DataAuthCreate, + game: str = Depends(deps.get_game_project), + db: AsyncIOMotorDatabase = Depends(get_database), + current_user: schemas.UserDB = Depends(deps.get_current_user) + ) -> schemas.Msg: + """创建数据权限""" + await crud.data_auth.create(db, data_id, game) + return schemas.Msg(code=0, msg='ok', data=data_id) + + +@router.get("/data_auth") +async def data_authority(request: Request, + db: AsyncIOMotorDatabase = Depends(get_database), + game: str = Depends(deps.get_game_project), + current_user: schemas.UserDB = Depends(deps.get_current_user) + ) -> schemas.Msg: + """获取前项目数据权限""" + + data = await crud.data_auth.get_game_data_auth(db, game) + for item in data: + item['id'] = str(item['_id']) + del item['_id'] + return schemas.Msg(code=0, msg='ok', data=data) diff --git a/api/api_v1/endpoints/folder.py b/api/api_v1/endpoints/folder.py index 90d8f17..036ab31 100644 --- a/api/api_v1/endpoints/folder.py +++ b/api/api_v1/endpoints/folder.py @@ -20,7 +20,6 @@ async def create( await crud.folder.create(db, data_in, user_id=current_user.id) except pymongo.errors.DuplicateKeyError: return schemas.Msg(code=-1, msg='文件夹已存在', data='文件夹已存在') - # todo 建默认文件夹 return schemas.Msg(code=0, msg='ok', data='创建成功') diff --git a/api/api_v1/endpoints/project.py b/api/api_v1/endpoints/project.py index a695f83..09ecd12 100644 --- a/api/api_v1/endpoints/project.py +++ b/api/api_v1/endpoints/project.py @@ -15,16 +15,35 @@ router = APIRouter() @router.post("/create") async def create( + request: Request, data_in: ProjectCreate, db: AsyncIOMotorDatabase = Depends(get_database), current_user: schemas.UserDB = Depends(deps.get_current_user) ) -> schemas.Msg: """创建项目""" try: - await crud.project.create(db, data_in, user_id=current_user.id) + res_project = await crud.project.create(db, data_in, current_user=request.user) except pymongo.errors.DuplicateKeyError: return schemas.Msg(code=-1, msg='项目名已存在', data='项目名已存在') - # todo 建默认文件夹 + + folder = schemas.FolderCreate( + name='未分组', + project_id=res_project.inserted_id, + cat='kanban', + pid=res_project.inserted_id, + ) + await crud.folder.create(db, folder, user_id=current_user.id) + folder = schemas.FolderCreate( + name='共享给我的', + project_id=res_project.inserted_id, + cat='kanban', + pid=res_project.inserted_id, + ) + await crud.folder.create(db, folder, user_id=current_user.id) + + # 创建全部数据权限 + data_auth = schemas.DataAuthCreate(name='全部', data=['*']) + await crud.data_auth.create(db, data_auth, data_in.game) # 新建项目管理员权限 role_name = f'{data_in.game}_admin' @@ -43,25 +62,25 @@ async def read_project(request: Request, current_user: schemas.UserDB = Depends(deps.get_current_user) ): """查看自己拥有的项目""" - res = await crud.project.read_project(db, user_id=current_user.id) + res = await crud.project.read_project(db, username=request.user.username) + return schemas.Msg(code=0, msg='ok', data=res) -@router.post("/detail") +@router.get("/detail") async def read_project(request: Request, game: str, - data_in: schemas.ProjectDetail, db: AsyncIOMotorDatabase = Depends(get_database), ck: CKDrive = Depends(get_ck_db), current_user: schemas.UserDB = Depends(deps.get_current_user) ): """查看项目信息""" - res = await crud.project.read_project(db, user_id=current_user.id, _id=data_in.project_id) + res = await crud.project.read_project(db, username=request.user.username, game=game) if res: res = res[0] event_count = await ck.count(game, 'event') user_count = await ck.count(game, 'user_view') - event_type_count = await ck.distinct_count(game, 'event') + event_type_count = await ck.distinct_count(game, 'event', '#event_name') event_attr_count = await ck.field_count(db=game, tb='event') user_attr_count = await ck.field_count(db=game, tb='user_view') @@ -75,6 +94,7 @@ async def read_project(request: Request, @router.post("/rename") async def rename_project(request: Request, + game: str, data_in: schemas.ProjectRename, db: AsyncIOMotorDatabase = Depends(get_database), current_user: schemas.UserDB = Depends(deps.get_current_user) diff --git a/api/api_v1/endpoints/user.py b/api/api_v1/endpoints/user.py index 7f928d5..46350e6 100644 --- a/api/api_v1/endpoints/user.py +++ b/api/api_v1/endpoints/user.py @@ -74,12 +74,26 @@ async def reset_password(request: Request, current_user: schemas.User = Depends(deps.get_current_user) ) -> Any: """ - 修改密码 + 修改其他人密码 """ await crud.user.reset_password(db, data_in) return schemas.Msg(code=0, msg='ok') +@router.post("/reset_my_password") +async def reset_password(request: Request, + game: str, + data_in: schemas.UserRestMyPassword, + db: AsyncIOMotorDatabase = Depends(get_database), + current_user: schemas.User = Depends(deps.get_current_user) + ) -> Any: + """ + 修改自己的密码 + """ + await crud.user.reset_password(db, schemas.UserRestPassword(username=current_user.name, password=data_in.password)) + return schemas.Msg(code=0, msg='ok') + + @router.get("/all_account") async def all_account(page: int = 1, limit: int = 10, db: AsyncIOMotorDatabase = Depends(get_database), current_user: schemas.User = Depends(deps.get_current_user) diff --git a/api/deps.py b/api/deps.py index da1018c..690b03a 100644 --- a/api/deps.py +++ b/api/deps.py @@ -10,6 +10,7 @@ import utils from core import security from core.config import settings from db import get_database +from db.ckdb import CKDrive, get_ck_db reusable_oauth2 = OAuth2PasswordBearer( tokenUrl=f"{settings.API_V1_STR}/user/login" @@ -59,3 +60,4 @@ async def get_game_project(game: str, db: AsyncIOMotorDatabase = Depends(get_dat detail='没有该项目' ) return game + diff --git a/crud/__init__.py b/crud/__init__.py index b12ce8a..c604fe7 100644 --- a/crud/__init__.py +++ b/crud/__init__.py @@ -5,3 +5,4 @@ from .crud_space import space from .crud_dashboard import dashboard from .crud_report import report from .crud_authority import authority +from .crud_data_auth import data_auth diff --git a/crud/base.py b/crud/base.py index 95e2001..e1a229e 100644 --- a/crud/base.py +++ b/crud/base.py @@ -13,8 +13,8 @@ class CRUDBase: async def find_one(self, db, filter=None, *args, **kwargs): return (await db[self.coll_name].find_one(filter, *args, **kwargs)) or dict() - async def read_have(self, db, user_id: str, **kwargs): - where = {'members': user_id} + async def read_have(self, db, v: str, **kwargs): + where = {'members': v} where.update(kwargs) cursor = db[self.coll_name].find(where) return await cursor.to_list(length=9999) diff --git a/crud/crud_authority.py b/crud/crud_authority.py index f14b33e..eec0a09 100644 --- a/crud/crud_authority.py +++ b/crud/crud_authority.py @@ -1,3 +1,5 @@ +from copy import deepcopy + import pymongo from motor.motor_asyncio import AsyncIOMotorDatabase @@ -29,17 +31,42 @@ class CRUDAuthority(CRUDBase): data.update(kwargs) await self.update_one(db, data, {'$set': data}, upsert=True) + # async def get_all_role(self, db): + # # todo 避免与用户同名 + # await self.find_many(db, ptype='p') + async def get_all_dom_role(self, db, dom): pass - async def get_role_dom_authority(self, db, role, dom): - data = await self.find_many(db, v0=role, v1=dom) - res = [] - for item in data: - res.append({ - 'api': item['v2'], - 'api_name': item.get('api_name', item['v2']) - }) + async def get_role_dom_authority(self, db, role, dom, api_data): + selected_api = {item['v2'] for item in await self.find_many(db, v0=role, v1=dom)} + + anonymous_api = {item['v2'] for item in await self.find_many(db, v0='anonymous')} + + api_data = deepcopy(api_data) + + for api, data in api_data.items(): + if api in selected_api or '*' in selected_api or api in anonymous_api: + data['selected'] = True + else: + data['selected'] = False + res = {} + for api, item in api_data.items(): + res.setdefault(item['title'], list()) + res[item['title']].append(item) + return res + + async def set_data_auth(self, db: AsyncIOMotorDatabase, data_in, game): + v0 = data_in.username + v2 = game + data_auth_id = data_in.data + await self.update_one(db, {'ptype': 'g', 'v0': v0, 'v2': v2}, {'$set': {'data_auth_id': data_auth_id}}, + upsert=True) + + async def get_data_auth(self, db, username, game): + v0 = username + v2 = game + res = await self.find_one(db, {'ptype': 'g', 'v0': v0, 'v2': v2}, {'_id': 0, 'data_auth_id': 1}) return res async def create_index(self, db: AsyncIOMotorDatabase): diff --git a/crud/crud_data_auth.py b/crud/crud_data_auth.py new file mode 100644 index 0000000..31d027f --- /dev/null +++ b/crud/crud_data_auth.py @@ -0,0 +1,26 @@ +import pymongo +from motor.motor_asyncio import AsyncIOMotorDatabase + +from crud.base import CRUDBase +from schemas import * + +__all__ = 'data_auth', + + +class CRUDDataAuth(CRUDBase): + + async def create(self, db: AsyncIOMotorDatabase, obj_in: DataAuthCreate, game): + data = obj_in.dict() + data['game'] = game + await self.update_one(db, data, {'$set': data}, upsert=True) + + async def get_game_data_auth(self, db, game): + return await self.find_many(db, game=game) + + async def create_index(self, db: AsyncIOMotorDatabase): + await db[self.coll_name].create_index( + [('game', pymongo.DESCENDING), ('name', pymongo.DESCENDING)], + unique=True) + + +data_auth = CRUDDataAuth('data_auth') diff --git a/crud/crud_folder.py b/crud/crud_folder.py index 72b7c3f..8f9621b 100644 --- a/crud/crud_folder.py +++ b/crud/crud_folder.py @@ -19,7 +19,7 @@ class CRUDFolder(CRUDBase): await db[self.coll_name].insert_one(db_obj.dict(by_alias=True)) async def read_folder(self, db, user_id, project_id, cat): - return await self.read_have(db, user_id=user_id, project_id=project_id, cat=cat) + return await self.read_have(db, user_id, project_id=project_id, cat=cat) async def create_index(self, db: AsyncIOMotorDatabase): await db[self.coll_name].create_index( diff --git a/crud/crud_project.py b/crud/crud_project.py index 54f9f81..1a29c60 100644 --- a/crud/crud_project.py +++ b/crud/crud_project.py @@ -8,15 +8,17 @@ __all__ = 'project', class CRUDProject(CRUDBase): - async def create(self, db: AsyncIOMotorDatabase, obj_in: ProjectCreate, user_id: str): + async def create(self, db: AsyncIOMotorDatabase, obj_in: ProjectCreate, current_user): db_obj = ProjectDB( - **obj_in.dict(), user_id=user_id, members=[user_id], + **obj_in.dict(), user_id=current_user.id, members=[current_user.username], _id=uuid.uuid1().hex ) return await db[self.coll_name].insert_one(db_obj.dict(by_alias=True)) - async def read_project(self, db: AsyncIOMotorDatabase, user_id: str, **kwargs): - return await self.read_have(db, user_id=user_id, **kwargs) + + + async def read_project(self, db: AsyncIOMotorDatabase, username: str, **kwargs): + return await self.read_have(db, username, **kwargs) async def add_members(self, db: AsyncIOMotorDatabase, obj_in: ProjectMember): p = await self.get(db, obj_in.project_id) diff --git a/db/ckdb.py b/db/ckdb.py index 0481ffd..28035d4 100644 --- a/db/ckdb.py +++ b/db/ckdb.py @@ -20,8 +20,8 @@ class CKDrive: res = await self.execute(sql) return res[0]['count'] - async def distinct_count(self, db: str, tb: str): - sql = f'select count(distinct `#event_name`) as `count` from {db}.{tb}' + async def distinct_count(self, db: str, tb: str, field: str): + sql = f'select count(distinct `{field}`) as `count` from {db}.{tb}' res = await self.execute(sql) return res[0]['count'] @@ -30,6 +30,11 @@ class CKDrive: res = await self.execute(sql) return res[0]['count'] + async def distinct(self, db: str, tb: str, field: str): + sql = f'select distinct `{field}` as v from {db}.{tb}' + res = await self.execute(sql) + return res[0]['v'] + ckdb = CKDrive() diff --git a/schemas/__init__.py b/schemas/__init__.py index 7c05566..1a811c6 100644 --- a/schemas/__init__.py +++ b/schemas/__init__.py @@ -7,5 +7,6 @@ from .dashboard import * from .report import * from .authotity import * from .table_struct import * +from .data_auth import * from .sql import * diff --git a/schemas/data_auth.py b/schemas/data_auth.py new file mode 100644 index 0000000..4ddac7c --- /dev/null +++ b/schemas/data_auth.py @@ -0,0 +1,15 @@ +from typing import List + +from pydantic import BaseModel + + +class DataAuthCreate(BaseModel): + name: str + data: List[str] = [] + + + + +class DataAuthSet(BaseModel): + username: str + data_auth_id: str diff --git a/schemas/user.py b/schemas/user.py index b4d95ab..9e93545 100644 --- a/schemas/user.py +++ b/schemas/user.py @@ -30,6 +30,8 @@ class UserRestPassword(BaseModel): username: str = ... password: str = ... +class UserRestMyPassword(BaseModel): + password: str = ... class UserCreate(UserBase): password: str