_synta/certificate/ace88.rs
1//! Python bindings for the RFC 7773 Authentication Context certificate
2//! extension (ACE-88) builder.
3//!
4//! Exposes [`PyAuthenticationContextsBuilder`] for constructing DER-encoded
5//! ``AuthenticationContexts`` structures (OID ``1.2.752.201.5.1``).
6
7use pyo3::prelude::*;
8use pyo3::types::PyBytes;
9
10// ── PyAuthenticationContextsBuilder ──────────────────────────────────────────
11
12/// Python-facing wrapper for
13/// [`synta_certificate::AuthenticationContextsBuilder`].
14///
15/// Builds a DER-encoded ``AuthenticationContexts`` SEQUENCE OF (RFC 7773 /
16/// ACE-88).
17///
18/// Each entry is an ``AuthenticationContext`` with a mandatory
19/// ``contextType`` UTF8String and an optional ``contextInfo`` UTF8String.
20/// At least one entry must be added before calling :meth:`build`.
21///
22/// The resulting DER goes inside the OCTET STRING wrapper of an X.509
23/// ``Extension`` SEQUENCE for OID ``1.2.752.201.5.1``
24/// (``id-ce-authContext``).
25///
26/// Example::
27///
28/// import synta
29///
30/// extn_der = (
31/// synta.AuthenticationContextsBuilder()
32/// .add("urn:id:skatteverket:2:1.0", None)
33/// .add(
34/// "urn:id:skatteverket:1:1.0",
35/// "https://www.skatteverket.se/ac/context",
36/// )
37/// .build()
38/// )
39#[pyclass(name = "AuthenticationContextsBuilder")]
40pub struct PyAuthenticationContextsBuilder {
41 inner: synta_certificate::AuthenticationContextsBuilder,
42 built: bool,
43}
44
45#[pymethods]
46impl PyAuthenticationContextsBuilder {
47 /// Create a new, empty ``AuthenticationContextsBuilder``.
48 #[new]
49 fn new() -> Self {
50 Self {
51 inner: synta_certificate::AuthenticationContextsBuilder::new(),
52 built: false,
53 }
54 }
55
56 /// Add an ``AuthenticationContext`` entry.
57 ///
58 /// :param context_type: URI string identifying the authentication context,
59 /// e.g. ``"urn:id:skatteverket:2:1.0"``.
60 /// :param context_info: optional URL pointing to a document describing the
61 /// context (e.g. ``"https://www.skatteverket.se/ac/context.xml"``), or
62 /// ``None``.
63 /// :raises ValueError: if DER encoding fails (deferred to :meth:`build`).
64 #[pyo3(signature = (context_type, context_info=None))]
65 fn add<'py>(
66 slf: Bound<'py, Self>,
67 context_type: &str,
68 context_info: Option<&str>,
69 ) -> Bound<'py, Self> {
70 {
71 let mut guard = slf.borrow_mut();
72 let old = std::mem::replace(
73 &mut guard.inner,
74 synta_certificate::AuthenticationContextsBuilder::new(),
75 );
76 guard.inner = old.add(context_type, context_info);
77 }
78 slf
79 }
80
81 /// Build the DER-encoded ``AuthenticationContexts`` SEQUENCE OF.
82 ///
83 /// :returns: DER bytes of the ``AuthenticationContexts``.
84 /// :raises ValueError: if no entries were added or DER encoding fails.
85 fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
86 if self.built {
87 return Err(pyo3::exceptions::PyValueError::new_err(
88 "build() has already been called; create a new builder",
89 ));
90 }
91 self.built = true;
92 let inner = std::mem::replace(
93 &mut self.inner,
94 synta_certificate::AuthenticationContextsBuilder::new(),
95 );
96 let der = inner
97 .build()
98 .map_err(pyo3::exceptions::PyValueError::new_err)?;
99 Ok(PyBytes::new(py, &der))
100 }
101
102 fn __repr__(&self) -> String {
103 "AuthenticationContextsBuilder()".to_string()
104 }
105}
106
107// ── register ──────────────────────────────────────────────────────────────────
108
109/// Register ACE-88 builder classes into the given module.
110pub(super) fn register_ace88_classes(m: &Bound<'_, PyModule>) -> PyResult<()> {
111 m.add_class::<PyAuthenticationContextsBuilder>()?;
112 Ok(())
113}