| 106 | @dataclasses.dataclass(eq=True) |
| 107 | @functools.total_ordering |
| 108 | class Reference: |
| 109 | reference_id: str = "" |
| 110 | reference_type: str = "" |
| 111 | url: str = "" |
| 112 | severities: List[VulnerabilitySeverity] = dataclasses.field(default_factory=list) |
| 113 | |
| 114 | def __post_init__(self): |
| 115 | if not self.url: |
| 116 | raise TypeError("Reference must have a url") |
| 117 | if self.reference_id and not isinstance(self.reference_id, str): |
| 118 | self.reference_id = str(self.reference_id) |
| 119 | |
| 120 | def __lt__(self, other): |
| 121 | if not isinstance(other, Reference): |
| 122 | return NotImplemented |
| 123 | return self._cmp_key() < other._cmp_key() |
| 124 | |
| 125 | # TODO: Add cache |
| 126 | def _cmp_key(self): |
| 127 | return (self.reference_id, self.reference_type, self.url, tuple(self.severities)) |
| 128 | |
| 129 | def to_dict(self): |
| 130 | """Return a normalized dictionary representation""" |
| 131 | return { |
| 132 | "reference_id": self.reference_id, |
| 133 | "reference_type": self.reference_type, |
| 134 | "url": self.url, |
| 135 | "severities": [severity.to_dict() for severity in sorted(self.severities)], |
| 136 | } |
| 137 | |
| 138 | @classmethod |
| 139 | def from_dict(cls, ref: dict): |
| 140 | return cls( |
| 141 | reference_id=str(ref["reference_id"]), |
| 142 | reference_type=ref.get("reference_type") or "", |
| 143 | url=ref["url"], |
| 144 | severities=[ |
| 145 | VulnerabilitySeverity.from_dict(severity) for severity in ref["severities"] |
| 146 | ], |
| 147 | ) |
| 148 | |
| 149 | @classmethod |
| 150 | def from_url(cls, url): |
| 151 | reference_id = get_reference_id(url) |
| 152 | if "GHSA-" in reference_id.upper(): |
| 153 | return cls(reference_id=reference_id, url=url) |
| 154 | if is_cve(reference_id): |
| 155 | return cls(url=url, reference_id=reference_id.upper()) |
| 156 | return cls(url=url) |
| 157 | |
| 158 | |
| 159 | @dataclasses.dataclass(eq=True) |
no outgoing calls