#!/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())