"""This module contains all views concerning the help pages used within the pki app."""
from __future__ import annotations
from typing import Any, override
from django.http import Http404
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext as _non_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic.detail import DetailView
from devices.views import PublicKeyInfoMissingErrorMsg
from help_pages.base import (
HelpContext,
HelpPageStrategy,
build_extract_files_from_p12_section,
build_issuing_ca_cert_section,
build_keygen_section,
build_tls_trust_store_section,
)
from help_pages.commands import (
CmpClientCertificateCommandBuilder,
EstClientCertificateCommandBuilder,
)
from help_pages.help_section import HelpPage, HelpRow, HelpSection, ValueRenderType
from management.models import TlsSettings
from pki.models import CaModel, DevIdRegistration
from trustpoint.page_context import (
PKI_PAGE_CATEGORY,
PKI_PAGE_DOMAIN_SUBCATEGORY,
PKI_PAGE_ISSUING_CAS_SUBCATEGORY,
PageContextMixin,
)
# --------------------------------------------------- Base Classes ----------------------------------------------------
[docs]
class BaseHelpView(PageContextMixin, DetailView[DevIdRegistration]):
"""Base help view that constructs the context."""
[docs]
template_name = 'help/help_page.html'
[docs]
http_method_names = ('get',)
[docs]
model = DevIdRegistration
[docs]
context_object_name = 'idevid_registration'
[docs]
page_category = PKI_PAGE_CATEGORY
[docs]
page_name = PKI_PAGE_DOMAIN_SUBCATEGORY
[docs]
strategy: HelpPageStrategy
def _make_context(self) -> HelpContext:
devid_registration = self.object
domain = getattr(devid_registration, 'domain', None)
if not domain:
raise Http404(_('Failed to get domain from DevidRegistration.'))
host_base = f'https://{TlsSettings.get_first_ipv4_address()}:{self.request.META.get("SERVER_PORT", "443")}'
public_key_info = domain.public_key_info
if not public_key_info:
raise Http404(PublicKeyInfoMissingErrorMsg)
return HelpContext(
devid_registration=devid_registration,
allowed_app_profiles=[], # not required, IDevID help views only for domain credential, not app certs
domain=domain,
domain_unique_name=domain.unique_name,
public_key_info=public_key_info,
host_base=host_base,
host_cmp_path=f'{host_base}/.well-known/cmp/p/{domain.unique_name}',
host_est_path=f'{host_base}/.well-known/est/{domain.unique_name}',
cred_count=0,
)
@override
[docs]
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
"""Gets the context data and builds the HelpPage.
Args:
kwargs: Passed to super().get_context_data().
Returns:
The django context for the detail view.
"""
context = super().get_context_data(**kwargs)
if not self.strategy:
err_msg = _('No strategy configured.')
raise RuntimeError(err_msg)
help_context = self._make_context()
sections, heading = self.strategy.build_sections(help_context=help_context)
context['help_page'] = HelpPage(heading=heading, sections=sections)
context['ValueRenderType_CODE'] = ValueRenderType.CODE.value
context['ValueRenderType_PLAIN'] = ValueRenderType.PLAIN.value
context['back_url'] = f'{self.page_category}:{self.page_name}-config'
return context
# ------------------------- Onboarding - Application Certificates - Help Page Implementations -------------------------
[docs]
class OnboardingCmpIdevIdDomainCredentialStrategy(HelpPageStrategy):
"""Strategy for building the onboarding cmp shared-secret help page."""
@override
[docs]
def build_sections(self, help_context: HelpContext) -> tuple[list[HelpSection], str]:
operation = 'initialization'
base = help_context.host_cmp_path
summary = HelpSection(
_non_lazy('Summary'),
[
HelpRow(
_non_lazy('Domain Credential Request URL'),
f'{base}/{operation}',
ValueRenderType.CODE,
),
HelpRow(
_non_lazy('Required Public Key Type'),
str(help_context.domain.public_key_info),
ValueRenderType.CODE,
),
],
)
enroll_cmd = CmpClientCertificateCommandBuilder.get_idevid_domain_credential_command(
host=f'{base}/domain_credential/{operation}'
)
enroll_row = HelpRow(
_non_lazy('Enroll the Domain Credential with CMP'), value=enroll_cmd, value_render_type=ValueRenderType.CODE
)
enroll_section = HelpSection(heading=_non_lazy('Enroll Domain Credential'), rows=[enroll_row])
sections = [
summary,
build_issuing_ca_cert_section(domain=help_context.domain),
build_keygen_section(help_context, file_name='domain-credential-key.pem'),
build_extract_files_from_p12_section(),
enroll_section,
]
return sections, _non_lazy('Help - Issue Application Certificates using CMP with a Domain Credential')
[docs]
class OnboardingCmpIdevidRegistrationHelpView(BaseHelpView):
"""Help view for the CMP IDevID Registration, which displays the required OpenSSL commands."""
[docs]
strategy = OnboardingCmpIdevIdDomainCredentialStrategy()
[docs]
class OnboardingEstIdevIdDomainCredentialStrategy(HelpPageStrategy):
"""Strategy for building the onboarding EST shared-secret help page."""
@override
[docs]
def build_sections(self, help_context: HelpContext) -> tuple[list[HelpSection], str]:
operation = 'simpleenroll'
base = help_context.host_est_path
summary = HelpSection(
_non_lazy('Summary'),
[
HelpRow(
_non_lazy('Domain Credential Request URL'),
f'{base}/{operation}',
ValueRenderType.CODE,
),
HelpRow(
_non_lazy('Required Public Key Type'),
str(help_context.domain.public_key_info),
ValueRenderType.CODE,
),
],
)
gen_csr_row = HelpRow(
_non_lazy('Domain Credential CSR Generation'),
value=EstClientCertificateCommandBuilder.get_idevid_gen_csr_command(),
value_render_type=ValueRenderType.CODE,
)
enroll_row = HelpRow(
_non_lazy('Enroll the Domain Credential with EST'),
value=EstClientCertificateCommandBuilder.get_idevid_enroll_domain_credential_command(
host=f'{base}/domain_credential/{operation}'
),
value_render_type=ValueRenderType.CODE,
)
der_pem_conversion_row = HelpRow(
_non_lazy('Convert DER to PEM'),
value=EstClientCertificateCommandBuilder.get_idevid_der_pem_conversion_command(),
value_render_type=ValueRenderType.CODE,
)
enroll_section = HelpSection(
heading=_non_lazy('Enroll Domain Credential'), rows=[gen_csr_row, enroll_row, der_pem_conversion_row]
)
get_ca_certs_row = HelpRow(
_non_lazy('Retrieve CA chain'),
value=EstClientCertificateCommandBuilder.get_idevid_ca_certs_command(host=f'{base}/cacerts/'),
value_render_type=ValueRenderType.CODE,
)
pkcs7_pem_conversion_row = HelpRow(
_non_lazy('Convert PKCS7 to PEM'),
value=EstClientCertificateCommandBuilder.get_idevid_pkcs7_pem_conversion_command(),
value_render_type=ValueRenderType.CODE,
)
ca_cert_section = HelpSection(
heading=_non_lazy('CA Certificate Chain'),
rows=[
get_ca_certs_row,
pkcs7_pem_conversion_row,
],
)
sections = [
summary,
build_tls_trust_store_section(),
build_keygen_section(help_context, file_name='domain-credential-key.pem'),
build_extract_files_from_p12_section(),
enroll_section,
ca_cert_section,
]
return sections, _non_lazy('Help - Issue Application Certificates using CMP with a Domain Credential')
[docs]
class OnboardingEstIdevidRegistrationHelpView(BaseHelpView):
"""Help view for the EST IDevID Registration, which displays the required OpenSSL commands."""
[docs]
strategy = OnboardingEstIdevIdDomainCredentialStrategy()
# ------------------------------------------ CRL Download Help Page ---------------------------------------------------
[docs]
class CrlDownloadHelpView(PageContextMixin, DetailView[CaModel]):
"""Help view for downloading CRLs via curl commands."""
[docs]
template_name = 'help/help_page.html'
[docs]
http_method_names = ('get',)
[docs]
context_object_name = 'ca'
[docs]
page_category = PKI_PAGE_CATEGORY
[docs]
page_name = PKI_PAGE_ISSUING_CAS_SUBCATEGORY
@override
[docs]
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
"""Gets the context data and builds the HelpPage for CRL download."""
context = super().get_context_data(**kwargs)
ca = self.object
host_base = f'https://{TlsSettings.get_first_ipv4_address()}:{self.request.META.get("SERVER_PORT", "443")}'
crl_endpoint = f'{host_base}/crl/{ca.pk}/'
has_crl = bool(ca.crl_pem)
crl_status_rows = []
if has_crl and ca.last_crl_issued_at:
crl_status_rows.append(
HelpRow(
_non_lazy('CRL Status'),
_non_lazy('Available'),
ValueRenderType.PLAIN,
)
)
crl_status_rows.append(
HelpRow(
_non_lazy('Last Generated'),
ca.last_crl_issued_at.strftime('%Y-%m-%d %H:%M:%S UTC'),
ValueRenderType.PLAIN,
)
)
else:
crl_status_rows.append(
HelpRow(
_non_lazy('CRL Status'),
_non_lazy('Not Available - Generate a CRL first'),
ValueRenderType.PLAIN,
)
)
help_page_url = reverse('pki:help_issuing_cas_crl_download', kwargs={'pk': ca.pk})
generate_url = reverse('pki:issuing_cas-crl-gen', kwargs={'pk': ca.pk})
generate_url_with_next = f'{generate_url}?next={help_page_url}'
generate_button = format_html(
'<a href="{}" class="btn btn-primary w-100">{}</a>',
generate_url_with_next,
_non_lazy('Generate CRL')
)
crl_status_rows.append(
HelpRow(
_non_lazy('Generate New CRL'),
generate_button,
ValueRenderType.PLAIN,
)
)
crl_status_section = HelpSection(_non_lazy('CRL Status'), crl_status_rows)
summary = HelpSection(
_non_lazy('Summary'),
[
HelpRow(
_non_lazy('CA'),
ca.unique_name,
ValueRenderType.PLAIN,
),
HelpRow(
_non_lazy('CRL Download Endpoint'),
crl_endpoint,
ValueRenderType.CODE,
),
],
)
download_pem_cmd = (
f'curl --cacert trustpoint-tls-trust-store.pem \\\n'
f' -o {ca.unique_name}.pem.crl \\\n'
f' "{crl_endpoint}"'
)
download_der_cmd = (
f'curl --cacert trustpoint-tls-trust-store.pem \\\n'
f' -o {ca.unique_name}.der.crl \\\n'
f' "{crl_endpoint}?encoding=der"'
)
download_section = HelpSection(
_non_lazy('Download CRL'),
[
HelpRow(
_non_lazy('Download CRL in PEM Format'),
download_pem_cmd,
ValueRenderType.CODE,
),
HelpRow(
_non_lazy('Download CRL in DER Format'),
download_der_cmd,
ValueRenderType.CODE,
),
],
)
notes_section = HelpSection(
_non_lazy('Important Notes'),
[
HelpRow(
_non_lazy('Public Access'),
_non_lazy(
'This endpoint does not require authentication and can be accessed by any client '
'that needs to verify certificate revocation status.'
),
ValueRenderType.PLAIN,
),
],
)
tls_trust_store_section = build_tls_trust_store_section()
sections = [summary, crl_status_section, tls_trust_store_section, download_section, notes_section]
context['help_page'] = HelpPage(
heading=_non_lazy(f'Help - Download CRL for {ca.unique_name}'), sections=sections
)
context['ValueRenderType_CODE'] = ValueRenderType.CODE.value
context['ValueRenderType_PLAIN'] = ValueRenderType.PLAIN.value
context['idevid_registration'] = type('obj', (object,), {
'domain': type('obj', (object,), {'pk': ca.pk})()
})()
context['back_url'] = f'{self.page_category}:{self.page_name}-config'
context['back_button_text'] = _non_lazy('Back to Issuing CA Configuration')
return context