Source code for repository_service_tuf_api.common_models

# SPDX-FileCopyrightText: 2023-2024 Repository Service for TUF Contributors
# SPDX-FileCopyrightText: 2022-2023 VMware Inc
#
# SPDX-License-Identifier: MIT

from enum import Enum
from typing import Any, Dict, List, Literal, Optional

from pydantic import BaseModel, ConfigDict, Field, model_validator

from repository_service_tuf_api import settings_repository


[docs] class Roles(Enum): ROOT = "root" TARGETS = "targets" SNAPSHOT = "snapshot" TIMESTAMP = "timestamp" BINS = "bins"
[docs] @staticmethod def is_role(input: Any) -> bool: if not isinstance(input, str): return False return any(input == role.value for role in Roles)
[docs] @staticmethod def all_str() -> str: return "root, targets, snapshot, timestamp and bins"
[docs] @staticmethod def values() -> List[str]: return Literal["root", "targets", "snapshot", "timestamp", "bins"]
[docs] @staticmethod def online_roles_values() -> List[str]: online_roles: List[str] = ["snapshot", "timestamp"] if settings_repository.get_fresh("TARGETS_ONLINE_KEY", True): online_roles.append("targets") delegated_roles: List[str] = settings_repository.get_fresh( "DELEGATED_ROLES_NAMES" ) # All delegated roles names should start with "bins" if we are using # hash bin delegation and none of the delegated roles should start with # "bins" if we are using custom target delegation. bins_used = True if delegated_roles[0].startswith("bins") else False if bins_used: online_roles.append(Roles.BINS.value) else: online_roles.extend(delegated_roles) return online_roles
[docs] class BaseErrorResponse(BaseModel): error: str = Field(description="Error message") details: Dict[str, str] | None = Field( description="Error details", default=None ) code: int | None = Field( description="Error code if available", default=None )
[docs] class TUFSignedDelegationsRoles(BaseModel): name: str terminating: bool keyids: List[str] threshold: int paths: List[str] | None = None path_hash_prefixes: List[str] | None = None x_rstuf_expire_policy: int = Field( alias="x-rstuf-expire-policy", description="Expire Policy for the role", default=None, ) # Note: No validation is required for paths as these patterns are only used # to distribute artifacts. No files are created based on them. paths: List[str] = Field(min_length=1)
[docs] @model_validator(mode="before") @classmethod def validate_path_patterns(cls, values: Dict[str, Any]): path_patterns = values.get("paths") if any(len(pattern) < 1 for pattern in path_patterns): raise ValueError("No empty strings are allowed as path patterns") return values
[docs] class TUFSignedDelegationsSuccinctRoles(BaseModel): bit_length: int = Field(gt=0, lt=15) name_prefix: str keyids: List[str] threshold: int
[docs] class TUFKeys(BaseModel): keytype: str scheme: str keyval: Dict[Literal["public", "issuer", "identity"], str] name: str | None = Field( description="Use x-rstuf-key-name instead. Key Name", default=None, ) x_rstuf_key_name: str | None = Field( alias="x-rstuf-key-name", description="Key Name", default=None ) x_rstuf_online_key_uri: Optional[str] = Field( alias="x-rstuf-online-key-uri", description="Online Key URI", default=None, )
[docs] class TUFSignedDelegations(BaseModel): keys: Dict[str, TUFKeys] roles: List[TUFSignedDelegationsRoles] | None succinct_roles: TUFSignedDelegationsSuccinctRoles | None
[docs] class TUFSignedMetaFile(BaseModel): version: int
[docs] class TUFSignedRoles(BaseModel): keyids: List[str] threshold: int
[docs] class TUFSigned(BaseModel): model_config = ConfigDict( extra="allow", ) type: str = Field(alias="_type") version: int spec_version: str expires: str keys: Dict[str, TUFKeys] | None = None consistent_snapshot: bool | None = None roles: Dict[Roles.values(), TUFSignedRoles] | None = None meta: Dict[str, TUFSignedMetaFile] | None = None targets: Dict[str, str] | None = None delegations: TUFSignedDelegations | None = None # Custom Validator for the extra fields (TUF unrecognized_fields)
[docs] @model_validator(mode="before") @classmethod def validate_unrecognized_fields( cls, values: Dict[str, Any] ) -> Dict[str, Any]: all_required_field_names = { v.alias or f for f, v in cls.model_fields.items() } for field_name in values: if field_name not in all_required_field_names: if ( not field_name.startswith("x") or len(field_name.split("-")) < 3 ): raise ValueError( f"Invalid: `{field_name}` field name, " "unrecognized_field must use format x-<vendor>-<name>" ) return values
[docs] class TUFSignatures(BaseModel): keyid: str sig: str bundle: Dict[str, Any] | None = None
[docs] class TUFMetadata(BaseModel): signatures: List[TUFSignatures] signed: TUFSigned
[docs] class TUFDelegations(BaseModel): keys: Dict[str, TUFKeys] roles: List[TUFSignedDelegationsRoles]