Source code for request.request_context

"""This module contains the RequestContext class for managing request-specific named attributes."""
from __future__ import annotations

from dataclasses import dataclass, fields
from typing import TYPE_CHECKING, Any, TypeVar

from cryptography import x509
from django.http import HttpResponse

from trustpoint.logger import LoggerMixin

if TYPE_CHECKING:
    from cryptography.x509 import CertificateSigningRequest
    from cryptography.x509.base import CertificateBuilder
    from django.http import HttpRequest
    from pyasn1_modules.rfc4210 import PKIMessage  # type: ignore[import-untyped]
    from trustpoint_core.serializer import PrivateKeySerializer

    from cmp.util import PKIFailureInfo
    from devices.models import DeviceModel
    from pki.models import CertificateProfileModel, CredentialModel, DomainModel, IssuedCredentialModel, TruststoreModel
    from workflows.events import Event
    from workflows.models import EnrollmentRequest

# Request context classes follow the naming convention of <Protocol><Operation>RequestContext

[docs] RCT = TypeVar('RCT', bound='BaseRequestContext')
@dataclass(kw_only=True)
[docs] class BaseRequestContext(LoggerMixin): """Base class for all specific request context classes."""
[docs] operation: str | None = None
[docs] protocol: str | None = None
[docs] parsed_message: CertificateSigningRequest | PKIMessage | None = None
[docs] domain_str: str | None = None
[docs] domain: DomainModel | None = None
[docs] device: DeviceModel | None = None
[docs] owner_credential: CredentialModel | None = None
[docs] issuer_credential: CredentialModel | None = None
[docs] client_certificate: x509.Certificate | None = None
[docs] client_intermediate_certificate: list[x509.Certificate] | None = None
# TODO: This should be refactored into the overall Request Context # noqa: FIX002, TD002
[docs] event: Event | None = None
[docs] def error(self, ext_msg: str | bytes |None, http_status: int | None = None, cmp_code: PKIFailureInfo | None = None) -> None: """Set an error message in the context.""" if isinstance(self, HttpBaseRequestContext): self.http_response_content = ext_msg self.http_response_status = http_status if isinstance(self, CmpBaseRequestContext): self.error_details = ext_msg self.error_code = cmp_code
[docs] def to_dict(self) -> dict[str, Any]: """Serialize the context to a dictionary.""" return {field.name: getattr(self, field.name) for field in fields(self)}
[docs] def to_http_response(self) -> HttpResponse: """Convert the context's HTTP response attributes to a Django HttpResponse.""" if not isinstance(self, HttpBaseRequestContext): exc_msg = 'to_http_response can only be called on HttpBaseRequestContext instances.' raise TypeError(exc_msg) response = HttpResponse(content=self.http_response_content or b'', status=self.http_response_status or 500, content_type=self.http_response_content_type or 'text/plain') if self.http_response_headers: for header_name, header_value in self.http_response_headers.items(): response[header_name] = header_value return response
[docs] def narrow(self, child_cls: type[RCT], **extra: Any) -> RCT: """Create a new request context of a more specific subclass, copying existing attributes.""" data = self.to_dict() data = {k: v for k, v in data.items() if hasattr(child_cls, k)} return child_cls(**data, **extra)
[docs] def clear(self) -> None: """Reset all attributes to None.""" for field in fields(self): setattr(self, field.name, None)
def __str__(self) -> str: """String representation showing all context fields.""" field_summary = ', '.join(f'{field.name}={getattr(self, field.name)}' for field in fields(self)) return f'{self.__class__.__name__}({field_summary})' def __repr__(self) -> str: """Detailed representation for debugging.""" return (f'{self.__class__.__name__}(protocol={self.protocol}, ' f'operation={self.operation}, domain_str={self.domain_str})')
@dataclass(kw_only=True)
[docs] class BaseCertificateRequestContext(BaseRequestContext): """Shared context for all certificate request operations."""
[docs] cert_requested: CertificateSigningRequest | CertificateBuilder | None = None
[docs] cert_profile_str: str | None = None
[docs] cert_requested_profile_validated: CertificateBuilder | None = None
[docs] issued_certificate: x509.Certificate | None = None
[docs] issued_certificate_chain: list[x509.Certificate] | None = None
[docs] certificate_profile_model: CertificateProfileModel | None = None
# Flag to allow CA certificate requests (e.g., for Issuing CA certificate enrollment)
[docs] allow_ca_certificate_request: bool = False
# Request data for building CSR
[docs] request_data: dict[str, Any] | None = None
[docs] validated_request_data: dict[str, Any] | None = None
# TODO: This should be refactored into the overall Request Context # noqa: FIX002, TD002
[docs] enrollment_request: EnrollmentRequest | None = None
[docs] event: Event | None = None
@dataclass(kw_only=True)
[docs] class BaseCredentialRequestContext(BaseCertificateRequestContext): """Shared context for all credential request (keypair generated by Trustpoint) operations."""
[docs] private_key: PrivateKeySerializer | None = None
[docs] issued_credential: IssuedCredentialModel | None = None
@dataclass(kw_only=True)
[docs] class BaseRevocationRequestContext(BaseRequestContext): """Shared context for all revocation request operations."""
[docs] cert_serial_number: str | None = None
[docs] credential_to_revoke: IssuedCredentialModel | None = None
[docs] revocation_reason: x509.ReasonFlags = x509.ReasonFlags.unspecified
@dataclass(kw_only=True)
[docs] class HttpBaseRequestContext(BaseRequestContext): """Shared context for all protocols that use HTTP(s) for message transfer."""
[docs] raw_message: HttpRequest | None = None
[docs] http_response_status: int | None = None
[docs] http_response_headers: dict[str, str] | None = None
[docs] http_response_content: bytes | str | None = None
[docs] http_response_content_type: str | None = None
@dataclass(kw_only=True)
[docs] class EstBaseRequestContext(HttpBaseRequestContext): """Shared context for all EST requests. Supports both EST server functionality (receiving requests) and EST client functionality (sending requests to external EST servers). """ # Server-side fields
[docs] parsed_message: CertificateSigningRequest | None = None
[docs] est_encoding: str | None = None
[docs] est_username: str | None = None
[docs] est_password: str | None = None
# Client-side fields
[docs] est_server_host: str | None = None
[docs] est_server_port: int | None = None
[docs] est_server_path: str | None = None
[docs] est_server_truststore: TruststoreModel | None = None
# Client certificate (mTLS) authentication — used instead of username/password
[docs] est_client_cert_pem: str | None = None
[docs] est_client_key_pem: str | None = None
@dataclass(kw_only=True)
[docs] class CmpBaseRequestContext(HttpBaseRequestContext): """Shared context for all CMP requests. Supports both CMP server functionality (receiving requests) and CMP client functionality (sending requests to external CMP servers). """ # Server-side fields
[docs] parsed_message: PKIMessage | None = None
[docs] cmp_shared_secret: str | None = None
[docs] error_code: PKIFailureInfo | None = None
[docs] error_details: str | None = None
# Client-side fields
[docs] cmp_server_host: str | None = None
[docs] cmp_server_port: int | None = None
[docs] cmp_server_path: str | None = None
[docs] cmp_server_truststore: TruststoreModel | None = None
@dataclass(kw_only=True)
[docs] class RestBaseRequestContext(HttpBaseRequestContext): """Shared context for all REST API requests."""
@dataclass(kw_only=True)
[docs] class ManualBaseRequestContext(BaseRequestContext): """Shared context for all manually triggered requests (e.g., from the web UI)."""
[docs] protocol: str = 'manual'
@dataclass(kw_only=True)
[docs] class EstCertificateRequestContext(EstBaseRequestContext, BaseCertificateRequestContext): """EST context for certificate enrollment requests."""
@dataclass(kw_only=True)
[docs] class EstRevocationRequestContext(EstBaseRequestContext, BaseRevocationRequestContext): """EST context for certificate revocation requests."""
@dataclass(kw_only=True)
[docs] class CmpCertificateRequestContext(CmpBaseRequestContext, BaseCertificateRequestContext): """CMP context for certificate enrollment requests (IR/CR)."""
@dataclass(kw_only=True)
[docs] class CmpRevocationRequestContext(CmpBaseRequestContext, BaseRevocationRequestContext): """CMP context for certificate revocation requests (RR)."""
@dataclass(kw_only=True)
[docs] class ManualCredentialRequestContext(ManualBaseRequestContext, BaseCredentialRequestContext): """Manual context for credential requests triggered from the web UI."""