aboutsummaryrefslogtreecommitdiffstats
path: root/src/nvd/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvd/models.py')
-rw-r--r--src/nvd/models.py315
1 files changed, 315 insertions, 0 deletions
diff --git a/src/nvd/models.py b/src/nvd/models.py
new file mode 100644
index 0000000..77fb7c4
--- /dev/null
+++ b/src/nvd/models.py
@@ -0,0 +1,315 @@
+"""Pydantic models for NVD API responses."""
+
+from datetime import datetime
+from typing import Any, Dict, List, Optional
+
+from pydantic import BaseModel, Field
+
+
+# Common models
+class LangString(BaseModel):
+ """Multilingual string."""
+
+ lang: str
+ value: str
+
+
+# CVSS Models
+class CVSSMetricV2(BaseModel):
+ """CVSS v2.0 metrics."""
+
+ source: str
+ type: str
+ cvssData: Dict[str, Any]
+ baseSeverity: Optional[str] = None
+ exploitabilityScore: Optional[float] = None
+ impactScore: Optional[float] = None
+ acInsufInfo: Optional[bool] = None
+ obtainAllPrivilege: Optional[bool] = None
+ obtainUserPrivilege: Optional[bool] = None
+ obtainOtherPrivilege: Optional[bool] = None
+ userInteractionRequired: Optional[bool] = None
+
+
+class CVSSMetricV3(BaseModel):
+ """CVSS v3.x metrics."""
+
+ source: str
+ type: str
+ cvssData: Dict[str, Any]
+ exploitabilityScore: Optional[float] = None
+ impactScore: Optional[float] = None
+
+
+class CVSSMetricV4(BaseModel):
+ """CVSS v4.0 metrics."""
+
+ source: str
+ type: str
+ cvssData: Dict[str, Any]
+
+
+class Metrics(BaseModel):
+ """CVSS metrics container."""
+
+ cvssMetricV2: Optional[List[CVSSMetricV2]] = None
+ cvssMetricV30: Optional[List[CVSSMetricV3]] = None
+ cvssMetricV31: Optional[List[CVSSMetricV3]] = None
+ cvssMetricV40: Optional[List[CVSSMetricV4]] = None
+
+
+# CPE Models
+class CPEMatch(BaseModel):
+ """CPE match criteria."""
+
+ vulnerable: bool
+ criteria: str
+ matchCriteriaId: str
+ versionStartIncluding: Optional[str] = None
+ versionStartExcluding: Optional[str] = None
+ versionEndIncluding: Optional[str] = None
+ versionEndExcluding: Optional[str] = None
+
+
+class Node(BaseModel):
+ """Configuration node."""
+
+ operator: str
+ negate: bool
+ cpeMatch: List[CPEMatch]
+
+
+class Configuration(BaseModel):
+ """CVE configuration."""
+
+ nodes: List[Node]
+
+
+# Weakness Models
+class WeaknessDescription(BaseModel):
+ """CWE description."""
+
+ lang: str
+ value: str
+
+
+class Weakness(BaseModel):
+ """CWE weakness."""
+
+ source: str
+ type: str
+ description: List[WeaknessDescription]
+
+
+# Reference Models
+class Reference(BaseModel):
+ """External reference."""
+
+ url: str
+ source: Optional[str] = None
+ tags: Optional[List[str]] = None
+
+
+# CVE Models
+class CVEData(BaseModel):
+ """Core CVE data."""
+
+ id: str = Field(alias="cveId")
+ sourceIdentifier: str
+ published: datetime
+ lastModified: datetime
+ vulnStatus: str
+ cveTags: Optional[List[Dict[str, str]]] = None
+ descriptions: List[LangString]
+ metrics: Optional[Metrics] = None
+ weaknesses: Optional[List[Weakness]] = None
+ configurations: Optional[List[Configuration]] = None
+ references: List[Reference]
+ vendorComments: Optional[List[Dict[str, Any]]] = None
+
+ class Config:
+ populate_by_name = True
+
+ @property
+ def description(self) -> str:
+ """Get English description."""
+ for desc in self.descriptions:
+ if desc.lang == "en":
+ return desc.value
+ return self.descriptions[0].value if self.descriptions else ""
+
+ @property
+ def cvss_v3_score(self) -> Optional[float]:
+ """Get primary CVSS v3.x base score."""
+ if self.metrics:
+ if self.metrics.cvssMetricV31:
+ return self.metrics.cvssMetricV31[0].cvssData.get("baseScore")
+ if self.metrics.cvssMetricV30:
+ return self.metrics.cvssMetricV30[0].cvssData.get("baseScore")
+ return None
+
+ @property
+ def cvss_v2_score(self) -> Optional[float]:
+ """Get CVSS v2.0 base score."""
+ if self.metrics and self.metrics.cvssMetricV2:
+ return self.metrics.cvssMetricV2[0].cvssData.get("baseScore")
+ return None
+
+
+class CVEItem(BaseModel):
+ """CVE item wrapper."""
+
+ cve: CVEData
+
+
+class CVEResponse(BaseModel):
+ """CVE API response."""
+
+ resultsPerPage: int
+ startIndex: int
+ totalResults: int
+ format: str
+ version: str
+ timestamp: datetime
+ vulnerabilities: List[CVEItem]
+
+
+# CVE Change History Models
+class ChangeDetail(BaseModel):
+ """Individual change detail."""
+
+ action: Optional[str] = None
+ type: Optional[str] = None
+ oldValue: Optional[str] = None
+ newValue: Optional[str] = None
+
+
+class CVEChange(BaseModel):
+ """CVE change event."""
+
+ cveId: str
+ eventName: str
+ cveChangeId: str
+ sourceIdentifier: str
+ created: datetime
+ details: Optional[List[ChangeDetail]] = None
+
+
+class CVEChangeResponse(BaseModel):
+ """CVE Change History API response."""
+
+ resultsPerPage: int
+ startIndex: int
+ totalResults: int
+ format: str
+ version: str
+ timestamp: datetime
+ cveChanges: List[CVEChange]
+
+
+# CPE Models
+class CPETitle(BaseModel):
+ """CPE title in different languages."""
+
+ title: str
+ lang: str
+
+
+class CPERef(BaseModel):
+ """CPE reference."""
+
+ ref: str
+ type: Optional[str] = None
+
+
+class CPEData(BaseModel):
+ """CPE product data."""
+
+ cpeName: str
+ cpeNameId: str
+ created: datetime
+ lastModified: datetime
+ deprecated: bool = False
+ titles: Optional[List[CPETitle]] = None
+ refs: Optional[List[CPERef]] = None
+ deprecatedBy: Optional[List[Dict[str, str]]] = None
+
+ @property
+ def title(self) -> str:
+ """Get English title."""
+ if self.titles:
+ for t in self.titles:
+ if t.lang == "en":
+ return t.title
+ return self.titles[0].title
+ return ""
+
+
+class CPEItem(BaseModel):
+ """CPE item wrapper from API response."""
+
+ cpe: CPEData
+
+
+class CPEResponse(BaseModel):
+ """CPE API response."""
+
+ resultsPerPage: int
+ startIndex: int
+ totalResults: int
+ format: str
+ version: str
+ timestamp: datetime
+ products: List[CPEItem]
+
+
+# CPE Match Criteria Models
+class CPEMatchString(BaseModel):
+ """CPE match string data."""
+
+ matchCriteriaId: str
+ criteria: str
+ lastModified: datetime
+ cpeLastModified: Optional[datetime] = None
+ created: datetime
+ status: str
+ matches: Optional[List[Dict[str, str]]] = None
+ versionStartIncluding: Optional[str] = None
+ versionStartExcluding: Optional[str] = None
+ versionEndIncluding: Optional[str] = None
+ versionEndExcluding: Optional[str] = None
+
+
+class CPEMatchResponse(BaseModel):
+ """CPE Match Criteria API response."""
+
+ resultsPerPage: int
+ startIndex: int
+ totalResults: int
+ format: str
+ version: str
+ timestamp: datetime
+ matchStrings: List[CPEMatchString]
+
+
+# Source Models
+class SourceData(BaseModel):
+ """Data source organization."""
+
+ name: str
+ contactEmail: str
+ sourceIdentifiers: List[str]
+ lastModified: datetime
+ created: datetime
+
+
+class SourceResponse(BaseModel):
+ """Source API response."""
+
+ resultsPerPage: int
+ startIndex: int
+ totalResults: int
+ format: str
+ version: str
+ timestamp: datetime
+ sources: List[SourceData]