| 1 | from datetime import datetime |
| 2 | from enum import Enum |
| 3 | from typing import Any |
| 4 | from typing import Dict |
| 5 | from typing import List |
| 6 | from typing import Optional |
| 7 | |
| 8 | from pydantic import BaseModel |
| 9 | from pydantic import Field |
| 10 | |
| 11 | |
| 12 | class AuditStatus(str, Enum): |
| 13 | PASS = "pass" |
| 14 | FAIL = "fail" |
| 15 | WARNING = "warning" |
| 16 | NOT_APPLICABLE = "not_applicable" |
| 17 | |
| 18 | |
| 19 | class SeverityLevel(str, Enum): |
| 20 | CRITICAL = "critical" |
| 21 | HIGH = "high" |
| 22 | MEDIUM = "medium" |
| 23 | LOW = "low" |
| 24 | INFO = "info" |
| 25 | |
| 26 | |
| 27 | # ==================== Request Schemas ==================== |
| 28 | |
| 29 | |
| 30 | class GitHubAuditRequest(BaseModel): |
| 31 | """Request to run a GitHub organization audit.""" |
| 32 | |
| 33 | organization: str = Field(..., description="GitHub organization name to audit") |
| 34 | include_repos: bool = Field(True, description="Include repository-level audits") |
| 35 | include_workflows: bool = Field(True, description="Include workflow/actions audits") |
| 36 | include_members: bool = Field(True, description="Include member/permission audits") |
| 37 | repo_filter: Optional[List[str]] = Field(None, description="Specific repos to audit (None = all)") |
| 38 | |
| 39 | |
| 40 | class GitHubAuditConfigCreate(BaseModel): |
| 41 | """Request to create a GitHub Audit configuration.""" |
| 42 | |
| 43 | customer_code: str = Field(..., max_length=50, description="Customer code") |
| 44 | github_token: str = Field(..., max_length=500, description="GitHub PAT or App token") |
| 45 | organization: str = Field(..., max_length=100, description="GitHub organization name") |
| 46 | token_type: str = Field("pat", max_length=50, description="Token type: 'pat' or 'app'") |
| 47 | token_expires_at: Optional[datetime] = Field(None, description="When the token expires") |
| 48 | enabled: bool = Field(True, description="Whether audits are enabled") |
| 49 | auto_audit_enabled: bool = Field(False, description="Enable scheduled audits") |
| 50 | audit_schedule_cron: Optional[str] = Field(None, max_length=50, description="Cron schedule") |
| 51 | include_repos: bool = Field(True, description="Include repository audits") |
| 52 | include_workflows: bool = Field(True, description="Include workflow audits") |
| 53 | include_members: bool = Field(True, description="Include member audits") |
| 54 | include_archived_repos: bool = Field(False, description="Include archived repos") |
| 55 | repo_filter_mode: str = Field("all", description="'all', 'include', or 'exclude'") |
| 56 | repo_filter_list: Optional[List[str]] = Field(None, description="Repos to include/exclude") |
| 57 | notify_on_critical: bool = Field(True, description="Notify on critical findings") |
| 58 | notify_on_high: bool = Field(False, description="Notify on high findings") |
| 59 | notification_webhook_url: Optional[str] = Field(None, max_length=500) |
| 60 | notification_email: Optional[str] = Field(None, max_length=255) |
| 61 | minimum_passing_score: float = Field(70.0, ge=0, le=100) |
| 62 | created_by: Optional[str] = Field(None, max_length=100) |
| 63 | |
| 64 | |
| 65 | class GitHubAuditConfigUpdate(BaseModel): |
| 66 | """Request to update a GitHub Audit configuration.""" |
| 67 | |
| 68 | github_token: Optional[str] = Field(None, max_length=500) |
| 69 | organization: Optional[str] = Field(None, max_length=100) |
| 70 | token_type: Optional[str] = Field(None, max_length=50) |
| 71 | token_expires_at: Optional[datetime] = None |
| 72 | enabled: Optional[bool] = None |
| 73 | auto_audit_enabled: Optional[bool] = None |
| 74 | audit_schedule_cron: Optional[str] = Field(None, max_length=50) |
| 75 | include_repos: Optional[bool] = None |
| 76 | include_workflows: Optional[bool] = None |
| 77 | include_members: Optional[bool] = None |
| 78 | include_archived_repos: Optional[bool] = None |
| 79 | repo_filter_mode: Optional[str] = None |
| 80 | repo_filter_list: Optional[List[str]] = None |
| 81 | notify_on_critical: Optional[bool] = None |
| 82 | notify_on_high: Optional[bool] = None |
| 83 | notification_webhook_url: Optional[str] = Field(None, max_length=500) |
| 84 | notification_email: Optional[str] = Field(None, max_length=255) |
| 85 | minimum_passing_score: Optional[float] = Field(None, ge=0, le=100) |
| 86 | updated_by: Optional[str] = Field(None, max_length=100) |
| 87 | |
| 88 | |
| 89 | class GitHubAuditExclusionCreate(BaseModel): |
| 90 | """Request to create a check exclusion.""" |
| 91 | |
| 92 | check_id: str = Field(..., max_length=100, description="Check ID to exclude") |
| 93 | resource_name: Optional[str] = Field(None, max_length=255, description="Specific resource") |
| 94 | resource_type: Optional[str] = Field(None, max_length=50) |
| 95 | reason: str = Field(..., description="Reason for exclusion") |
| 96 | approved_by: Optional[str] = Field(None, max_length=100) |
| 97 | expires_at: Optional[datetime] = None |
| 98 | created_by: str = Field(..., max_length=100) |
| 99 | |
| 100 | |
| 101 | class GitHubAuditExclusionUpdate(BaseModel): |
| 102 | """Request to update a check exclusion.""" |
| 103 | |
| 104 | reason: Optional[str] = None |
| 105 | approved_by: Optional[str] = Field(None, max_length=100) |
| 106 | expires_at: Optional[datetime] = None |
| 107 | enabled: Optional[bool] = None |
| 108 | |
| 109 | |
| 110 | class GitHubAuditBaselineCreate(BaseModel): |
| 111 | """Request to create a baseline.""" |
| 112 | |
| 113 | name: str = Field(..., max_length=255) |
| 114 | description: Optional[str] = None |
| 115 | expected_checks: Optional[Dict[str, str]] = None |
| 116 | baseline_report_id: Optional[int] = None |
| 117 | is_active: bool = Field(True) |
| 118 | created_by: str = Field(..., max_length=100) |
| 119 | |
| 120 | |
| 121 | class GitHubAuditBaselineUpdate(BaseModel): |
| 122 | """Request to update a baseline.""" |
| 123 | |
| 124 | name: Optional[str] = Field(None, max_length=255) |
| 125 | description: Optional[str] = None |
| 126 | is_active: Optional[bool] = None |
| 127 | |
| 128 | |
| 129 | # ==================== Audit Check Result Schemas ==================== |
| 130 | |
| 131 | |
| 132 | class AuditCheck(BaseModel): |
| 133 | """Individual audit check result.""" |
| 134 | |
| 135 | check_id: str = Field(..., description="Unique identifier for the check") |
| 136 | check_name: str = Field(..., description="Human-readable check name") |
| 137 | category: str = Field(..., description="Category (e.g., 'repository', 'organization')") |
| 138 | status: AuditStatus = Field(..., description="Pass/Fail/Warning status") |
| 139 | severity: SeverityLevel = Field(..., description="Severity if failed") |
| 140 | description: str = Field(..., description="Description of what was checked") |
| 141 | recommendation: Optional[str] = Field(None, description="Remediation recommendation") |
| 142 | details: Optional[Dict[str, Any]] = Field(None, description="Additional context") |
| 143 | resource_name: Optional[str] = Field(None, description="Name of resource being checked") |
| 144 | resource_type: Optional[str] = Field(None, description="Type of resource") |
| 145 | |
| 146 | |
| 147 | class RepositoryAuditResult(BaseModel): |
| 148 | """Audit results for a single repository.""" |
| 149 | |
| 150 | repo_name: str |
| 151 | repo_full_name: str |
| 152 | repo_url: str |
| 153 | is_private: bool |
| 154 | is_archived: bool = False |
| 155 | default_branch: str |
| 156 | checks: List[AuditCheck] = Field(default_factory=list) |
| 157 | passed_count: int = 0 |
| 158 | failed_count: int = 0 |
| 159 | warning_count: int = 0 |
| 160 | |
| 161 | |
| 162 | class OrganizationAuditResult(BaseModel): |
| 163 | """Audit results for organization-level settings.""" |
| 164 | |
| 165 | org_name: str |
| 166 | org_url: str |
| 167 | checks: List[AuditCheck] = Field(default_factory=list) |
| 168 | passed_count: int = 0 |
| 169 | failed_count: int = 0 |
| 170 | warning_count: int = 0 |
| 171 | |
| 172 | |
| 173 | class WorkflowAuditResult(BaseModel): |
| 174 | """Audit results for GitHub Actions workflows.""" |
| 175 | |
| 176 | repo_name: str |
| 177 | workflow_name: str |
| 178 | workflow_path: str |
| 179 | checks: List[AuditCheck] = Field(default_factory=list) |
| 180 | |
| 181 | |
| 182 | class MemberAuditResult(BaseModel): |
| 183 | """Audit results for organization members.""" |
| 184 | |
| 185 | username: str |
| 186 | role: str |
| 187 | has_2fa: Optional[bool] = None |
| 188 | checks: List[AuditCheck] = Field(default_factory=list) |
| 189 | |
| 190 | |
| 191 | class AuditSummary(BaseModel): |
| 192 | """Summary of the entire audit.""" |
| 193 | |
| 194 | organization: str |
| 195 | audit_timestamp: str |
| 196 | total_repos_audited: int = 0 |
| 197 | total_checks: int = 0 |
| 198 | passed_checks: int = 0 |
| 199 | failed_checks: int = 0 |
| 200 | warning_checks: int = 0 |
| 201 | critical_findings: int = 0 |
| 202 | high_findings: int = 0 |
| 203 | medium_findings: int = 0 |
| 204 | low_findings: int = 0 |
| 205 | score: float = 0.0 |
| 206 | grade: str = "F" |
| 207 | |
| 208 | |
| 209 | # ==================== Response Schemas ==================== |
| 210 | |
| 211 | |
| 212 | class GitHubAuditResponse(BaseModel): |
| 213 | """Full GitHub audit response.""" |
| 214 | |
| 215 | success: bool |
| 216 | message: str |
| 217 | summary: Optional[AuditSummary] = None |
| 218 | organization_results: Optional[OrganizationAuditResult] = None |
| 219 | repository_results: List[RepositoryAuditResult] = Field(default_factory=list) |
| 220 | workflow_results: List[WorkflowAuditResult] = Field(default_factory=list) |
| 221 | member_results: List[MemberAuditResult] = Field(default_factory=list) |
| 222 | top_findings: List[AuditCheck] = Field(default_factory=list) |
| 223 | |
| 224 | |
| 225 | class GitHubAuditSummaryResponse(BaseModel): |
| 226 | """Lightweight summary response.""" |
| 227 | |
| 228 | success: bool |
| 229 | message: str |
| 230 | summary: Optional[AuditSummary] = None |
| 231 | top_findings: List[AuditCheck] = Field(default_factory=list) |
| 232 | |
| 233 | |
| 234 | class GitHubAuditConfigResponse(BaseModel): |
| 235 | """Response for config operations.""" |
| 236 | |
| 237 | success: bool |
| 238 | message: str |
| 239 | config: Optional[Any] = None # GitHubAuditConfig model |
| 240 | configs: Optional[List[Any]] = None # List of configs |
| 241 | |
| 242 | |
| 243 | class GitHubAuditReportListResponse(BaseModel): |
| 244 | """Response for listing reports.""" |
| 245 | |
| 246 | success: bool |
| 247 | message: str |
| 248 | reports: List[Dict[str, Any]] = Field(default_factory=list) |
| 249 | total_count: int = 0 |
| 250 | |
| 251 | |
| 252 | class GitHubAuditReportResponse(BaseModel): |
| 253 | """Response for single report.""" |
| 254 | |
| 255 | success: bool |
| 256 | message: str |
| 257 | report: Optional[Any] = None # GitHubAuditReport model |
| 258 | |
| 259 | |
| 260 | class GitHubAuditExclusionResponse(BaseModel): |
| 261 | """Response for exclusion operations.""" |
| 262 | |
| 263 | success: bool |
| 264 | message: str |
| 265 | exclusion: Optional[Any] = None |
| 266 | exclusions: Optional[List[Any]] = None |
| 267 | |
| 268 | |
| 269 | class GitHubAuditBaselineResponse(BaseModel): |
| 270 | """Response for baseline operations.""" |
| 271 | |
| 272 | success: bool |
| 273 | message: str |
| 274 | baseline: Optional[Any] = None |
| 275 | baselines: Optional[List[Any]] = None |
| 276 | |
| 277 | |
| 278 | class AvailableChecksResponse(BaseModel): |
| 279 | """Response for available checks.""" |
| 280 | |
| 281 | success: bool |
| 282 | message: str |
| 283 | checks: List[Dict[str, str]] = Field(default_factory=list) |