Files
service-finder/backend/app/scripts/smart_admin_audit.py
2026-03-22 11:02:05 +00:00

353 lines
15 KiB
Python

#!/usr/bin/env python3
"""
Smart Admin Audit Script
This script performs a targeted audit of the Service Finder admin system:
1. Finds business hardcoded values (excluding trivial 0, 1, True, False)
2. Identifies which API modules lack /admin prefixed endpoints
3. Generates a comprehensive gap analysis report in Markdown format
"""
import ast
import os
import re
import datetime
from pathlib import Path
from typing import List, Dict, Set, Tuple, Any
import sys
# Project root (relative to script location)
# In container: /app/app/scripts/smart_admin_audit.py -> parent.parent.parent = /app
PROJECT_ROOT = Path("/app")
BACKEND_DIR = PROJECT_ROOT # /app is the backend root in container
ENDPOINTS_DIR = BACKEND_DIR / "app" / "api" / "v1" / "endpoints"
SERVICES_DIR = BACKEND_DIR / "app" / "services"
MODELS_DIR = BACKEND_DIR / "app" / "models"
OUTPUT_FILE = PROJECT_ROOT / "admin_gap_analysis.md"
# Patterns for business hardcoded values (exclude trivial values)
BUSINESS_PATTERNS = [
r"award_points\s*=\s*(\d+)",
r"validation_level\s*=\s*(\d+)",
r"max_vehicles\s*=\s*(\d+)",
r"max_users\s*=\s*(\d+)",
r"credit_limit\s*=\s*(\d+)",
r"daily_limit\s*=\s*(\d+)",
r"monthly_limit\s*=\s*(\d+)",
r"threshold\s*=\s*(\d+)",
r"quota\s*=\s*(\d+)",
r"priority\s*=\s*(\d+)",
r"timeout\s*=\s*(\d+)",
r"retry_count\s*=\s*(\d+)",
r"batch_size\s*=\s*(\d+)",
r"page_size\s*=\s*(\d+)",
r"cache_ttl\s*=\s*(\d+)",
r"expiry_days\s*=\s*(\d+)",
r"cooldown\s*=\s*(\d+)",
r"penalty\s*=\s*(\d+)",
r"reward\s*=\s*(\d+)",
r"discount\s*=\s*(\d+)",
r"commission\s*=\s*(\d+)",
r"fee\s*=\s*(\d+)",
r"vat_rate\s*=\s*(\d+)",
r"service_fee\s*=\s*(\d+)",
r"subscription_fee\s*=\s*(\d+)",
]
# Trivial values to exclude
TRIVIAL_VALUES = {"0", "1", "True", "False", "None", "''", '""', "[]", "{}"}
def find_hardcoded_values() -> List[Dict[str, Any]]:
"""
Scan Python files for business-relevant hardcoded values.
Returns list of findings with file, line, value, and context.
"""
findings = []
# Walk through backend directory
for root, dirs, files in os.walk(BACKEND_DIR):
# Skip virtual environments and test directories
if any(exclude in root for exclude in ["__pycache__", ".venv", "tests", "migrations"]):
continue
for file in files:
if file.endswith(".py"):
filepath = Path(root) / file
try:
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
# Parse AST to find assignments
tree = ast.parse(content, filename=str(filepath))
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name):
var_name = target.id
# Check if assignment value is a constant
if isinstance(node.value, ast.Constant):
value = node.value.value
value_str = str(value)
# Skip trivial values
if value_str in TRIVIAL_VALUES:
continue
# Check if variable name matches business patterns
for pattern in BUSINESS_PATTERNS:
if re.match(pattern.replace(r"\s*=\s*(\d+)", ""), var_name):
findings.append({
"file": str(filepath.relative_to(PROJECT_ROOT)),
"line": node.lineno,
"variable": var_name,
"value": value_str,
"context": ast.get_source_segment(content, node)
})
break
# Also check numeric values > 1 or strings that look like config
if isinstance(value, (int, float)) and value > 1:
findings.append({
"file": str(filepath.relative_to(PROJECT_ROOT)),
"line": node.lineno,
"variable": var_name,
"value": value_str,
"context": ast.get_source_segment(content, node)
})
elif isinstance(value, str) and len(value) > 10 and " " not in value:
# Could be API keys, URLs, etc
findings.append({
"file": str(filepath.relative_to(PROJECT_ROOT)),
"line": node.lineno,
"variable": var_name,
"value": f'"{value_str[:50]}..."',
"context": ast.get_source_segment(content, node)
})
except (SyntaxError, UnicodeDecodeError):
continue
return findings
def analyze_admin_endpoints() -> Dict[str, Dict[str, Any]]:
"""
Analyze which API modules have /admin prefixed endpoints.
Returns dict with module analysis.
"""
modules = {}
if not ENDPOINTS_DIR.exists():
print(f"Warning: Endpoints directory not found: {ENDPOINTS_DIR}")
return modules
for endpoint_file in ENDPOINTS_DIR.glob("*.py"):
module_name = endpoint_file.stem
with open(endpoint_file, "r", encoding="utf-8") as f:
content = f.read()
# Check for router definition
router_match = re.search(r"router\s*=\s*APIRouter\(.*?prefix\s*=\s*[\"']/admin[\"']", content, re.DOTALL)
has_admin_prefix = bool(router_match)
# Check for admin endpoints (routes with /admin in path)
admin_routes = re.findall(r'@router\.\w+\([\"\'][^\"\']*?/admin[^\"\']*?[\"\']', content)
# Check for admin-specific functions
admin_functions = re.findall(r"def\s+\w+.*admin.*:", content, re.IGNORECASE)
modules[module_name] = {
"has_admin_prefix": has_admin_prefix,
"admin_routes_count": len(admin_routes),
"admin_functions": len(admin_functions),
"file_size": len(content),
"has_admin_file": (endpoint_file.stem == "admin")
}
return modules
def identify_missing_admin_modules(modules: Dict[str, Dict[str, Any]]) -> List[str]:
"""
Identify which core modules lack admin endpoints.
"""
core_modules = [
"users", "vehicles", "services", "assets", "organizations",
"billing", "gamification", "analytics", "security", "documents",
"evidence", "expenses", "finance_admin", "notifications", "reports",
"catalog", "providers", "search", "social", "system_parameters"
]
missing = []
for module in core_modules:
if module not in modules:
missing.append(module)
continue
mod_info = modules[module]
if not mod_info["has_admin_prefix"] and mod_info["admin_routes_count"] == 0:
missing.append(module)
return missing
def generate_markdown_report(hardcoded_findings: List[Dict[str, Any]],
modules: Dict[str, Dict[str, Any]],
missing_admin_modules: List[str]) -> str:
"""
Generate comprehensive Markdown report.
"""
report = []
report.append("# Admin System Gap Analysis Report")
report.append(f"*Generated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*")
report.append("")
# Executive Summary
report.append("## 📊 Executive Summary")
report.append("")
report.append(f"- **Total hardcoded business values found:** {len(hardcoded_findings)}")
report.append(f"- **API modules analyzed:** {len(modules)}")
report.append(f"- **Modules missing admin endpoints:** {len(missing_admin_modules)}")
report.append("")
# Hardcoded Values Section
report.append("## 🔍 Hardcoded Business Values")
report.append("")
report.append("These values should be moved to `system_parameters` table for dynamic configuration.")
report.append("")
if hardcoded_findings:
report.append("| File | Line | Variable | Value | Context |")
report.append("|------|------|----------|-------|---------|")
for finding in hardcoded_findings[:50]: # Limit to 50 for readability
file_link = finding["file"]
line = finding["line"]
variable = finding["variable"]
value = finding["value"]
context = finding["context"].replace("|", "\\|").replace("\n", " ").strip()[:100]
report.append(f"| `{file_link}` | {line} | `{variable}` | `{value}` | `{context}` |")
if len(hardcoded_findings) > 50:
report.append(f"\n*... and {len(hardcoded_findings) - 50} more findings*")
else:
report.append("*No significant hardcoded business values found.*")
report.append("")
# Admin Endpoints Analysis
report.append("## 🏗️ Admin Endpoints Analysis")
report.append("")
report.append("### Modules with Admin Prefix")
report.append("")
admin_modules = [m for m, info in modules.items() if info["has_admin_prefix"]]
if admin_modules:
report.append(", ".join(f"`{m}`" for m in admin_modules))
else:
report.append("*No modules have `/admin` prefix*")
report.append("")
report.append("### Modules with Admin Routes (but no prefix)")
report.append("")
mixed_modules = [m for m, info in modules.items() if not info["has_admin_prefix"] and info["admin_routes_count"] > 0]
if mixed_modules:
for module in mixed_modules:
info = modules[module]
report.append(f"- `{module}`: {info['admin_routes_count']} admin routes")
else:
report.append("*No mixed admin routes found*")
report.append("")
# Missing Admin Modules
report.append("## ⚠️ Critical Gaps: Missing Admin Endpoints")
report.append("")
report.append("These core business modules lack dedicated admin endpoints:")
report.append("")
if missing_admin_modules:
for module in missing_admin_modules:
report.append(f"- **{module}** - No `/admin` prefix and no admin routes")
report.append("")
report.append("### Recommended Actions:")
report.append("1. Create `/admin` prefixed routers for each missing module")
report.append("2. Implement CRUD endpoints for administrative operations")
report.append("3. Add audit logging and permission checks")
else:
report.append("*All core modules have admin endpoints!*")
report.append("")
# Recommendations
report.append("## 🚀 Recommendations")
report.append("")
report.append("### Phase 1: Hardcode Elimination")
report.append("1. Create `system_parameters` migration if not exists")
report.append("2. Move identified hardcoded values to database")
report.append("3. Implement `ConfigService` for dynamic value retrieval")
report.append("")
report.append("### Phase 2: Admin Endpoint Expansion")
report.append("1. Prioritize modules with highest business impact:")
report.append(" - `users` (user management)")
report.append(" - `billing` (financial oversight)")
report.append(" - `security` (access control)")
report.append("2. Follow consistent pattern: `/admin/{module}/...`")
report.append("3. Implement RBAC with `admin` and `superadmin` roles")
report.append("")
report.append("### Phase 3: Monitoring & Audit")
report.append("1. Add admin action logging to `SecurityAuditLog`")
report.append("2. Implement admin dashboard with real-time metrics")
report.append("3. Create automated health checks for admin endpoints")
report.append("")
# Technical Details
report.append("## 🔧 Technical Details")
report.append("")
report.append("### Scan Parameters")
report.append(f"- Project root: `{PROJECT_ROOT}`")
report.append(f"- Files scanned: Python files in `{BACKEND_DIR}`")
report.append(f"- Business patterns: {len(BUSINESS_PATTERNS)}")
report.append(f"- Trivial values excluded: {', '.join(TRIVIAL_VALUES)}")
report.append("")
return "\n".join(report)
def main():
"""Main execution function."""
print("🔍 Starting Smart Admin Audit...")
# 1. Find hardcoded values
print("Step 1: Scanning for hardcoded business values...")
hardcoded_findings = find_hardcoded_values()
print(f" Found {len(hardcoded_findings)} potential hardcoded values")
# 2. Analyze admin endpoints
print("Step 2: Analyzing admin endpoints...")
modules = analyze_admin_endpoints()
print(f" Analyzed {len(modules)} API modules")
# 3. Identify missing admin modules
missing_admin_modules = identify_missing_admin_modules(modules)
print(f" Found {len(missing_admin_modules)} modules missing admin endpoints")
# 4. Generate report
print("Step 3: Generating Markdown report...")
import datetime
report = generate_markdown_report(hardcoded_findings, modules, missing_admin_modules)
# Write to file
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
f.write(report)
print(f"✅ Report generated: {OUTPUT_FILE}")
print(f" - Hardcoded values: {len(hardcoded_findings)}")
print(f" - Modules analyzed: {len(modules)}")
print(f" - Missing admin: {len(missing_admin_modules)}")
# Print summary to console
if missing_admin_modules:
print("\n⚠️ CRITICAL GAPS:")
for module in missing_admin_modules[:5]:
print(f" - {module} lacks admin endpoints")
if len(missing_admin_modules) > 5:
print(f" ... and {len(missing_admin_modules) - 5} more")
return 0
if __name__ == "__main__":
sys.exit(main())