Skip to content

harborapi.models._scanner

These models have been generated from the official Harbor Pluggable Scanner Spec OpenAPI 3.0 Schema using datamodel-code-generator version 0.13.0.

DEPRECATED: This module will be removed in a future version. Module kept only for backwards compatibility with old code generation scheme.

logger = logging.getLogger('harborapi') module-attribute

DEFAULT_VENDORS = ('nvd', 'redhat') module-attribute

SEVERITY_PRIORITY: Final[Dict[Severity, int]] = {Severity.none: 0, Severity.unknown: 1, Severity.negligible: 2, Severity.low: 3, Severity.medium: 4, Severity.high: 5, Severity.critical: 6} module-attribute

StrDictRootModel

Source code in harborapi/models/base.py
class StrDictRootModel(RootModel[Optional[Dict[str, T]]]):
    # All JSON keys are string, so the key type does need to be
    # parameterized with a generic type.

    def __iter__(self) -> Any:
        # TODO: fix API spec so root  types can never be none, only
        # the empty container. That way we can always iterate and access
        # without checking for None.
        if self.root is not None:
            return iter(self.root)
        return iter([])

    def __getitem__(self, item: str) -> Optional[T]:
        if self.root is not None:
            return self.root[item]
        return None

    # Enables dot access to dict keys for backwards compatibility
    def __getattr__(self, attr: str) -> T:
        try:
            return self.root[attr]  # type: ignore # forego None check and let KeyError raise
        except (KeyError, TypeError):
            raise AttributeError(f"{self.__class__.__name__} has no attribute {attr}")

__iter__()

Source code in harborapi/models/base.py
def __iter__(self) -> Any:
    # TODO: fix API spec so root  types can never be none, only
    # the empty container. That way we can always iterate and access
    # without checking for None.
    if self.root is not None:
        return iter(self.root)
    return iter([])

__getitem__(item)

Source code in harborapi/models/base.py
def __getitem__(self, item: str) -> Optional[T]:
    if self.root is not None:
        return self.root[item]
    return None

__getattr__(attr)

Source code in harborapi/models/base.py
def __getattr__(self, attr: str) -> T:
    try:
        return self.root[attr]  # type: ignore # forego None check and let KeyError raise
    except (KeyError, TypeError):
        raise AttributeError(f"{self.__class__.__name__} has no attribute {attr}")

StrRootModel

Source code in harborapi/models/base.py
class StrRootModel(RootModel[str]):
    def __str__(self) -> str:
        return str(self.root)

__str__()

Source code in harborapi/models/base.py
def __str__(self) -> str:
    return str(self.root)

SemVer

Source code in harborapi/version.py
class SemVer(NamedTuple):
    major: int
    minor: int = 0
    patch: int = 0
    prerelease: Optional[str] = None
    build: Optional[str] = None

    def __bool__(self) -> bool:
        return bool(self.major or self.minor or self.patch)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, SemVer):
            raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
        other = get_semver(other)
        return (
            self.major == other.major
            and self.minor == other.minor
            and self.patch == other.patch
            and self.prerelease == other.prerelease
            and self.build == other.build
        )

    def __gt__(self, other: object) -> bool:
        if not isinstance(other, SemVer):
            raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
        other = get_semver(other)
        if self.major > other.major:
            return True
        if self.major < other.major:
            return False
        if self.minor > other.minor:
            return True
        if self.minor < other.minor:
            return False
        if self.patch > other.patch:
            return True
        if self.patch < other.patch:
            return False
        # A non-prerelease version is always greater than a prerelease version
        if self.prerelease is None and other.prerelease is not None:
            return True
        return False

    def __ge__(self, other: object) -> bool:
        if not isinstance(other, SemVer):
            raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
        other = get_semver(other)
        return self > other or self == other

    def __le__(self, other: object) -> bool:
        if not isinstance(other, SemVer):
            raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
        other = get_semver(other)
        return (not self > other) or self == other

    def __lt__(self, other: object) -> bool:
        if not isinstance(other, SemVer):
            raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
        other = get_semver(other)
        if (
            self.build != other.build
        ):  # we don't care about build equality for less than
            other = SemVer(other.major, other.minor, other.patch, other.prerelease)
        return not self >= other

major: int instance-attribute

minor: int = 0 class-attribute instance-attribute

patch: int = 0 class-attribute instance-attribute

prerelease: Optional[str] = None class-attribute instance-attribute

build: Optional[str] = None class-attribute instance-attribute

__bool__()

Source code in harborapi/version.py
def __bool__(self) -> bool:
    return bool(self.major or self.minor or self.patch)

__eq__(other)

Source code in harborapi/version.py
def __eq__(self, other: object) -> bool:
    if not isinstance(other, SemVer):
        raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
    other = get_semver(other)
    return (
        self.major == other.major
        and self.minor == other.minor
        and self.patch == other.patch
        and self.prerelease == other.prerelease
        and self.build == other.build
    )

__gt__(other)

Source code in harborapi/version.py
def __gt__(self, other: object) -> bool:
    if not isinstance(other, SemVer):
        raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
    other = get_semver(other)
    if self.major > other.major:
        return True
    if self.major < other.major:
        return False
    if self.minor > other.minor:
        return True
    if self.minor < other.minor:
        return False
    if self.patch > other.patch:
        return True
    if self.patch < other.patch:
        return False
    # A non-prerelease version is always greater than a prerelease version
    if self.prerelease is None and other.prerelease is not None:
        return True
    return False

__ge__(other)

Source code in harborapi/version.py
def __ge__(self, other: object) -> bool:
    if not isinstance(other, SemVer):
        raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
    other = get_semver(other)
    return self > other or self == other

__le__(other)

Source code in harborapi/version.py
def __le__(self, other: object) -> bool:
    if not isinstance(other, SemVer):
        raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
    other = get_semver(other)
    return (not self > other) or self == other

__lt__(other)

Source code in harborapi/version.py
def __lt__(self, other: object) -> bool:
    if not isinstance(other, SemVer):
        raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
    other = get_semver(other)
    if (
        self.build != other.build
    ):  # we don't care about build equality for less than
        other = SemVer(other.major, other.minor, other.patch, other.prerelease)
    return not self >= other

BaseModel

Source code in harborapi/models/base.py
class BaseModel(PydanticBaseModel):
    model_config = ConfigDict(extra="allow", validate_assignment=True, strict=False)

    # Validators

    # The __rich* properties are only used by methods defined when Rich
    # is installed, but they are defined here, so that static typing works
    # when overriding the properties in subclasses.
    @property
    def __rich_table_title__(self) -> str:
        """The title to use for the table representation of the model.
        By default, the model's class name is be used.
        """
        try:
            title = self.__name__
            assert isinstance(title, str)
        except (AttributeError, AssertionError):
            title = self.__class__.__name__
        return title

    @property
    def __rich_panel_title__(self) -> Optional[str]:
        """Title of the panel that wraps the table representation of the model."""
        return None

    def convert_to(
        self, model: Type[BaseModelType], extra: bool = False
    ) -> BaseModelType:
        """Converts the model to a different model type.

        By default, only fields that are defined in the destination model
        are included in the converted model.

        Parameters
        ----------
        model : Type[BaseModelType]
            The model type to convert to.
        extra : bool
            Whether to include fields that are not defined in the destination model.

        Returns
        -------
        BaseModelType
            The converted model.
        """
        # TODO: include mapping of source fields to destination fields
        # e.g. Project.name -> ProjectReq.project_name
        # pass in mapping: {"name": "project_name"}
        if extra:
            include = None
        else:
            include = model.get_model_fields()
        return model.model_validate(self.model_dump(include=include))

    @classmethod
    def get_model_fields(cls) -> Set[str]:
        """Get a list of the names of the model's fields.

        Returns
        -------
        List[str]
            The names of the model's fields.
        """
        return set(cls.model_fields)

    if rich_installed:

        def __rich_console__(
            self, console: Console, options: ConsoleOptions
        ) -> RenderResult:
            """Rich console representation of the model.
            Returns a panel containing tables representing the model's
            fields and values.
            If the model has a nested model, the nested model's table representation
            is printed after the main table.

            See: https://rich.readthedocs.io/en/latest/protocol.html#console-render
            """
            yield self.as_panel(with_description=False)

        def as_panel(self, title: Optional[str] = None, **kwargs: Any) -> Panel:
            """Returns table representation of model wrapped in a Panel.
            Passes all keyword arguments to `as_table`.

            Returns
            -------
            Panel
                A Rich panel containing the table representation of the model.
            """
            title = title or self.__rich_panel_title__
            return Panel(Group(*self.as_table(**kwargs)), title=title)

        def as_table(
            self,
            with_description: bool = False,
            max_depth: Optional[int] = None,
            parent_field: Optional[str] = None,
            _depth: int = 1,
        ) -> Iterable[Table]:
            """Returns a Rich table representation of the model, and any nested models.

            Parameters
            ----------
            with_description : bool
                Whether to include the description of the model fields.
            max_depth : Optional[int]
                The maximum depth to print nested models.
                `None` means no limit.
            parent_field : Optional[str]
                The title of the parent field that contains this model.
                Used when printing submodels.
            _depth : int
                DO NOT SET THIS.
                This is used internally to track the current depth level.

            Returns
            -------
            Iterable[Table]
                A generator of Rich tables representing the model and any nested models.
            """
            # VOCABULARY:
            # "field" -> a field in the model spec
            # "field name" -> the name of the field in the model spec
            # "submodel" -> a nested model
            # "submodel table" -> the table representation of a nested model

            # None and n <= 0 means no limit to recursion depth
            if max_depth is not None and max_depth <= 0:
                max_depth = None

            # TODO: add list index indicator for list fields
            if not parent_field:
                title = type(self).__qualname__
            else:
                title = f"{parent_field}"

            columns = [
                Column(
                    header="Field", justify="left", style="green", header_style="bold"
                ),
                Column(header="Value", style="blue", justify="left", overflow="fold"),
            ]
            if with_description:
                columns.append(
                    Column(header="Description", style="yellow", justify="left"),
                )

            table = Table(
                title=f"[bold]{title}[/bold]",
                title_style=DEPTH_TITLE_COLORS.get(_depth, "magenta"),
                title_justify="left",
                expand=True,
                *columns,
            )

            subtables = []  # type: list[Iterable[Table]]

            def add_submodel_table(field_title: str, submodel: "BaseModel") -> str:
                """Adds a submodel table to the subtables list."""
                if parent_field:
                    pfield = f"{parent_field}.{field_title}"
                else:
                    pfield = f"{type(self).__qualname__}.{field_title}"
                submodel_table = submodel.as_table(
                    with_description=with_description,
                    max_depth=max_depth,
                    _depth=_depth + 1,
                    parent_field=pfield,
                )
                subtables.append(submodel_table)
                return pfield

            # Iterate over self to get model fields + extra fields
            for field_name, value in super(BaseModel, self).__iter__():
                # Prioritize getting field info from __fields__ dict
                # since this dict contains more metadata for the field
                field = self.model_fields.get(field_name)
                if field is not None:
                    # Try to use field title if available
                    field_title = str(field.title or field_name)
                    # Get the field value
                    value = getattr(self, field_name)
                    description = str(field.description) or ""
                else:
                    field_title = field_name
                    description = ""

                submodels = []  # type: Iterable[BaseModel]
                if isinstance(value, BaseModel):
                    submodels = [value]
                elif isinstance(value, Iterable):
                    if all(isinstance(v, BaseModel) for v in value):
                        submodels = value

                # Only print the submodel table if we are not at the max depth
                # If we don't enter this, we print the string representation of the
                # submodel(s) in the main table.
                if submodels and (max_depth is None or _depth < max_depth):
                    # consume iterable immediately so we can get table title
                    # It's likely this is NOT a generator, but we don't want to
                    # assume that.
                    submodels = list(submodels)
                    table_title = ""
                    for submodel in submodels:
                        table_title = add_submodel_table(field_title, submodel)
                    value = f"[bold]See below ({table_title})[/bold]"

                row = [field_title, str(value)]
                if with_description:
                    row.append(description)
                table.add_row(*row)

            # TODO: sort table rows by field name

            yield table
            for subtable in subtables:
                yield from subtable

model_config = ConfigDict(extra='allow', validate_assignment=True, strict=False) class-attribute instance-attribute

__rich_table_title__: str property

The title to use for the table representation of the model. By default, the model's class name is be used.

__rich_panel_title__: Optional[str] property

Title of the panel that wraps the table representation of the model.

convert_to(model, extra=False)

Converts the model to a different model type.

By default, only fields that are defined in the destination model are included in the converted model.

Parameters:

Name Type Description Default
model Type[BaseModelType]

The model type to convert to.

required
extra bool

Whether to include fields that are not defined in the destination model.

False

Returns:

Type Description
BaseModelType

The converted model.

Source code in harborapi/models/base.py
def convert_to(
    self, model: Type[BaseModelType], extra: bool = False
) -> BaseModelType:
    """Converts the model to a different model type.

    By default, only fields that are defined in the destination model
    are included in the converted model.

    Parameters
    ----------
    model : Type[BaseModelType]
        The model type to convert to.
    extra : bool
        Whether to include fields that are not defined in the destination model.

    Returns
    -------
    BaseModelType
        The converted model.
    """
    # TODO: include mapping of source fields to destination fields
    # e.g. Project.name -> ProjectReq.project_name
    # pass in mapping: {"name": "project_name"}
    if extra:
        include = None
    else:
        include = model.get_model_fields()
    return model.model_validate(self.model_dump(include=include))

get_model_fields() classmethod

Get a list of the names of the model's fields.

Returns:

Type Description
List[str]

The names of the model's fields.

Source code in harborapi/models/base.py
@classmethod
def get_model_fields(cls) -> Set[str]:
    """Get a list of the names of the model's fields.

    Returns
    -------
    List[str]
        The names of the model's fields.
    """
    return set(cls.model_fields)

__rich_console__(console, options)

Rich console representation of the model. Returns a panel containing tables representing the model's fields and values. If the model has a nested model, the nested model's table representation is printed after the main table.

See: https://rich.readthedocs.io/en/latest/protocol.html#console-render

Source code in harborapi/models/base.py
def __rich_console__(
    self, console: Console, options: ConsoleOptions
) -> RenderResult:
    """Rich console representation of the model.
    Returns a panel containing tables representing the model's
    fields and values.
    If the model has a nested model, the nested model's table representation
    is printed after the main table.

    See: https://rich.readthedocs.io/en/latest/protocol.html#console-render
    """
    yield self.as_panel(with_description=False)

as_panel(title=None, **kwargs)

Returns table representation of model wrapped in a Panel. Passes all keyword arguments to as_table.

Returns:

Type Description
Panel

A Rich panel containing the table representation of the model.

Source code in harborapi/models/base.py
def as_panel(self, title: Optional[str] = None, **kwargs: Any) -> Panel:
    """Returns table representation of model wrapped in a Panel.
    Passes all keyword arguments to `as_table`.

    Returns
    -------
    Panel
        A Rich panel containing the table representation of the model.
    """
    title = title or self.__rich_panel_title__
    return Panel(Group(*self.as_table(**kwargs)), title=title)

as_table(with_description=False, max_depth=None, parent_field=None, _depth=1)

Returns a Rich table representation of the model, and any nested models.

Parameters:

Name Type Description Default
with_description bool

Whether to include the description of the model fields.

False
max_depth Optional[int]

The maximum depth to print nested models. None means no limit.

None
parent_field Optional[str]

The title of the parent field that contains this model. Used when printing submodels.

None
_depth int

DO NOT SET THIS. This is used internally to track the current depth level.

1

Returns:

Type Description
Iterable[Table]

A generator of Rich tables representing the model and any nested models.

Source code in harborapi/models/base.py
def as_table(
    self,
    with_description: bool = False,
    max_depth: Optional[int] = None,
    parent_field: Optional[str] = None,
    _depth: int = 1,
) -> Iterable[Table]:
    """Returns a Rich table representation of the model, and any nested models.

    Parameters
    ----------
    with_description : bool
        Whether to include the description of the model fields.
    max_depth : Optional[int]
        The maximum depth to print nested models.
        `None` means no limit.
    parent_field : Optional[str]
        The title of the parent field that contains this model.
        Used when printing submodels.
    _depth : int
        DO NOT SET THIS.
        This is used internally to track the current depth level.

    Returns
    -------
    Iterable[Table]
        A generator of Rich tables representing the model and any nested models.
    """
    # VOCABULARY:
    # "field" -> a field in the model spec
    # "field name" -> the name of the field in the model spec
    # "submodel" -> a nested model
    # "submodel table" -> the table representation of a nested model

    # None and n <= 0 means no limit to recursion depth
    if max_depth is not None and max_depth <= 0:
        max_depth = None

    # TODO: add list index indicator for list fields
    if not parent_field:
        title = type(self).__qualname__
    else:
        title = f"{parent_field}"

    columns = [
        Column(
            header="Field", justify="left", style="green", header_style="bold"
        ),
        Column(header="Value", style="blue", justify="left", overflow="fold"),
    ]
    if with_description:
        columns.append(
            Column(header="Description", style="yellow", justify="left"),
        )

    table = Table(
        title=f"[bold]{title}[/bold]",
        title_style=DEPTH_TITLE_COLORS.get(_depth, "magenta"),
        title_justify="left",
        expand=True,
        *columns,
    )

    subtables = []  # type: list[Iterable[Table]]

    def add_submodel_table(field_title: str, submodel: "BaseModel") -> str:
        """Adds a submodel table to the subtables list."""
        if parent_field:
            pfield = f"{parent_field}.{field_title}"
        else:
            pfield = f"{type(self).__qualname__}.{field_title}"
        submodel_table = submodel.as_table(
            with_description=with_description,
            max_depth=max_depth,
            _depth=_depth + 1,
            parent_field=pfield,
        )
        subtables.append(submodel_table)
        return pfield

    # Iterate over self to get model fields + extra fields
    for field_name, value in super(BaseModel, self).__iter__():
        # Prioritize getting field info from __fields__ dict
        # since this dict contains more metadata for the field
        field = self.model_fields.get(field_name)
        if field is not None:
            # Try to use field title if available
            field_title = str(field.title or field_name)
            # Get the field value
            value = getattr(self, field_name)
            description = str(field.description) or ""
        else:
            field_title = field_name
            description = ""

        submodels = []  # type: Iterable[BaseModel]
        if isinstance(value, BaseModel):
            submodels = [value]
        elif isinstance(value, Iterable):
            if all(isinstance(v, BaseModel) for v in value):
                submodels = value

        # Only print the submodel table if we are not at the max depth
        # If we don't enter this, we print the string representation of the
        # submodel(s) in the main table.
        if submodels and (max_depth is None or _depth < max_depth):
            # consume iterable immediately so we can get table title
            # It's likely this is NOT a generator, but we don't want to
            # assume that.
            submodels = list(submodels)
            table_title = ""
            for submodel in submodels:
                table_title = add_submodel_table(field_title, submodel)
            value = f"[bold]See below ({table_title})[/bold]"

        row = [field_title, str(value)]
        if with_description:
            row.append(description)
        table.add_row(*row)

    # TODO: sort table rows by field name

    yield table
    for subtable in subtables:
        yield from subtable

Scanner

Basic scanner properties such as name, vendor, and version.

Source code in harborapi/models/scanner.py
class Scanner(BaseModel):
    """
    Basic scanner properties such as name, vendor, and version.

    """

    name: Optional[str] = Field(
        default=None, description="The name of the scanner.", examples=["Trivy"]
    )
    vendor: Optional[str] = Field(
        default=None,
        description="The name of the scanner's provider.",
        examples=["Aqua Security"],
    )
    version: Optional[str] = Field(
        default=None, description="The version of the scanner.", examples=["0.4.0"]
    )

    @property
    def semver(self) -> SemVer:
        return get_semver(self.version)

name: Optional[str] = Field(default=None, description='The name of the scanner.', examples=['Trivy']) class-attribute instance-attribute

vendor: Optional[str] = Field(default=None, description="The name of the scanner's provider.", examples=['Aqua Security']) class-attribute instance-attribute

version: Optional[str] = Field(default=None, description='The version of the scanner.', examples=['0.4.0']) class-attribute instance-attribute

semver: SemVer property

ScannerProperties

A set of custom properties that can further describe capabilities of a given scanner.

Source code in harborapi/models/scanner.py
class ScannerProperties(StrDictRootModel[str]):
    """
    A set of custom properties that can further describe capabilities of a given scanner.

    """

    root: Optional[Dict[str, str]] = None

root: Optional[Dict[str, str]] = None class-attribute instance-attribute

ScannerCapability

Capability consists of the set of recognized artifact MIME types and the set of scanner report MIME types. For example, a scanner capable of analyzing Docker images and producing a vulnerabilities report recognizable by Harbor web console might be represented with the following capability: - consumes MIME types: - application/vnd.oci.image.manifest.v1+json - application/vnd.docker.distribution.manifest.v2+json - produces MIME types: - application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0

Source code in harborapi/models/scanner.py
class ScannerCapability(BaseModel):
    """
    Capability consists of the set of recognized artifact MIME types and the set of scanner report MIME types.
    For example, a scanner capable of analyzing Docker images and producing a vulnerabilities report recognizable
    by Harbor web console might be represented with the following capability:
    - consumes MIME types:
      - `application/vnd.oci.image.manifest.v1+json`
      - `application/vnd.docker.distribution.manifest.v2+json`
    - produces MIME types:
      - `application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0`

    """

    consumes_mime_types: List[str] = Field(
        ...,
        description='The set of MIME types of the artifacts supported by the scanner to produce the reports specified in the "produces_mime_types". A given\nmime type should only be present in one capability item.\n',
        examples=[
            [
                "application/vnd.oci.image.manifest.v1+json",
                "application/vnd.docker.distribution.manifest.v2+json",
            ]
        ],
    )
    produces_mime_types: List[str] = Field(
        ...,
        description="The set of MIME types of reports generated by the scanner for the consumes_mime_types of the same capability record.\n",
        examples=[
            ["application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"]
        ],
    )

consumes_mime_types: List[str] = Field(..., description='The set of MIME types of the artifacts supported by the scanner to produce the reports specified in the "produces_mime_types". A given\nmime type should only be present in one capability item.\n', examples=[['application/vnd.oci.image.manifest.v1+json', 'application/vnd.docker.distribution.manifest.v2+json']]) class-attribute instance-attribute

produces_mime_types: List[str] = Field(..., description='The set of MIME types of reports generated by the scanner for the consumes_mime_types of the same capability record.\n', examples=[['application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0']]) class-attribute instance-attribute

ScanRequestId

Source code in harborapi/models/scanner.py
class ScanRequestId(StrRootModel):
    root: str = Field(
        ...,
        description="A unique identifier returned by the [/scan](#/operation/AcceptScanRequest] operations. The format of the\nidentifier is not imposed but it should be unique enough to prevent collisons when polling for scan reports.\n",
        examples=["3fa85f64-5717-4562-b3fc-2c963f66afa6"],
    )

root: str = Field(..., description='A unique identifier returned by the [/scan](#/operation/AcceptScanRequest] operations. The format of the\nidentifier is not imposed but it should be unique enough to prevent collisons when polling for scan reports.\n', examples=['3fa85f64-5717-4562-b3fc-2c963f66afa6']) class-attribute instance-attribute

Registry

Source code in harborapi/models/scanner.py
class Registry(BaseModel):
    url: Optional[str] = Field(
        default=None,
        description="A base URL or the Docker Registry v2 API.",
        examples=["https://core.harbor.domain"],
    )
    authorization: Optional[str] = Field(
        default=None,
        description="An optional value of the HTTP Authorization header sent with each request to the Docker Registry v2 API.\nIt's used to exchange Base64 encoded robot account credentials to a short lived JWT access token which\nallows the underlying scanner to pull the artifact from the Docker Registry.\n",
        examples=["Basic BASE64_ENCODED_CREDENTIALS"],
    )

url: Optional[str] = Field(default=None, description='A base URL or the Docker Registry v2 API.', examples=['https://core.harbor.domain']) class-attribute instance-attribute

authorization: Optional[str] = Field(default=None, description="An optional value of the HTTP Authorization header sent with each request to the Docker Registry v2 API.\nIt's used to exchange Base64 encoded robot account credentials to a short lived JWT access token which\nallows the underlying scanner to pull the artifact from the Docker Registry.\n", examples=['Basic BASE64_ENCODED_CREDENTIALS']) class-attribute instance-attribute

Artifact

Source code in harborapi/models/scanner.py
class Artifact(BaseModel):
    repository: Optional[str] = Field(
        default=None,
        description="The name of the Docker Registry repository containing the artifact.",
        examples=["library/mongo"],
    )
    digest: Optional[str] = Field(
        default=None,
        description="The artifact's digest, consisting of an algorithm and hex portion.",
        examples=[
            "sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b"
        ],
    )
    tag: Optional[str] = Field(
        default=None, description="The artifact's tag", examples=["3.14-xenial"]
    )
    mime_type: Optional[str] = Field(
        default=None,
        description="The MIME type of the artifact.",
        examples=["application/vnd.docker.distribution.manifest.v2+json"],
    )

repository: Optional[str] = Field(default=None, description='The name of the Docker Registry repository containing the artifact.', examples=['library/mongo']) class-attribute instance-attribute

digest: Optional[str] = Field(default=None, description="The artifact's digest, consisting of an algorithm and hex portion.", examples=['sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b']) class-attribute instance-attribute

tag: Optional[str] = Field(default=None, description="The artifact's tag", examples=['3.14-xenial']) class-attribute instance-attribute

mime_type: Optional[str] = Field(default=None, description='The MIME type of the artifact.', examples=['application/vnd.docker.distribution.manifest.v2+json']) class-attribute instance-attribute

Severity

A standard scale for measuring the severity of a vulnerability.

  • Unknown - either a security problem that has not been assigned to a priority yet or a priority that the scanner did not recognize.
  • Negligible - technically a security problem, but is only theoretical in nature, requires a very special situation, has almost no install base, or does no real damage.
  • Low - a security problem, but is hard to exploit due to environment, requires a user-assisted attack, a small install base, or does very little damage.
  • Medium - a real security problem, and is exploitable for many people. Includes network daemon denial of service attacks, cross-site scripting, and gaining user privileges.
  • High - a real problem, exploitable for many people in a default installation. Includes serious remote denial of service, local root privilege escalations, or data loss.
  • Critical - a world-burning problem, exploitable for nearly all people in a default installation. Includes remote root privilege escalations, or massive data loss.
Source code in harborapi/models/scanner.py
class Severity(Enum):
    """
    A standard scale for measuring the severity of a vulnerability.

    * `Unknown` - either a security problem that has not been assigned to a priority yet or a priority that the
      scanner did not recognize.
    * `Negligible` - technically a security problem, but is only theoretical in nature, requires a very special
      situation, has almost no install base, or does no real damage.
    * `Low` - a security problem, but is hard to exploit due to environment, requires a user-assisted attack,
      a small install base, or does very little damage.
    * `Medium` - a real security problem, and is exploitable for many people. Includes network daemon denial of
      service attacks, cross-site scripting, and gaining user privileges.
    * `High` - a real problem, exploitable for many people in a default installation. Includes serious remote denial
      of service, local root privilege escalations, or data loss.
    * `Critical` - a world-burning problem, exploitable for nearly all people in a default installation. Includes
      remote root privilege escalations, or massive data loss.

    """

    unknown = "Unknown"
    negligible = "Negligible"
    low = "Low"
    medium = "Medium"
    high = "High"
    critical = "Critical"
    none = "None"

    def __gt__(self, other: Severity) -> bool:
        return SEVERITY_PRIORITY[self] > SEVERITY_PRIORITY[other]

    def __ge__(self, other: Severity) -> bool:
        return SEVERITY_PRIORITY[self] >= SEVERITY_PRIORITY[other]

    def __lt__(self, other: Severity) -> bool:
        return SEVERITY_PRIORITY[self] < SEVERITY_PRIORITY[other]

    def __le__(self, other: Severity) -> bool:
        return SEVERITY_PRIORITY[self] <= SEVERITY_PRIORITY[other]

unknown = 'Unknown' class-attribute instance-attribute

negligible = 'Negligible' class-attribute instance-attribute

low = 'Low' class-attribute instance-attribute

medium = 'Medium' class-attribute instance-attribute

high = 'High' class-attribute instance-attribute

critical = 'Critical' class-attribute instance-attribute

none = 'None' class-attribute instance-attribute

__gt__(other)

Source code in harborapi/models/scanner.py
def __gt__(self, other: Severity) -> bool:
    return SEVERITY_PRIORITY[self] > SEVERITY_PRIORITY[other]

__ge__(other)

Source code in harborapi/models/scanner.py
def __ge__(self, other: Severity) -> bool:
    return SEVERITY_PRIORITY[self] >= SEVERITY_PRIORITY[other]

__lt__(other)

Source code in harborapi/models/scanner.py
def __lt__(self, other: Severity) -> bool:
    return SEVERITY_PRIORITY[self] < SEVERITY_PRIORITY[other]

__le__(other)

Source code in harborapi/models/scanner.py
def __le__(self, other: Severity) -> bool:
    return SEVERITY_PRIORITY[self] <= SEVERITY_PRIORITY[other]

Error

Source code in harborapi/models/scanner.py
class Error(BaseModel):
    message: Optional[str] = Field(default=None, examples=["Some unexpected error"])

message: Optional[str] = Field(default=None, examples=['Some unexpected error']) class-attribute instance-attribute

CVSSDetails

Source code in harborapi/models/scanner.py
class CVSSDetails(BaseModel):
    score_v3: Optional[float] = Field(
        default=None,
        description="The CVSS 3.0 score for the vulnerability.\n",
        examples=[3.2],
    )
    score_v2: Optional[float] = Field(
        default=None, description="The CVSS 2.0 score for the vulnerability.\n"
    )
    vector_v3: Optional[str] = Field(
        default=None,
        description="The CVSS 3.0 vector for the vulnerability. \n",
        examples=["CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"],
    )
    vector_v2: Optional[str] = Field(
        default=None,
        description="The CVSS 2.0 vector for the vulnerability. The string is of the form AV:L/AC:M/Au:N/C:P/I:N/A:N\n",
        examples=["AV:N/AC:L/Au:N/C:N/I:N/A:P"],
    )

score_v3: Optional[float] = Field(default=None, description='The CVSS 3.0 score for the vulnerability.\n', examples=[3.2]) class-attribute instance-attribute

score_v2: Optional[float] = Field(default=None, description='The CVSS 2.0 score for the vulnerability.\n') class-attribute instance-attribute

vector_v3: Optional[str] = Field(default=None, description='The CVSS 3.0 vector for the vulnerability. \n', examples=['CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N']) class-attribute instance-attribute

vector_v2: Optional[str] = Field(default=None, description='The CVSS 2.0 vector for the vulnerability. The string is of the form AV:L/AC:M/Au:N/C:P/I:N/A:N\n', examples=['AV:N/AC:L/Au:N/C:N/I:N/A:P']) class-attribute instance-attribute

ScannerAdapterMetadata

Represents metadata of a Scanner Adapter which allows Harbor to lookup a scanner capable of scanning a given Artifact stored in its registry and making sure that it can interpret a returned result.

Source code in harborapi/models/scanner.py
class ScannerAdapterMetadata(BaseModel):
    """
    Represents metadata of a Scanner Adapter which allows Harbor to lookup a scanner capable
    of scanning a given Artifact stored in its registry and making sure that it
    can interpret a returned result.

    """

    scanner: Scanner
    capabilities: List[ScannerCapability]
    properties: Optional[ScannerProperties] = None

scanner: Scanner instance-attribute

capabilities: List[ScannerCapability] instance-attribute

properties: Optional[ScannerProperties] = None class-attribute instance-attribute

ScanRequest

Source code in harborapi/models/scanner.py
class ScanRequest(BaseModel):
    registry: Registry
    artifact: Artifact

registry: Registry instance-attribute

artifact: Artifact instance-attribute

ScanResponse

Source code in harborapi/models/scanner.py
class ScanResponse(BaseModel):
    id: ScanRequestId

id: ScanRequestId instance-attribute

VulnerabilityItem

Source code in harborapi/models/scanner.py
class VulnerabilityItem(BaseModel):
    id: Optional[str] = Field(
        default=None,
        description="The unique identifier of the vulnerability.",
        examples=["CVE-2017-8283"],
    )
    package: Optional[str] = Field(
        default=None,
        description="An operating system package containing the vulnerability.\n",
        examples=["dpkg"],
    )
    version: Optional[str] = Field(
        default=None,
        description="The version of the package containing the vulnerability.\n",
        examples=["1.17.27"],
    )
    fix_version: Optional[str] = Field(
        default=None,
        description="The version of the package containing the fix if available.\n",
        examples=["1.18.0"],
    )
    severity: Severity = Field(
        Severity.unknown, description="The severity of the vulnerability."
    )
    description: Optional[str] = Field(
        default=None,
        description="The detailed description of the vulnerability.\n",
        examples=[
            "dpkg-source in dpkg 1.3.0 through 1.18.23 is able to use a non-GNU patch program\nand does not offer a protection mechanism for blank-indented diff hunks, which\nallows remote attackers to conduct directory traversal attacks via a crafted\nDebian source package, as demonstrated by using of dpkg-source on NetBSD.\n"
        ],
    )
    links: Optional[List[str]] = Field(
        None,
        description="The list of links to the upstream databases with the full description of the vulnerability.\n",
    )
    preferred_cvss: Optional[CVSSDetails] = None
    cwe_ids: Optional[List[str]] = Field(
        default=None,
        description="The Common Weakness Enumeration Identifiers associated with this vulnerability.\n",
        examples=[["CWE-476"]],
    )
    vendor_attributes: Optional[Dict[str, Any]] = None

    @field_validator("severity", mode="before")
    @classmethod
    def _severity_none_is_default(
        cls, v: Optional[Severity], info: ValidationInfo
    ) -> Severity:
        if not info.field_name:
            raise ValueError("Validator is not attached to a field.")
        return v or cls.model_fields[info.field_name].default

    @property
    def semver(self) -> SemVer:
        return get_semver(self.version)

    @property
    def fixable(self) -> bool:
        return bool(self.fix_version)

    def get_cvss_score(
        self,
        scanner: Union[Optional[Scanner], str] = "Trivy",
        version: int = 3,
        vendor_priority: Optional[Iterable[str]] = None,
        default: float = 0.0,
    ) -> float:
        """The default scanner Trivy, as of version 0.29.1, does not use the
        preferred_cvss field.

        In order to not tightly couple this method with a specific scanner,
        we use the scanner name to determine how to retrieve the CVSS score.

        Forward compatibility is in place in the event that Trivy starts
        conforming to the spec.
        """
        if vendor_priority is None:
            vendor_priority = DEFAULT_VENDORS
        if self.preferred_cvss is not None:
            if version == 3 and self.preferred_cvss.score_v3 is not None:
                return self.preferred_cvss.score_v3
            elif version == 2 and self.preferred_cvss.score_v2 is not None:
                return self.preferred_cvss.score_v2
        if not scanner:
            return default
        if isinstance(scanner, str):
            scanner_name = scanner
        elif isinstance(scanner, Scanner):
            scanner_name = scanner.name or ""
        if scanner_name.lower() == "trivy":
            return self._get_trivy_cvss_score(
                version=version, vendor_priority=vendor_priority, default=default
            )
        return default

    def _get_trivy_cvss_score(
        self, version: int, vendor_priority: Iterable[str], default: float = 0.0
    ) -> float:
        if self.vendor_attributes is None:
            return default
        cvss_data = self.vendor_attributes.get("CVSS", {})
        if not cvss_data:
            return default
        for prio in vendor_priority:
            vendor_cvss = cvss_data.get(prio, {})
            if not vendor_cvss:
                continue
            elif not isinstance(vendor_cvss, dict):
                logger.warning("Received malformed vendor CVSS data: %s", vendor_cvss)
                continue
            score = vendor_cvss.get(f"V{version}Score", default)
            if isinstance(score, (int, float)):
                return float(score)
            else:
                logger.error(
                    "Received non-float value for vendor CVSS V%dScore: %s",
                    version,
                    score,
                )
        return default

    def get_severity(
        self,
        scanner: Union[Optional[Scanner], str] = "Trivy",
        vendor_priority: Optional[Iterable[str]] = None,
    ) -> Severity:
        """Returns the CVSS V3 severity of the vulnerability based on a specific vendor.
        If no vendor is specified, the default vendor priority is used (NVD over RedHat).

        With Trivy 0.29.1, the `severity` field is based on the Red Hat vulnerability rating.
        This attempts to return the severity based on a user-provided vendor priority.

        TODO: improve documentation for the what and why of this method
        """
        cvss_score = self.get_cvss_score(
            scanner=scanner, vendor_priority=vendor_priority
        )
        if cvss_score >= 9.0:
            return Severity.critical
        elif cvss_score >= 7.0:
            return Severity.high
        elif cvss_score >= 4.0:
            return Severity.medium
        elif cvss_score >= 0.1:
            return Severity.low
        else:
            return Severity.negligible

    def get_severity_highest(
        self,
        scanner: Union[Optional[Scanner], str] = "Trivy",
        vendors: Optional[Iterable[str]] = None,
    ) -> Severity:
        """Attempts to find the highest severity of the vulnerability based on a specific vendor."""
        if vendors is None:
            vendors = DEFAULT_VENDORS
        severities = [
            self.get_severity(scanner=scanner, vendor_priority=[v]) for v in vendors
        ]
        if self.severity is not None:
            severities.append(self.severity)
        return most_severe(severities)

id: Optional[str] = Field(default=None, description='The unique identifier of the vulnerability.', examples=['CVE-2017-8283']) class-attribute instance-attribute

package: Optional[str] = Field(default=None, description='An operating system package containing the vulnerability.\n', examples=['dpkg']) class-attribute instance-attribute

version: Optional[str] = Field(default=None, description='The version of the package containing the vulnerability.\n', examples=['1.17.27']) class-attribute instance-attribute

fix_version: Optional[str] = Field(default=None, description='The version of the package containing the fix if available.\n', examples=['1.18.0']) class-attribute instance-attribute

severity: Severity = Field(Severity.unknown, description='The severity of the vulnerability.') class-attribute instance-attribute

description: Optional[str] = Field(default=None, description='The detailed description of the vulnerability.\n', examples=['dpkg-source in dpkg 1.3.0 through 1.18.23 is able to use a non-GNU patch program\nand does not offer a protection mechanism for blank-indented diff hunks, which\nallows remote attackers to conduct directory traversal attacks via a crafted\nDebian source package, as demonstrated by using of dpkg-source on NetBSD.\n']) class-attribute instance-attribute

preferred_cvss: Optional[CVSSDetails] = None class-attribute instance-attribute

cwe_ids: Optional[List[str]] = Field(default=None, description='The Common Weakness Enumeration Identifiers associated with this vulnerability.\n', examples=[['CWE-476']]) class-attribute instance-attribute

vendor_attributes: Optional[Dict[str, Any]] = None class-attribute instance-attribute

semver: SemVer property

fixable: bool property

get_cvss_score(scanner='Trivy', version=3, vendor_priority=None, default=0.0)

The default scanner Trivy, as of version 0.29.1, does not use the preferred_cvss field.

In order to not tightly couple this method with a specific scanner, we use the scanner name to determine how to retrieve the CVSS score.

Forward compatibility is in place in the event that Trivy starts conforming to the spec.

Source code in harborapi/models/scanner.py
def get_cvss_score(
    self,
    scanner: Union[Optional[Scanner], str] = "Trivy",
    version: int = 3,
    vendor_priority: Optional[Iterable[str]] = None,
    default: float = 0.0,
) -> float:
    """The default scanner Trivy, as of version 0.29.1, does not use the
    preferred_cvss field.

    In order to not tightly couple this method with a specific scanner,
    we use the scanner name to determine how to retrieve the CVSS score.

    Forward compatibility is in place in the event that Trivy starts
    conforming to the spec.
    """
    if vendor_priority is None:
        vendor_priority = DEFAULT_VENDORS
    if self.preferred_cvss is not None:
        if version == 3 and self.preferred_cvss.score_v3 is not None:
            return self.preferred_cvss.score_v3
        elif version == 2 and self.preferred_cvss.score_v2 is not None:
            return self.preferred_cvss.score_v2
    if not scanner:
        return default
    if isinstance(scanner, str):
        scanner_name = scanner
    elif isinstance(scanner, Scanner):
        scanner_name = scanner.name or ""
    if scanner_name.lower() == "trivy":
        return self._get_trivy_cvss_score(
            version=version, vendor_priority=vendor_priority, default=default
        )
    return default

get_severity(scanner='Trivy', vendor_priority=None)

Returns the CVSS V3 severity of the vulnerability based on a specific vendor. If no vendor is specified, the default vendor priority is used (NVD over RedHat).

With Trivy 0.29.1, the severity field is based on the Red Hat vulnerability rating. This attempts to return the severity based on a user-provided vendor priority.

TODO: improve documentation for the what and why of this method

Source code in harborapi/models/scanner.py
def get_severity(
    self,
    scanner: Union[Optional[Scanner], str] = "Trivy",
    vendor_priority: Optional[Iterable[str]] = None,
) -> Severity:
    """Returns the CVSS V3 severity of the vulnerability based on a specific vendor.
    If no vendor is specified, the default vendor priority is used (NVD over RedHat).

    With Trivy 0.29.1, the `severity` field is based on the Red Hat vulnerability rating.
    This attempts to return the severity based on a user-provided vendor priority.

    TODO: improve documentation for the what and why of this method
    """
    cvss_score = self.get_cvss_score(
        scanner=scanner, vendor_priority=vendor_priority
    )
    if cvss_score >= 9.0:
        return Severity.critical
    elif cvss_score >= 7.0:
        return Severity.high
    elif cvss_score >= 4.0:
        return Severity.medium
    elif cvss_score >= 0.1:
        return Severity.low
    else:
        return Severity.negligible

get_severity_highest(scanner='Trivy', vendors=None)

Attempts to find the highest severity of the vulnerability based on a specific vendor.

Source code in harborapi/models/scanner.py
def get_severity_highest(
    self,
    scanner: Union[Optional[Scanner], str] = "Trivy",
    vendors: Optional[Iterable[str]] = None,
) -> Severity:
    """Attempts to find the highest severity of the vulnerability based on a specific vendor."""
    if vendors is None:
        vendors = DEFAULT_VENDORS
    severities = [
        self.get_severity(scanner=scanner, vendor_priority=[v]) for v in vendors
    ]
    if self.severity is not None:
        severities.append(self.severity)
    return most_severe(severities)

ErrorResponse

Source code in harborapi/models/scanner.py
class ErrorResponse(BaseModel):
    error: Optional[Error] = None

error: Optional[Error] = None class-attribute instance-attribute

HarborVulnerabilityReport

Source code in harborapi/models/scanner.py
class HarborVulnerabilityReport(BaseModel):
    generated_at: Optional[AwareDatetime] = Field(
        None, description="The time the vulnerability report was generated."
    )
    artifact: Optional[Artifact] = Field(
        default=None, description="The scanned artifact."
    )
    scanner: Optional[Scanner] = Field(
        default=None, description="The scanner used to generate the report."
    )
    severity: Optional[Severity] = Field(
        default=Severity.unknown,
        description="The overall severity of the vulnerabilities.",
    )
    vulnerabilities: List[VulnerabilityItem] = Field(
        default_factory=list, description="The list of vulnerabilities found."
    )
    model_config = ConfigDict(ignored_types=(cached_property,))

    @field_validator("severity", mode="before")
    @classmethod
    def _severity_none_is_default(
        cls, v: Optional[Severity], info: ValidationInfo
    ) -> Severity:
        if not info.field_name:
            raise ValueError("Validator is not attached to a field.")
        return v or cls.model_fields[info.field_name].default

    def __repr__(self) -> str:
        return f"HarborVulnerabilityReport(generated_at={self.generated_at}, artifact={self.artifact}, scanner={self.scanner}, severity={self.severity}, vulnerabilities=list(len={len(self.vulnerabilities)}))"

    @property
    def fixable(self) -> List[VulnerabilityItem]:
        return [v for v in self.vulnerabilities if v.fixable]

    @property
    def unfixable(self) -> List[VulnerabilityItem]:
        return [v for v in self.vulnerabilities if not v.fixable]

    @property
    def critical(self) -> List[VulnerabilityItem]:
        return self.vulnerabilities_by_severity(Severity.critical)

    @property
    def high(self) -> List[VulnerabilityItem]:
        return self.vulnerabilities_by_severity(Severity.high)

    @property
    def medium(self) -> List[VulnerabilityItem]:
        return self.vulnerabilities_by_severity(Severity.medium)

    @property
    def low(self) -> List[VulnerabilityItem]:
        return self.vulnerabilities_by_severity(Severity.low)

    @property
    def distribution(self) -> Counter[Severity]:
        dist: typing.Counter[Severity] = Counter()
        for vulnerability in self.vulnerabilities:
            if vulnerability.severity:
                dist[vulnerability.severity] += 1
        return dist

    def vulnerabilities_by_severity(
        self, severity: Severity
    ) -> List[VulnerabilityItem]:
        return [v for v in self.vulnerabilities if v.severity == severity]

    def sort(self, descending: bool = True, use_cvss: bool = False) -> None:
        """Sorts the vulnerabilities by severity in place.

        A wrapper around `vulnerabilities.sort` that sorts by severity,
        then optionally by CVSS score to break ties.

        Parameters
        ----------
        descending : bool, optional
            Whether to sort in descending order, by default True
            Equivalent to `reverse=True` in `sorted()`.
        use_cvss : bool, optional
            Whether to use CVSS score to determine sorting order
            when items have identical severity, by default False
            This is somewhat experimental and may be removed in the future.
        """

        def cmp(v1: VulnerabilityItem, v2: VulnerabilityItem) -> int:
            if v1.severity > v2.severity:
                return 1
            elif v1.severity < v2.severity:
                return -1
            if not use_cvss:
                return 0
            diff = v1.get_cvss_score(self.scanner) - v2.get_cvss_score(self.scanner)
            if diff > 0:
                return 1
            elif diff < 0:
                return -1
            return 0

        self.vulnerabilities.sort(key=functools.cmp_to_key(cmp), reverse=descending)

    @cached_property
    def cvss_scores(self) -> List[float]:
        """Returns a list of CVSS scores for each vulnerability.
        Vulnerabilities with a score of `None` are omitted.

        Returns
        ----
        List[Optional[float]]
            A list of CVSS scores for each vulnerability.
        """
        return list(
            filter(None, [v.get_cvss_score(self.scanner) for v in self.vulnerabilities])
        )

    def top_vulns(self, n: int = 5, fixable: bool = False) -> List[VulnerabilityItem]:
        """Returns the n most severe vulnerabilities.


        Parameters
        ----------
        n : int
            The maximum number of vulnerabilities to return.
        fixable : bool
            If `True`, only vulnerabilities with a fix version are returned.

        Returns
        -------
        List[VulnerabilityItem]
            The n most severe vulnerabilities.

        """
        vulns: Iterable[VulnerabilityItem] = []
        if fixable:
            vulns = self.fixable
        else:
            vulns = self.vulnerabilities
        vulns = filter(lambda v: v.get_cvss_score(self.scanner) is not None, vulns)
        return sorted(
            vulns, key=lambda v: v.get_cvss_score(self.scanner), reverse=True
        )[:n]

    def has_cve(self, cve_id: str, case_sensitive: bool = False) -> bool:
        """Whether or not the report contains a vulnerability with the given CVE ID.

        Parameters
        ----------
        cve_id : str
            The CVE ID to search for.

        Returns
        -------
        bool
            Report contains the a vulnerability with the given CVE ID.
        """
        return self.vuln_with_cve(cve_id, case_sensitive) is not None

    def has_description(self, description: str, case_sensitive: bool = False) -> bool:
        """Whether or not the report contains a vulnerability whose description contains the given string.

        Parameters
        ----------
        description : str
            The string to search for in the descriptions.
        case_sensitive : bool
            Case sensitive search, by default False

        Returns
        -------
        bool
            The report contains a vulnerability whose description contains the given string.
        """
        for _ in self.vulns_with_description(description, case_sensitive):
            return True
        return False

    def has_package(self, package: str, case_sensitive: bool = False) -> bool:
        """Whether or not the report contains a vulnerability affecting the given package.

        Parameters
        ----------
        package : str
            Name of the package to search for.
        case_sensitive : bool
            Case sensitive search, by default False

        Returns
        -------
        bool
            The given package is affected by a vulnerability in the report.
        """
        for _ in self.vulns_with_package(package, case_sensitive):
            return True
        return False

    def vuln_with_cve(
        self, cve: str, case_sensitive: bool = False
    ) -> Optional[VulnerabilityItem]:
        """Returns a vulnerability with the specified CVE ID if it exists in the report.

        Parameters
        ----------
        cve : str
            The CVE ID of the vulnerability to return.
        case_sensitive : bool
            Case sensitive search, by default False

        Returns
        -------
        Optional[VulnerabilityItem]
            A vulnerability with the specified CVE ID if it exists, otherwise `None`.
        """
        for vuln in self.vulnerabilities:
            if vuln.id is None:
                continue
            vuln_id = vuln.id
            if not case_sensitive:
                vuln_id = vuln.id.lower()
                cve = cve.lower()
            if vuln_id == cve:
                return vuln
        return None

    def vulns_with_package(
        self, package: str, case_sensitive: bool = False
    ) -> Iterable[VulnerabilityItem]:
        """Generator that yields all vulnerabilities that affect the given package.

        Parameters
        ----------
        package : str
            The package name to search for.
        case_sensitive : bool
            Case sensitive search, by default False

        Yields
        ------
        VulnerabilityItem
            Vulnerability that affects the given package.
        """
        for vuln in self.vulnerabilities:
            if vuln.package is None:
                continue
            vuln_package = vuln.package
            if not case_sensitive:
                vuln_package = vuln.package.lower()
                package = package.lower()
            if vuln_package == package:
                yield vuln

    def vulns_with_description(
        self, description: str, case_sensitive: bool = False
    ) -> Iterable[VulnerabilityItem]:
        """Generator that yields all vulnerabilities whose description contains the given string.

        Parameters
        ----------
        description : str
            The string to search for in vulnerability descriptions.
        case_sensitive : bool
            Case sensitive search, by default False

        Yields
        ------
        VulnerabilityItem
            Vulnerability whose description contains the given string.
        """
        for vuln in self.vulnerabilities:
            if vuln.description is None:
                continue
            vuln_description = vuln.description
            if not case_sensitive:
                description = description.lower()
                vuln_description = vuln_description.lower()
            if description in vuln_description:
                yield vuln

generated_at: Optional[AwareDatetime] = Field(None, description='The time the vulnerability report was generated.') class-attribute instance-attribute

artifact: Optional[Artifact] = Field(default=None, description='The scanned artifact.') class-attribute instance-attribute

scanner: Optional[Scanner] = Field(default=None, description='The scanner used to generate the report.') class-attribute instance-attribute

severity: Optional[Severity] = Field(default=Severity.unknown, description='The overall severity of the vulnerabilities.') class-attribute instance-attribute

vulnerabilities: List[VulnerabilityItem] = Field(default_factory=list, description='The list of vulnerabilities found.') class-attribute instance-attribute

model_config = ConfigDict(ignored_types=(cached_property)) class-attribute instance-attribute

fixable: List[VulnerabilityItem] property

unfixable: List[VulnerabilityItem] property

critical: List[VulnerabilityItem] property

high: List[VulnerabilityItem] property

medium: List[VulnerabilityItem] property

low: List[VulnerabilityItem] property

distribution: Counter[Severity] property

cvss_scores: List[float] cached property

Returns a list of CVSS scores for each vulnerability. Vulnerabilities with a score of None are omitted.

Returns:

Type Description
List[Optional[float]]

A list of CVSS scores for each vulnerability.

__repr__()

Source code in harborapi/models/scanner.py
def __repr__(self) -> str:
    return f"HarborVulnerabilityReport(generated_at={self.generated_at}, artifact={self.artifact}, scanner={self.scanner}, severity={self.severity}, vulnerabilities=list(len={len(self.vulnerabilities)}))"

vulnerabilities_by_severity(severity)

Source code in harborapi/models/scanner.py
def vulnerabilities_by_severity(
    self, severity: Severity
) -> List[VulnerabilityItem]:
    return [v for v in self.vulnerabilities if v.severity == severity]

sort(descending=True, use_cvss=False)

Sorts the vulnerabilities by severity in place.

A wrapper around vulnerabilities.sort that sorts by severity, then optionally by CVSS score to break ties.

Parameters:

Name Type Description Default
descending bool

Whether to sort in descending order, by default True Equivalent to reverse=True in sorted().

True
use_cvss bool

Whether to use CVSS score to determine sorting order when items have identical severity, by default False This is somewhat experimental and may be removed in the future.

False
Source code in harborapi/models/scanner.py
def sort(self, descending: bool = True, use_cvss: bool = False) -> None:
    """Sorts the vulnerabilities by severity in place.

    A wrapper around `vulnerabilities.sort` that sorts by severity,
    then optionally by CVSS score to break ties.

    Parameters
    ----------
    descending : bool, optional
        Whether to sort in descending order, by default True
        Equivalent to `reverse=True` in `sorted()`.
    use_cvss : bool, optional
        Whether to use CVSS score to determine sorting order
        when items have identical severity, by default False
        This is somewhat experimental and may be removed in the future.
    """

    def cmp(v1: VulnerabilityItem, v2: VulnerabilityItem) -> int:
        if v1.severity > v2.severity:
            return 1
        elif v1.severity < v2.severity:
            return -1
        if not use_cvss:
            return 0
        diff = v1.get_cvss_score(self.scanner) - v2.get_cvss_score(self.scanner)
        if diff > 0:
            return 1
        elif diff < 0:
            return -1
        return 0

    self.vulnerabilities.sort(key=functools.cmp_to_key(cmp), reverse=descending)

top_vulns(n=5, fixable=False)

Returns the n most severe vulnerabilities.

Parameters:

Name Type Description Default
n int

The maximum number of vulnerabilities to return.

5
fixable bool

If True, only vulnerabilities with a fix version are returned.

False

Returns:

Type Description
List[VulnerabilityItem]

The n most severe vulnerabilities.

Source code in harborapi/models/scanner.py
def top_vulns(self, n: int = 5, fixable: bool = False) -> List[VulnerabilityItem]:
    """Returns the n most severe vulnerabilities.


    Parameters
    ----------
    n : int
        The maximum number of vulnerabilities to return.
    fixable : bool
        If `True`, only vulnerabilities with a fix version are returned.

    Returns
    -------
    List[VulnerabilityItem]
        The n most severe vulnerabilities.

    """
    vulns: Iterable[VulnerabilityItem] = []
    if fixable:
        vulns = self.fixable
    else:
        vulns = self.vulnerabilities
    vulns = filter(lambda v: v.get_cvss_score(self.scanner) is not None, vulns)
    return sorted(
        vulns, key=lambda v: v.get_cvss_score(self.scanner), reverse=True
    )[:n]

has_cve(cve_id, case_sensitive=False)

Whether or not the report contains a vulnerability with the given CVE ID.

Parameters:

Name Type Description Default
cve_id str

The CVE ID to search for.

required

Returns:

Type Description
bool

Report contains the a vulnerability with the given CVE ID.

Source code in harborapi/models/scanner.py
def has_cve(self, cve_id: str, case_sensitive: bool = False) -> bool:
    """Whether or not the report contains a vulnerability with the given CVE ID.

    Parameters
    ----------
    cve_id : str
        The CVE ID to search for.

    Returns
    -------
    bool
        Report contains the a vulnerability with the given CVE ID.
    """
    return self.vuln_with_cve(cve_id, case_sensitive) is not None

has_description(description, case_sensitive=False)

Whether or not the report contains a vulnerability whose description contains the given string.

Parameters:

Name Type Description Default
description str

The string to search for in the descriptions.

required
case_sensitive bool

Case sensitive search, by default False

False

Returns:

Type Description
bool

The report contains a vulnerability whose description contains the given string.

Source code in harborapi/models/scanner.py
def has_description(self, description: str, case_sensitive: bool = False) -> bool:
    """Whether or not the report contains a vulnerability whose description contains the given string.

    Parameters
    ----------
    description : str
        The string to search for in the descriptions.
    case_sensitive : bool
        Case sensitive search, by default False

    Returns
    -------
    bool
        The report contains a vulnerability whose description contains the given string.
    """
    for _ in self.vulns_with_description(description, case_sensitive):
        return True
    return False

has_package(package, case_sensitive=False)

Whether or not the report contains a vulnerability affecting the given package.

Parameters:

Name Type Description Default
package str

Name of the package to search for.

required
case_sensitive bool

Case sensitive search, by default False

False

Returns:

Type Description
bool

The given package is affected by a vulnerability in the report.

Source code in harborapi/models/scanner.py
def has_package(self, package: str, case_sensitive: bool = False) -> bool:
    """Whether or not the report contains a vulnerability affecting the given package.

    Parameters
    ----------
    package : str
        Name of the package to search for.
    case_sensitive : bool
        Case sensitive search, by default False

    Returns
    -------
    bool
        The given package is affected by a vulnerability in the report.
    """
    for _ in self.vulns_with_package(package, case_sensitive):
        return True
    return False

vuln_with_cve(cve, case_sensitive=False)

Returns a vulnerability with the specified CVE ID if it exists in the report.

Parameters:

Name Type Description Default
cve str

The CVE ID of the vulnerability to return.

required
case_sensitive bool

Case sensitive search, by default False

False

Returns:

Type Description
Optional[VulnerabilityItem]

A vulnerability with the specified CVE ID if it exists, otherwise None.

Source code in harborapi/models/scanner.py
def vuln_with_cve(
    self, cve: str, case_sensitive: bool = False
) -> Optional[VulnerabilityItem]:
    """Returns a vulnerability with the specified CVE ID if it exists in the report.

    Parameters
    ----------
    cve : str
        The CVE ID of the vulnerability to return.
    case_sensitive : bool
        Case sensitive search, by default False

    Returns
    -------
    Optional[VulnerabilityItem]
        A vulnerability with the specified CVE ID if it exists, otherwise `None`.
    """
    for vuln in self.vulnerabilities:
        if vuln.id is None:
            continue
        vuln_id = vuln.id
        if not case_sensitive:
            vuln_id = vuln.id.lower()
            cve = cve.lower()
        if vuln_id == cve:
            return vuln
    return None

vulns_with_package(package, case_sensitive=False)

Generator that yields all vulnerabilities that affect the given package.

Parameters:

Name Type Description Default
package str

The package name to search for.

required
case_sensitive bool

Case sensitive search, by default False

False

Yields:

Type Description
VulnerabilityItem

Vulnerability that affects the given package.

Source code in harborapi/models/scanner.py
def vulns_with_package(
    self, package: str, case_sensitive: bool = False
) -> Iterable[VulnerabilityItem]:
    """Generator that yields all vulnerabilities that affect the given package.

    Parameters
    ----------
    package : str
        The package name to search for.
    case_sensitive : bool
        Case sensitive search, by default False

    Yields
    ------
    VulnerabilityItem
        Vulnerability that affects the given package.
    """
    for vuln in self.vulnerabilities:
        if vuln.package is None:
            continue
        vuln_package = vuln.package
        if not case_sensitive:
            vuln_package = vuln.package.lower()
            package = package.lower()
        if vuln_package == package:
            yield vuln

vulns_with_description(description, case_sensitive=False)

Generator that yields all vulnerabilities whose description contains the given string.

Parameters:

Name Type Description Default
description str

The string to search for in vulnerability descriptions.

required
case_sensitive bool

Case sensitive search, by default False

False

Yields:

Type Description
VulnerabilityItem

Vulnerability whose description contains the given string.

Source code in harborapi/models/scanner.py
def vulns_with_description(
    self, description: str, case_sensitive: bool = False
) -> Iterable[VulnerabilityItem]:
    """Generator that yields all vulnerabilities whose description contains the given string.

    Parameters
    ----------
    description : str
        The string to search for in vulnerability descriptions.
    case_sensitive : bool
        Case sensitive search, by default False

    Yields
    ------
    VulnerabilityItem
        Vulnerability whose description contains the given string.
    """
    for vuln in self.vulnerabilities:
        if vuln.description is None:
            continue
        vuln_description = vuln.description
        if not case_sensitive:
            description = description.lower()
            vuln_description = vuln_description.lower()
        if description in vuln_description:
            yield vuln

get_semver(version)

Source code in harborapi/version.py
def get_semver(version: Optional[VersionType]) -> SemVer:
    if isinstance(version, SemVer):
        return version
    elif isinstance(version, tuple):
        for i, v in enumerate(version):
            # first 3 values are major, minor, patch
            if i <= 2:
                if not isinstance(v, int):
                    raise ValueError(
                        f"Version tuple must contain integers, got {version}"
                    )
                elif v < 0:
                    raise ValueError(
                        f"Version tuple must contain positive integers, got {version}"
                    )
            try:
                return SemVer(*version)
            except Exception as e:
                raise ValueError(f"Invalid version {version}: {e}")
        else:
            raise ValueError(f"Invalid semver tuple: {version}")
    elif isinstance(version, int):
        # Return SemVer with major version only if version is an integer
        return SemVer(version)
    elif version is None:
        # Return empty SemVer if version is None or empty string
        return SemVer(0, 0, 0)

    # Otherwise, parse the version string
    parts = version.split(".", 2)
    major, minor, patch = 0, 0, 0
    prerelease = None
    build = None
    if len(parts) > 0:
        major = clean_version_number(parts[0])
    if len(parts) > 1:
        minor = clean_version_number(parts[1])
    # Patch + prerelease + build
    if len(parts) > 2:
        patch_str = parts[2]
        # Get prerelease (if exists)
        try:
            patch_str, prerelease = patch_str.split("-", 1)
        except:  # noqa: E722
            pass
        # Get build info (if exists)
        try:
            if prerelease:
                prerelease, build = prerelease.split("+", 1)
            else:
                patch_str, build = patch_str.split("+", 1)
        except:  # noqa: E722
            pass
        patch = clean_version_number(patch_str)

    return SemVer(major, minor, patch, prerelease, build)

most_severe(severities)

Returns the highest severity in a list of severities.

Source code in harborapi/models/scanner.py
def most_severe(severities: Iterable[Severity]) -> Severity:
    """Returns the highest severity in a list of severities."""
    return max(severities, key=lambda x: SEVERITY_PRIORITY[x], default=Severity.unknown)

sort_distribution(distribution)

Turn a counter of Severities into a sorted list of (severity, count) tuples.

Source code in harborapi/models/scanner.py
def sort_distribution(distribution: "Counter[Severity]") -> List[Tuple[Severity, int]]:
    """Turn a counter of Severities into a sorted list of (severity, count) tuples."""
    return [
        (k, v)
        for k, v in sorted(distribution.items(), key=lambda x: SEVERITY_PRIORITY[x[0]])
    ]