Security hardening release addressing CodeQL and Dependabot alerts: - Fix stack trace exposure in error responses - Add SSRF protection with DNS resolution checking - Implement proper URL hostname validation (replaces substring matching) - Add centralized path sanitization to prevent path traversal - Fix ReDoS vulnerability in email validation regex - Improve HTML sanitization in validation utilities - Fix capability wildcard matching in auth utilities - Update glob dependency to address CVE - Add CodeQL suppression comments for verified false positives 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
240 lines
7.4 KiB
Python
240 lines
7.4 KiB
Python
"""
|
|
Analytics and Dremio SQL Federation Endpoints
|
|
"""
|
|
from typing import List, Dict, Any, Optional
|
|
from datetime import datetime
|
|
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from pydantic import BaseModel
|
|
|
|
from app.core.database import get_db
|
|
from app.services.dremio_service import DremioService
|
|
from app.core.auth import get_current_user
|
|
from app.models.user import User
|
|
|
|
router = APIRouter(prefix="/api/v1/analytics", tags=["Analytics"])
|
|
|
|
|
|
class TenantDashboardResponse(BaseModel):
|
|
"""Response model for tenant dashboard data"""
|
|
tenant: Dict[str, Any]
|
|
metrics: Dict[str, Any]
|
|
analytics: Dict[str, Any]
|
|
alerts: List[Dict[str, Any]]
|
|
|
|
|
|
class CustomQueryRequest(BaseModel):
|
|
"""Request model for custom analytics queries"""
|
|
query_type: str
|
|
start_date: Optional[datetime] = None
|
|
end_date: Optional[datetime] = None
|
|
|
|
|
|
class DatasetCreationResponse(BaseModel):
|
|
"""Response model for dataset creation"""
|
|
tenant_id: int
|
|
datasets_created: List[str]
|
|
status: str
|
|
|
|
|
|
@router.get("/dashboard/{tenant_id}", response_model=TenantDashboardResponse)
|
|
async def get_tenant_dashboard(
|
|
tenant_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get comprehensive dashboard data for a tenant using Dremio SQL federation"""
|
|
|
|
# Check permissions
|
|
if current_user.user_type != 'super_admin':
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Insufficient permissions to view dashboard"
|
|
)
|
|
|
|
|
|
service = DremioService(db)
|
|
|
|
try:
|
|
dashboard_data = await service.get_tenant_dashboard_data(tenant_id)
|
|
return TenantDashboardResponse(**dashboard_data)
|
|
except ValueError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=str(e)
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to fetch dashboard data: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.post("/query/{tenant_id}")
|
|
async def execute_custom_analytics(
|
|
tenant_id: int,
|
|
request: CustomQueryRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Execute custom analytics queries for a tenant"""
|
|
|
|
# Check permissions (only admins)
|
|
if current_user.user_type != 'super_admin':
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Insufficient permissions for analytics queries"
|
|
)
|
|
|
|
|
|
service = DremioService(db)
|
|
|
|
try:
|
|
results = await service.get_custom_analytics(
|
|
tenant_id=tenant_id,
|
|
query_type=request.query_type,
|
|
start_date=request.start_date,
|
|
end_date=request.end_date
|
|
)
|
|
return {
|
|
"query_type": request.query_type,
|
|
"results": results,
|
|
"count": len(results)
|
|
}
|
|
except ValueError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e)
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Query execution failed: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.post("/datasets/create/{tenant_id}", response_model=DatasetCreationResponse)
|
|
async def create_virtual_datasets(
|
|
tenant_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Create Dremio virtual datasets for tenant analytics"""
|
|
|
|
# Check permissions (only GT admin)
|
|
if current_user.user_type != 'super_admin':
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Only GT admins can create virtual datasets"
|
|
)
|
|
|
|
service = DremioService(db)
|
|
|
|
try:
|
|
result = await service.create_virtual_datasets(tenant_id)
|
|
return DatasetCreationResponse(**result)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to create datasets: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/metrics/performance/{tenant_id}")
|
|
async def get_performance_metrics(
|
|
tenant_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get real-time performance metrics for a tenant"""
|
|
|
|
# Check permissions
|
|
if current_user.user_type != 'super_admin':
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Insufficient permissions to view metrics"
|
|
)
|
|
|
|
if current_user.user_type == 'tenant_admin' and current_user.tenant_id != tenant_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Cannot view metrics for other tenants"
|
|
)
|
|
|
|
service = DremioService(db)
|
|
|
|
try:
|
|
metrics = await service._get_performance_metrics(tenant_id)
|
|
return metrics
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to fetch metrics: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/alerts/{tenant_id}")
|
|
async def get_security_alerts(
|
|
tenant_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get security and operational alerts for a tenant"""
|
|
|
|
# Check permissions
|
|
if current_user.user_type != 'super_admin':
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Insufficient permissions to view alerts"
|
|
)
|
|
|
|
if current_user.user_type == 'tenant_admin' and current_user.tenant_id != tenant_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Cannot view alerts for other tenants"
|
|
)
|
|
|
|
service = DremioService(db)
|
|
|
|
try:
|
|
alerts = await service._get_security_alerts(tenant_id)
|
|
return {
|
|
"tenant_id": tenant_id,
|
|
"alerts": alerts,
|
|
"total": len(alerts),
|
|
"critical": len([a for a in alerts if a.get('severity') == 'critical']),
|
|
"warning": len([a for a in alerts if a.get('severity') == 'warning']),
|
|
"info": len([a for a in alerts if a.get('severity') == 'info'])
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to fetch alerts: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/query-types")
|
|
async def get_available_query_types(
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get list of available analytics query types"""
|
|
|
|
return {
|
|
"query_types": [
|
|
{
|
|
"id": "user_activity",
|
|
"name": "User Activity Analysis",
|
|
"description": "Analyze user activity, token usage, and costs"
|
|
},
|
|
{
|
|
"id": "resource_trends",
|
|
"name": "Resource Usage Trends",
|
|
"description": "View resource usage trends over time"
|
|
},
|
|
{
|
|
"id": "cost_optimization",
|
|
"name": "Cost Optimization Report",
|
|
"description": "Identify cost optimization opportunities"
|
|
}
|
|
]
|
|
} |