138 lines
4.2 KiB
Python
138 lines
4.2 KiB
Python
import abc
|
|
from dataclasses import field
|
|
from pathlib import Path
|
|
from typing import List, Optional
|
|
|
|
from pydantic.dataclasses import dataclass
|
|
|
|
from .specification import PythonSpecification
|
|
|
|
NOT_IMPLEMENTED_ERROR_MSG = (
|
|
"Needs implementation for the specific " "specification type."
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class Dependency:
|
|
name: str
|
|
version: Optional[str]
|
|
specifications: List[PythonSpecification]
|
|
found: Optional[Path] = None
|
|
absolute_path: Optional[Path] = None
|
|
insecure_versions: List[str] = field(default_factory=lambda: [])
|
|
secure_versions: List[str] = field(default_factory=lambda: [])
|
|
latest_version_without_known_vulnerabilities: Optional[str] = None
|
|
latest_version: Optional[str] = None
|
|
more_info_url: Optional[str] = None
|
|
|
|
def has_unpinned_specification(self):
|
|
for specification in self.specifications:
|
|
if not specification.is_pinned():
|
|
return True
|
|
return False
|
|
|
|
def get_unpinned_specificaitons(self):
|
|
return filter(
|
|
lambda specification: not specification.is_pinned(), self.specifications
|
|
)
|
|
|
|
@abc.abstractmethod
|
|
def filter_by_supported_versions(self, versions: List[str]) -> List[str]:
|
|
raise NotImplementedError()
|
|
|
|
@abc.abstractmethod
|
|
def get_versions(self, db_full):
|
|
raise NotImplementedError()
|
|
|
|
@abc.abstractmethod
|
|
def refresh_from(self, db_full):
|
|
raise NotImplementedError()
|
|
|
|
def to_dict(self, **kwargs):
|
|
if kwargs.get("short_version", False):
|
|
return {
|
|
"name": self.name,
|
|
"version": self.version,
|
|
"requirements": self.specifications,
|
|
}
|
|
|
|
return {
|
|
"name": self.name,
|
|
"version": self.version,
|
|
"requirements": self.specifications,
|
|
"found": None,
|
|
"insecure_versions": self.insecure_versions,
|
|
"secure_versions": self.secure_versions,
|
|
"latest_version_without_known_vulnerabilities": self.latest_version_without_known_vulnerabilities, # noqa: E501
|
|
"latest_version": self.latest_version,
|
|
"more_info_url": self.more_info_url,
|
|
}
|
|
|
|
def update(self, new):
|
|
for key, value in new.items():
|
|
if hasattr(self, key):
|
|
setattr(self, key, value)
|
|
|
|
|
|
@dataclass
|
|
class PythonDependency(Dependency):
|
|
def filter_by_supported_versions(self, versions: List[str]) -> List[str]:
|
|
from packaging.version import parse as parse_version
|
|
|
|
allowed = []
|
|
|
|
for version in versions:
|
|
try:
|
|
parse_version(version)
|
|
allowed.append(version)
|
|
except Exception:
|
|
pass
|
|
|
|
return allowed
|
|
|
|
def get_versions(self, db_full):
|
|
pkg_meta = db_full.get("meta", {}).get("packages", {}).get(self.name, {})
|
|
versions = self.filter_by_supported_versions(
|
|
pkg_meta.get("insecure_versions", []) + pkg_meta.get("secure_versions", [])
|
|
)
|
|
return set(versions)
|
|
|
|
def refresh_from(self, db_full):
|
|
from packaging.utils import canonicalize_name
|
|
|
|
base_domain = db_full.get("meta", {}).get("base_domain")
|
|
pkg_meta = (
|
|
db_full.get("meta", {})
|
|
.get("packages", {})
|
|
.get(canonicalize_name(self.name), {})
|
|
)
|
|
|
|
kwargs = {
|
|
"insecure_versions": self.filter_by_supported_versions(
|
|
pkg_meta.get("insecure_versions", [])
|
|
),
|
|
"secure_versions": self.filter_by_supported_versions(
|
|
pkg_meta.get("secure_versions", [])
|
|
),
|
|
"latest_version_without_known_vulnerabilities": pkg_meta.get(
|
|
"latest_secure_version", None
|
|
),
|
|
"latest_version": pkg_meta.get("latest_version", None),
|
|
"more_info_url": f"{base_domain}{pkg_meta.get('more_info_path', '')}",
|
|
}
|
|
|
|
self.update(kwargs)
|
|
|
|
@staticmethod
|
|
def find_version(specifications: List[PythonSpecification]) -> Optional[str]:
|
|
ver = None
|
|
|
|
if len(specifications) != 1:
|
|
return ver
|
|
|
|
specification = specifications[0]
|
|
if specification.is_pinned():
|
|
ver = next(iter(specification.specifier)).version
|
|
|
|
return ver
|