import time import traceback import asyncio from bson import ObjectId from fastapi import APIRouter, Depends, BackgroundTasks from api.task import create_scan_task, delete_asset from api.users import verify_token from motor.motor_asyncio import AsyncIOMotorCursor from core.config import Project_List from core.db import get_mongo_db from core.redis_handler import refresh_config, get_redis_pool from loguru import logger from core.util import * from core.apscheduler_handler import scheduler router = APIRouter() @router.post("/project/data") async def get_projects_data(request_data: dict, db=Depends(get_mongo_db), _: dict = Depends(verify_token), background_tasks: BackgroundTasks = BackgroundTasks()): background_tasks.add_task(update_project_count) search_query = request_data.get("search", "") page_index = request_data.get("pageIndex", 1) page_size = request_data.get("pageSize", 10) query = { "$or": [ {"name": {"$regex": search_query, "$options": "i"}}, {"root_domain": {"$regex": search_query, "$options": "i"}} ] } if search_query else {} # 获取标签统计信息 tag_result = await db.project.aggregate([ {"$group": {"_id": "$tag", "count": {"$sum": 1}}}, {"$sort": {"count": -1}} ]).to_list(None) tag_num = {tag["_id"]: tag["count"] for tag in tag_result} all_num = sum(tag_num.values()) tag_num["All"] = all_num result_list = {} async def fetch_projects(tag, tag_query): cursor = db.project.find(tag_query, { "_id": 0, "id": {"$toString": "$_id"}, "name": 1, "logo": 1, "AssetCount": 1, "tag": 1 }).sort("AssetCount", -1).skip((page_index - 1) * page_size).limit(page_size) results = await cursor.to_list(length=None) for result in results: result["AssetCount"] = result.get("AssetCount", 0) return results fetch_tasks = [] for tag in tag_num: if tag != "All": tag_query = {"$and": [query, {"tag": tag}]} else: tag_query = query fetch_tasks.append(fetch_projects(tag, tag_query)) fetch_results = await asyncio.gather(*fetch_tasks) for tag, results in zip(tag_num, fetch_results): result_list[tag] = results return { "code": 200, "data": { "result": result_list, "tag": tag_num } } @router.get("/project/all") async def get_projects_all(db=Depends(get_mongo_db), _: dict = Depends(verify_token)): try: pipeline = [ { "$group": { "_id": "$tag", # 根据 tag 字段分组 "children": {"$push": {"value": {"$toString": "$_id"}, "label": "$name"}} # 将每个文档的 _id 和 name 放入 children 集合中 } }, { "$project": { "_id": 0, "label": "$_id", "value": {"$literal": ""}, "children": 1 } } ] result = await db['project'].aggregate(pipeline).to_list(None) return { "code": 200, "data": { 'list': result } } except Exception as e: logger.error(str(e)) logger.error(traceback.format_exc()) return {"message": "error","code":500} async def update_project_count(): async for db in get_mongo_db(): cursor = db.project.find({}, {"_id": 0, "id": {"$toString": "$_id"}}) results = await cursor.to_list(length=None) async def update_count(id): query = {"project": {"$eq": id}} total_count = await db.asset.count_documents(query) if total_count != 0: update_document = { "$set": { "AssetCount": total_count } } await db.project.update_one({"_id": ObjectId(id)}, update_document) fetch_tasks = [update_count(r['id']) for r in results] await asyncio.gather(*fetch_tasks) @router.post("/project/content") async def get_project_content(request_data: dict, db=Depends(get_mongo_db), _: dict = Depends(verify_token)): project_id = request_data.get("id") if not project_id: return {"message": "ID is missing in the request data", "code": 400} query = {"_id": ObjectId(project_id)} doc = await db.project.find_one(query) if not doc: return {"message": "Content not found for the provided ID", "code": 404} project_target_data = await db.ProjectTargetData.find_one({"id": project_id}) result = { "name": doc.get("name", ""), "tag": doc.get("tag", ""), "target": project_target_data.get("target", ""), "node": doc.get("node", []), "logo": doc.get("logo", ""), "subdomainScan": doc.get("subdomainScan", False), "subdomainConfig": doc.get("subdomainConfig", []), "urlScan": doc.get("urlScan", False), "sensitiveInfoScan": doc.get("sensitiveInfoScan", False), "pageMonitoring": doc.get("pageMonitoring", ""), "crawlerScan": doc.get("crawlerScan", False), "vulScan": doc.get("vulScan", False), "vulList": doc.get("vulList", []), "portScan": doc.get("portScan"), "ports": doc.get("ports"), "waybackurl": doc.get("waybackurl"), "dirScan": doc.get("dirScan"), "scheduledTasks": doc.get("scheduledTasks"), "hour": doc.get("hour"), "allNode": doc.get("allNode", False), "duplicates": doc.get("duplicates") } return {"code": 200, "data": result} @router.post("/project/add") async def add_project_rule(request_data: dict, db=Depends(get_mongo_db), _: dict = Depends(verify_token), background_tasks: BackgroundTasks = BackgroundTasks()): try: # Extract values from request data name = request_data.get("name") target = request_data.get("target") runNow = request_data.get("runNow") del request_data["runNow"] scheduledTasks = request_data.get("scheduledTasks", False) hour = request_data.get("hour", 1) t_list = [] tmsg = '' root_domains = [] for t in target.split('\n'): if t not in t_list: targetTmp = t.replace('http://', "").replace('https://', "").replace("\n", "").replace("\r", "").strip() if targetTmp != "": root_domain = get_root_domain(targetTmp) if root_domain not in root_domains: root_domains.append(root_domain) tmsg += targetTmp + '\n' # Create a new SensitiveRule document tmsg = tmsg.strip().strip("\n") request_data["root_domains"] = root_domains del request_data['target'] if "All Poc" in request_data['vulList']: request_data['vulList'] = ["All Poc"] cursor = db.project.find({"name": {"$eq": name}}, {"_id": 1}) results = await cursor.to_list(length=None) if len(results) != 0: return {"code": 400, "message": "name already exists"} # Insert the new document into the SensitiveRule collection result = await db.project.insert_one(request_data) # Check if the insertion was successful if result.inserted_id: await db.ProjectTargetData.insert_one({"id": str(result.inserted_id), "target": tmsg}) if scheduledTasks: scheduler.add_job(scheduler_project, 'interval', hours=hour, args=[str(result.inserted_id)], id=str(result.inserted_id), jobstore='mongo') next_time = scheduler.get_job(str(result.inserted_id)).next_run_time formatted_time = next_time.strftime("%Y-%m-%d %H:%M:%S") db.ScheduledTasks.insert_one( {"id": str(result.inserted_id), "name": name, 'hour': hour, 'type': 'Project', 'state': True, 'lastTime': get_now_time(), 'nextTime': formatted_time, 'runner_id': str(result.inserted_id)}) if runNow: await scheduler_project(str(result.inserted_id)) background_tasks.add_task(update_project, root_domains, str(result.inserted_id)) await refresh_config('all', 'project') Project_List[name] = str(result.inserted_id) return {"code": 200, "message": "Project added successfully"} else: return {"code": 400, "message": "Failed to add Project"} except Exception as e: logger.error(str(e)) # Handle exceptions as needed return {"message": "error", "code": 500} @router.post("/project/delete") async def delete_project_rules(request_data: dict, db=Depends(get_mongo_db), _: dict = Depends(verify_token), background_tasks: BackgroundTasks = BackgroundTasks()): try: pro_ids = request_data.get("ids", []) delA = request_data.get("delA", False) if delA: background_tasks.add_task(delete_asset, pro_ids, True) obj_ids = [ObjectId(poc_id) for poc_id in pro_ids] result = await db.project.delete_many({"_id": {"$in": obj_ids}}) await db.ProjectTargetData.delete_many({"id": {"$in": pro_ids}}) # Check if the deletion was successful if result.deleted_count > 0: for pro_id in pro_ids: job = scheduler.get_job(pro_id) if job: scheduler.remove_job(pro_id) background_tasks.add_task(delete_asset_project_handler, pro_id) for project_id in Project_List: if pro_id == Project_List[project_id]: del Project_List[project_id] break await db.ScheduledTasks.delete_many({"id": {"$in": pro_ids}}) return {"code": 200, "message": "Project deleted successfully"} else: return {"code": 404, "message": "Project not found"} except Exception as e: logger.error(str(e)) # Handle exceptions as needed return {"message": "error", "code": 500} @router.post("/project/update") async def update_project_data(request_data: dict, db=Depends(get_mongo_db), _: dict = Depends(verify_token), background_tasks: BackgroundTasks = BackgroundTasks()): try: # Get the ID from the request data pro_id = request_data.get("id") hour = request_data.get("hour") runNow = request_data.get("runNow") del request_data["runNow"] # Check if ID is provided if not pro_id: return {"message": "ID is missing in the request data", "code": 400} query = {"id": pro_id} doc = await db.ScheduledTasks.find_one(query) newScheduledTasks = request_data.get("scheduledTasks") if doc: oldScheduledTasks = doc["state"] old_hour = doc["hour"] if oldScheduledTasks != newScheduledTasks: if newScheduledTasks: scheduler.add_job(scheduler_project, 'interval', hours=hour, args=[pro_id], id=str(pro_id), jobstore='mongo') await db.ScheduledTasks.update_one({"id": pro_id}, {"$set": {'state': True}}) else: job = scheduler.get_job(pro_id) if job is not None: scheduler.remove_job(pro_id) await db.ScheduledTasks.update_one({"id": pro_id}, {"$set": {'state': False}}) else: if newScheduledTasks: if hour != old_hour: job = scheduler.get_job(pro_id) if job is not None: scheduler.remove_job(pro_id) scheduler.add_job(scheduler_project, 'interval', hours=hour, args=[pro_id], id=str(pro_id), jobstore='mongo') else: if newScheduledTasks: scheduler.add_job(scheduler_project, 'interval', hours=hour, args=[str(pro_id)], id=str(pro_id), jobstore='mongo') next_time = scheduler.get_job(str(pro_id)).next_run_time formatted_time = next_time.strftime("%Y-%m-%d %H:%M:%S") db.ScheduledTasks.insert_one( {"id": str(pro_id), "name": request_data['name'], 'hour': hour, 'type': 'Project', 'state': True, 'lastTime': get_now_time(), 'nextTime': formatted_time}) result = await db.project.find_one({"_id": ObjectId(pro_id)}) project_target_data = await db.ProjectTargetData.find_one({"id": pro_id}) old_targets = project_target_data['target'] old_name = result['name'] new_name = request_data['name'] new_targets = request_data['target'] if old_targets != new_targets.strip().strip('\n'): new_root_domain = [] update_document = { "$set": { "target": new_targets.strip().strip('\n') } } for n_t in new_targets.strip().strip('\n').split('\n'): t_root_domain = get_root_domain(n_t) if t_root_domain not in new_root_domain: new_root_domain.append(t_root_domain) request_data["root_domains"] = new_root_domain await db.ProjectTargetData.update_one({"id": pro_id}, update_document) background_tasks.add_task(update_project, new_root_domain, pro_id, True) if old_name != new_name: del Project_List[old_name] Project_List[new_name] = pro_id await db.ScheduledTasks.update_one({"id": pro_id}, {"$set": {'name': new_name}}) request_data.pop("id") del request_data['target'] update_document = { "$set": request_data } result = await db.project.update_one({"_id": ObjectId(pro_id)}, update_document) # Check if the update was successful if result: if runNow: await scheduler_project(str(pro_id)) return {"message": "Task updated successfully", "code": 200} else: return {"message": "Failed to update data", "code": 404} except Exception as e: logger.error(str(e)) logger.error(traceback.format_exc()) # Handle exceptions as needed return {"message": "error", "code": 500} async def update_project(root_domain, project_id, change=False): asset_collection_list = { 'asset': ["url", "host", "ip"], 'subdomain': ["host", "ip"], 'DirScanResult': ["url"], 'vulnerability': ["url"], 'SubdoaminTakerResult': ["input"], 'PageMonitoring': ["url"], 'SensitiveResult': ["url"], 'UrlScan': ["input"], 'crawler': ["url"]} async for db in get_mongo_db(): for a in asset_collection_list: if change: await asset_update_project(root_domain, asset_collection_list[a], a, db, project_id) else: await asset_add_project(root_domain, asset_collection_list[a], a, db, project_id) async def asset_add_project(root_domain, db_key, doc_name, db, project_id): regex_patterns = [f".*{domain}.*" for domain in root_domain] pattern = "|".join(regex_patterns) # 构建查询条件 query = { "$and": [ { "$or": [ {key: {"$regex": pattern, "$options": "i"}} for key in db_key ] }, {"project": {"$exists": True, "$eq": ""}} ] } update_query = { "$set": { "project": project_id } } result = await db[doc_name].update_many(query, update_query) # 打印更新的文档数量 logger.info(f"Updated {doc_name} {result.modified_count} documents") async def asset_update_project(root_domain, db_key, doc_name, db, project_id): regex_patterns = [f".*{domain}.*" for domain in root_domain] pattern = "|".join(regex_patterns) # 构建查询条件 query = { "$and": [ {"project": project_id}, { "$nor": [ {key: {"$regex": pattern, "$options": "i"}} for key in db_key ] } ] } update_query = { "$set": { "project": "" } } result = await db[doc_name].update_many(query, update_query) # 打印更新的文档数量 logger.info(f"Updated {doc_name} {result.modified_count} documents to null ") await asset_add_project(root_domain, db_key, doc_name, db, project_id) async def delete_asset_project(db, collection, project_id): try: query = {"project": project_id} cursor = db[collection].find(query) async for document in cursor: await db[collection].update_one({"_id": document["_id"]}, {"$set": {"project": ""}}) except Exception as e: logger.error(f"delete_asset_project error:{e}") async def delete_asset_project_handler(project_id): async for db in get_mongo_db(): asset_collection_list = ['asset', 'subdomain', 'DirScanResult', 'vulnerability', 'SubdoaminTakerResult', 'PageMonitoring', 'SensitiveResult', 'UrlScan', 'crawler'] for c in asset_collection_list: await delete_asset_project(db, c, project_id) async def scheduler_project(id): logger.info(f"Scheduler project {id}") async for db in get_mongo_db(): async for redis in get_redis_pool(): job = scheduler.get_job(id) task_id = generate_random_string(15) if job: next_time = job.next_run_time formatted_time = next_time.strftime("%Y-%m-%d %H:%M:%S") doc = await db.ScheduledTasks.find_one({"id": id}) run_id_last = doc.get("runner_id", "") if run_id_last != "": progresskeys = await redis.keys(f"TaskInfo:progress:{run_id_last}:*") for pgk in progresskeys: await redis.delete(pgk) update_document = { "$set": { "lastTime": get_now_time(), "nextTime": formatted_time, "runner_id": task_id } } await db.ScheduledTasks.update_one({"id": id}, update_document) query = {"_id": ObjectId(id)} doc = await db.project.find_one(query) targetList = [] target_data = await db.ProjectTargetData.find_one({"id": id}) for t in target_data.get('target', '').split("\n"): t.replace("http://", "").replace("https://", "") t = t.strip("\n").strip("\r").strip() if t != "" and t not in targetList: targetList.append(t) await create_scan_task(doc, task_id, targetList, redis)