átlagos kiegészítséek jó sok
This commit is contained in:
353
backend/app/scripts/smart_admin_audit.py
Normal file
353
backend/app/scripts/smart_admin_audit.py
Normal file
@@ -0,0 +1,353 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user