Source code for help_pages.base

"""This module contains classes and functions used by all help pages."""

from __future__ import annotations

import abc
from dataclasses import dataclass
from typing import TYPE_CHECKING, cast

from django.http import Http404
from django.urls import reverse
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _non_lazy
from django.utils.translation import gettext_lazy as _

from help_pages.commands import KeyGenCommandBuilder
from help_pages.help_section import HelpRow, HelpSection, ValueRenderType
from pki.models.domain import DomainAllowedCertificateProfileModel
from pki.models.truststore import ActiveTrustpointTlsServerCredentialModel

if TYPE_CHECKING:
    from trustpoint_core import oid

    from devices.models import DeviceModel
    from pki.models import CredentialModel, DevIdRegistration
    from pki.models.domain import DomainModel


# --------------------------------------------------- Base Classes ----------------------------------------------------


[docs] class HelpPageStrategy(abc.ABC): """Abstract base class for help page strategies.""" @abc.abstractmethod
[docs] def build_sections(self, help_context: HelpContext) -> tuple[list[HelpSection], str]: """Builds the required sections."""
@dataclass(frozen=True)
[docs] class HelpContext: """Holds shared context data."""
[docs] domain: DomainModel
[docs] domain_unique_name: str
[docs] allowed_app_profiles: list[DomainAllowedCertificateProfileModel]
[docs] public_key_info: oid.PublicKeyInfo
[docs] host_base: str # https://IP:PORT
[docs] host_cmp_path: str # {host_base}/.well-known/cmp/p/{domain.unique_name}
[docs] host_est_path: str # {host_base}/.well-known/est/{domain.unique_name}
[docs] cred_count: int # Running number to avoid overriding files on the client side
[docs] device: None | DeviceModel = None
[docs] devid_registration: None | DevIdRegistration = None
[docs] def get_device_or_http_404(self) -> DeviceModel: """Gets the device or throws an HTTP404 error. Raises: Http404: If the device does not exist. Returns: The DeviceModel. """ if self.device is None: err_msg = 'Device not found.' raise Http404(err_msg) return self.device
[docs] def get_devid_registration_or_http_404(self) -> DevIdRegistration: """Gets the DevidRegistration or throws an HTTP404 error. Raises: Http404: If the DevidRegistration does not exist. Returns: The DevidRegistration. """ if self.devid_registration is None: err_msg = 'DevidRegistration not found.' raise Http404(err_msg) return self.devid_registration
# ----------------------------------------- Reusable section build functions ------------------------------------------
[docs] def build_keygen_section(help_context: HelpContext, file_name: str) -> HelpSection: """Builds the key-generation section. Args: help_context: The help context which will file_name: The file_name to use if the default shall not be used. Defaults to None. Returns: The key-generation section. """ cmd = KeyGenCommandBuilder.get_key_gen_command( public_key_info=help_context.public_key_info, cred_number=help_context.cred_count, key_name=file_name ) return HelpSection( _non_lazy('Key Generation'), [HelpRow(_non_lazy('Generate Key-Pair'), cmd, ValueRenderType.CODE)] )
[docs] def build_profile_select_section(app_cert_profiles: list[DomainAllowedCertificateProfileModel]) -> HelpSection: """Builds the profile select section. Returns: The profile select section. """ options = mark_safe('') profile_list = DomainAllowedCertificateProfileModel.get_list_of_display_names(app_cert_profiles) for i, (_profile_id, display_name, unique_name) in enumerate(profile_list): options += format_html( '<option value="{}"{}>{}</option>', unique_name, ' selected' if i == 0 else '', display_name, ) if not options: options = format_html('<option value="" selected disabled>{}</option>', _('No application certificate profiles allowed in domain.')) select = format_html( '<select id="cert-profile-select" class="form-select" aria-label="Certificate Profile Select">{}</select>', options, ) return HelpSection( _non_lazy('Certificate Profile Selection'), [HelpRow(_non_lazy('Certificate Profile'), select, ValueRenderType.PLAIN)], )
[docs] def build_tls_trust_store_section() -> HelpSection: """Builds the TLS trust-store section. Raises: Http404: If no active Trustpoint TLS-server credential is found or the root CA cert is missing. Returns: The TLS trust-store section. """ tls = ActiveTrustpointTlsServerCredentialModel.objects.first() if not tls or not tls.credential: msg = format_html( '<div class="tp-message alert alert-danger d-flex" role="alert">' '<svg class="bi flex-shrink-0 tp-msg-icon-margin" width="20" height="20" ' 'fill="currentColor" role="img" aria-label="error:">' '<use xlink:href="/static/img/icons.svg#icon-error"></use></svg>' '<div>{}<br>{}</div>' '</div>', 'This onboarding method is not securely available without TLS.', 'Please ensure you are running Trustpoint within the correctly configured Docker environment!', ) return HelpSection( _non_lazy('Download TLS Trust-Store'), [HelpRow(_non_lazy('Truststore is unavailable!'), msg, ValueRenderType.PLAIN)], ) root = tls.credential.get_last_in_chain() if not root: raise Http404(_('Root CA certificate is missing.')) url = reverse( 'pki:certificate-file-download-file-name', kwargs={'file_format': 'pem', 'pk': root.pk, 'file_name': 'trustpoint-tls-trust-store.pem'}, ) btn = format_html('<a class="btn btn-primary w-100" href="{}">{}</a>', url, _('Download TLS Trust-Store')) return HelpSection( _non_lazy('Download TLS Trust-Store'), [HelpRow(_non_lazy('Download TLS Trust-Store'), btn, ValueRenderType.PLAIN)], )
[docs] def build_cmp_signer_trust_store_section(domain: DomainModel) -> HelpSection: """Builds the CMP-signer trust-store section. Returns: The CMP-signer trust-store section. """ issuing_ca = domain.issuing_ca if not issuing_ca: err_msg = 'Issuing CA not configured' raise ValueError(err_msg) root_ca_model = cast('CredentialModel', issuing_ca.credential).get_last_in_chain() if not root_ca_model: err_msg = 'No Root CA certificate found.' raise ValueError(err_msg) cmp_signer_pk = root_ca_model.pk download_tls_truststore_row = HelpRow( key=_non_lazy('Download CMP-Signer Trust-Store'), value=format_html( '<a href="{}" class="btn btn-primary w-100">{}</a>', reverse( 'pki:certificate-file-download-file-name', kwargs={'file_format': 'pem', 'pk': cmp_signer_pk, 'file_name': 'domain-credential-full-chain.pem'}, ), _non_lazy('Download CMP-Signer Trust-Store'), ), value_render_type=ValueRenderType.PLAIN, ) return HelpSection(heading=_non_lazy('Download CMP-Signer Trust-Store'), rows=[download_tls_truststore_row])
[docs] def build_issuing_ca_cert_section(domain: DomainModel) -> HelpSection: """Builds the Issuing CA Certificate section. Returns: The Issuing CA Certificate section. """ issuing_ca = domain.issuing_ca if not issuing_ca: err_msg = 'Issuing CA not configured' raise ValueError(err_msg) issuing_ca_pk = issuing_ca.pk download_issuing_ca_cert_row = HelpRow( key=_non_lazy('Download Issuing CA Certificate'), value=format_html( '<a href="{}" class="btn btn-primary w-100">{}</a>', reverse( 'pki:certificate-file-download-file-name', kwargs={'file_format': 'pem', 'pk': issuing_ca_pk, 'file_name': 'issuing_ca_cert.pem'}, ), _non_lazy('Download Issuing CA Certificate'), ), value_render_type=ValueRenderType.PLAIN, ) return HelpSection(heading=_non_lazy('Download Issuing CA Certificate'), rows=[download_issuing_ca_cert_row])
[docs] def build_extract_files_from_p12_section() -> HelpSection: """Builds the extract files form P12 section. Returns: The extract files form P12 section. """ intro_row = HelpRow( key=_non_lazy('Instructions'), value=_non_lazy( 'If only a .p12 or .pfx file is available, you need to extract the certificate and ' 'private key to use it with the OpenSSL CMP command.' ), value_render_type=ValueRenderType.PLAIN, ) p12_cert_row = HelpRow( key=_non_lazy('PKCS#12 IDevID Certificate Extraction'), value='openssl pkcs12 -in idevid.p12 -clcerts -nokeys -out idevid.pem', value_render_type=ValueRenderType.CODE, ) p12_cert_chain_row = HelpRow( key=_non_lazy('PKCS#12 IDevID Certificate Chain Extraction'), value='openssl pkcs12 -in idevid.p12 -cacerts -nokeys -chain -out idevid_chain.pem', value_render_type=ValueRenderType.CODE, ) p12_priv_key_row = HelpRow( key=_non_lazy('PKCS#12 IDevID Private Key Extraction'), value='openssl pkcs12 -in idevid.p12 -out idevid.key -nodes -nocerts', value_render_type=ValueRenderType.CODE, ) remarks_row = HelpRow( key=_non_lazy('Remarks'), value=_non_lazy( 'Please make sure that the root CA certificate is not included in the chain. ' 'If it is, you should remove it manually, e.g. using a text editor.' ), value_render_type=ValueRenderType.CODE, ) return HelpSection( heading=_non_lazy('PKCS#12 or PFX convertion'), rows=[intro_row, p12_cert_row, p12_cert_chain_row, p12_priv_key_row, remarks_row], )