Spaces:
Sleeping
Sleeping
File size: 9,189 Bytes
faaaf38 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | """
Deepfake Hunter - Zoom Webhook Handler
Handles Zoom meeting webhooks and processes video streams for deepfake detection.
Author: Deepfake Hunter Team
License: MIT
"""
import os
import json
import hmac
import hashlib
from typing import Dict, Any, Optional
from datetime import datetime
from fastapi import FastAPI, Request, HTTPException, Header
from pydantic import BaseModel
import uvicorn
from loguru import logger
# Initialize FastAPI
app = FastAPI(title="Deepfake Hunter - Zoom Webhook Handler")
# Configuration
ZOOM_WEBHOOK_SECRET = os.getenv("ZOOM_WEBHOOK_SECRET", "your-zoom-webhook-secret")
ZOOM_VERIFICATION_TOKEN = os.getenv("ZOOM_VERIFICATION_TOKEN", "your-zoom-verification-token")
API_BASE_URL = os.getenv("DEEPFAKE_API_URL", "http://localhost:8001")
API_KEY = os.getenv("DEEPFAKE_API_KEY", "your-secret-api-key")
# Store active meeting sessions
active_meetings = {}
class ZoomWebhookEvent(BaseModel):
"""Zoom webhook event model"""
event: str
payload: Dict[str, Any]
def verify_zoom_webhook(
request_body: bytes,
signature: str,
timestamp: str
) -> bool:
"""
Verify Zoom webhook signature
Args:
request_body: Raw request body
signature: X-Zm-Signature header
timestamp: X-Zm-Request-Timestamp header
Returns:
True if signature is valid
"""
# Create message to hash
message = f"v0:{timestamp}:".encode('utf-8') + request_body
# Calculate expected signature
expected_signature = hmac.new(
ZOOM_WEBHOOK_SECRET.encode('utf-8'),
message,
hashlib.sha256
).hexdigest()
expected_signature = f"v0={expected_signature}"
return hmac.compare_digest(expected_signature, signature)
@app.get("/health")
async def health_check():
"""Health check endpoint"""
return {
"status": "healthy",
"service": "Deepfake Hunter Zoom Webhook Handler",
"active_meetings": len(active_meetings)
}
@app.post("/zoom/webhook")
async def handle_zoom_webhook(
request: Request,
x_zm_signature: Optional[str] = Header(None),
x_zm_request_timestamp: Optional[str] = Header(None)
):
"""
Handle Zoom webhook events
Processes meeting events and triggers deepfake analysis for video streams.
"""
try:
# Get raw body for signature verification
body = await request.body()
# Verify webhook signature (in production)
if x_zm_signature and x_zm_request_timestamp:
if not verify_zoom_webhook(body, x_zm_signature, x_zm_request_timestamp):
logger.warning("Invalid webhook signature")
raise HTTPException(status_code=401, detail="Invalid signature")
# Parse event
event_data = json.loads(body)
event_type = event_data.get("event")
payload = event_data.get("payload", {})
logger.info(f"Received Zoom event: {event_type}")
# Handle different event types
if event_type == "endpoint.url_validation":
# Respond to URL validation
return handle_url_validation(payload)
elif event_type == "meeting.started":
return await handle_meeting_started(payload)
elif event_type == "meeting.participant_joined":
return await handle_participant_joined(payload)
elif event_type == "meeting.ended":
return await handle_meeting_ended(payload)
else:
logger.info(f"Unhandled event type: {event_type}")
return {"status": "ok"}
except Exception as e:
logger.error(f"Webhook handler error: {e}")
raise HTTPException(status_code=500, detail=str(e))
def handle_url_validation(payload: Dict[str, Any]) -> Dict[str, str]:
"""
Handle Zoom URL validation challenge
Args:
payload: Event payload with challenge
Returns:
Response with encrypted token
"""
plain_token = payload.get("plainToken")
if not plain_token:
raise HTTPException(status_code=400, detail="Missing plainToken")
# Encrypt token with webhook secret
encrypted_token = hmac.new(
ZOOM_WEBHOOK_SECRET.encode('utf-8'),
plain_token.encode('utf-8'),
hashlib.sha256
).hexdigest()
return {
"plainToken": plain_token,
"encryptedToken": encrypted_token
}
async def handle_meeting_started(payload: Dict[str, Any]) -> Dict[str, str]:
"""
Handle meeting started event
Initializes deepfake detection for the meeting.
"""
meeting_id = payload.get("object", {}).get("id")
meeting_topic = payload.get("object", {}).get("topic", "Unknown")
logger.info(f"Meeting started: {meeting_id} - {meeting_topic}")
# Initialize meeting session
active_meetings[meeting_id] = {
"topic": meeting_topic,
"started_at": datetime.now().isoformat(),
"participants": {},
"alerts": []
}
return {"status": "meeting_monitoring_started"}
async def handle_participant_joined(payload: Dict[str, Any]) -> Dict[str, str]:
"""
Handle participant joined event
Starts deepfake analysis for the participant's video stream.
"""
meeting_id = payload.get("object", {}).get("id")
participant = payload.get("object", {}).get("participant", {})
participant_id = participant.get("id")
participant_name = participant.get("user_name", "Unknown")
logger.info(f"Participant joined: {participant_name} in meeting {meeting_id}")
if meeting_id not in active_meetings:
active_meetings[meeting_id] = {
"participants": {},
"alerts": []
}
# Initialize participant tracking
active_meetings[meeting_id]["participants"][participant_id] = {
"name": participant_name,
"joined_at": datetime.now().isoformat(),
"deepfake_scores": [],
"is_suspicious": False,
"analysis_count": 0
}
# Note: Actual video stream analysis would be triggered here
# For now, this is a placeholder
# In production, you would:
# 1. Subscribe to participant's video stream
# 2. Periodically capture frames
# 3. Send frames to deepfake detection API
# 4. Update participant's deepfake_scores
# 5. Alert host if scores consistently high
logger.info(f"Started monitoring participant: {participant_name}")
return {"status": "participant_monitoring_started"}
async def handle_meeting_ended(payload: Dict[str, Any]) -> Dict[str, str]:
"""
Handle meeting ended event
Cleanup and generate final report.
"""
meeting_id = payload.get("object", {}).get("id")
logger.info(f"Meeting ended: {meeting_id}")
if meeting_id in active_meetings:
meeting_data = active_meetings[meeting_id]
# Generate summary
total_participants = len(meeting_data["participants"])
suspicious_count = sum(
1 for p in meeting_data["participants"].values()
if p.get("is_suspicious", False)
)
logger.info(
f"Meeting summary - Total participants: {total_participants}, "
f"Suspicious: {suspicious_count}"
)
# Cleanup
del active_meetings[meeting_id]
return {"status": "meeting_monitoring_ended"}
@app.post("/zoom/analyze_participant")
async def analyze_participant(
meeting_id: str,
participant_id: str,
frame_data: str
):
"""
Analyze participant video frame
Args:
meeting_id: Zoom meeting ID
participant_id: Participant ID
frame_data: Base64-encoded frame data
Returns:
Analysis results
"""
# This endpoint would be called periodically to analyze participant frames
# Implementation would:
# 1. Decode frame data
# 2. Send to deepfake detection API
# 3. Update participant scores
# 4. Generate alerts if needed
logger.info(f"Analyzing participant {participant_id} in meeting {meeting_id}")
# Placeholder response
return {
"meeting_id": meeting_id,
"participant_id": participant_id,
"is_deepfake": False,
"confidence": 0.0,
"timestamp": datetime.now().isoformat()
}
@app.get("/zoom/meetings")
async def get_active_meetings():
"""Get list of active meetings being monitored"""
return {
"active_meetings": len(active_meetings),
"meetings": {
meeting_id: {
"topic": data.get("topic", "Unknown"),
"participants": len(data.get("participants", {})),
"alerts": len(data.get("alerts", []))
}
for meeting_id, data in active_meetings.items()
}
}
@app.get("/zoom/meeting/{meeting_id}")
async def get_meeting_details(meeting_id: str):
"""Get details for specific meeting"""
if meeting_id not in active_meetings:
raise HTTPException(status_code=404, detail="Meeting not found")
return active_meetings[meeting_id]
if __name__ == "__main__":
logger.info("Starting Deepfake Hunter Zoom Webhook Handler...")
uvicorn.run(
app,
host="0.0.0.0",
port=8002,
log_level="info"
)
|