voa_config/config/technology/openpgp/verification/
plain.rs1use std::{collections::HashSet, fmt::Display};
4
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 common::{ordered_set, set_to_vec},
9 config::technology::openpgp::{DomainName, OpenpgpFingerprint},
10 file::ConfigPlainMode,
11};
12
13#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
15pub struct PlainMode {
16 #[serde(serialize_with = "ordered_set", default)]
18 identity_domain_matches: HashSet<DomainName>,
19
20 #[serde(serialize_with = "ordered_set", default)]
22 fingerprint_matches: HashSet<OpenpgpFingerprint>,
23}
24
25impl PlainMode {
26 pub fn new(
28 identity_domain_matches: HashSet<DomainName>,
29 fingerprint_matches: HashSet<OpenpgpFingerprint>,
30 ) -> Self {
31 Self {
32 identity_domain_matches,
33 fingerprint_matches,
34 }
35 }
36
37 pub(crate) fn from_config_with_defaults(
39 config: &ConfigPlainMode,
40 defaults: &PlainMode,
41 ) -> Self {
42 Self::new(
43 config
44 .identity_domain_matches()
45 .unwrap_or(defaults.identity_domain_matches())
46 .clone(),
47 config
48 .fingerprint_matches()
49 .unwrap_or(defaults.fingerprint_matches())
50 .clone(),
51 )
52 }
53
54 pub fn identity_domain_matches(&self) -> &HashSet<DomainName> {
56 &self.identity_domain_matches
57 }
58
59 pub fn fingerprint_matches(&self) -> &HashSet<OpenpgpFingerprint> {
61 &self.fingerprint_matches
62 }
63}
64
65impl Display for PlainMode {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 writeln!(
68 f,
69 "✅ Each artifact is verified using the \"plain\" verification method.\n"
70 )?;
71
72 if !self.identity_domain_matches().is_empty() {
73 writeln!(
74 f,
75 "📧 A valid certificate must have a valid User ID that uses one of the following domains to be considered as artifact verifier:"
76 )?;
77 for domain_name in set_to_vec(self.identity_domain_matches()).iter() {
78 writeln!(f, "⤷ {domain_name}")?;
79 }
80 writeln!(f)?;
81 } else {
82 writeln!(
83 f,
84 "📧 A valid certificate is not required to use a specific domain in any of its User IDs to be considered as artifact verifier.\n"
85 )?;
86 }
87
88 if !self.fingerprint_matches().is_empty() {
89 writeln!(
90 f,
91 "🐾 A certificate must match one of the following OpenPGP fingerprints to be considered as artifact verifier:"
92 )?;
93 for fingerprint in set_to_vec(self.fingerprint_matches()).iter() {
94 writeln!(f, "⤷ {fingerprint}")?;
95 }
96 } else {
97 writeln!(
98 f,
99 "🐾 A certificate is not required to match a specific OpenPGP fingerprint to be considered as artifact verifier."
100 )?;
101 }
102
103 Ok(())
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use rstest::rstest;
110 use testresult::TestResult;
111
112 use super::*;
113
114 #[rstest]
115 #[case::some_domains_and_fingerprints(
116 HashSet::from_iter(["example.org".parse()?]),
117 HashSet::from_iter(["f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?]),
118 )]
119 #[case::no_domains_and_fingerprints(HashSet::new(), HashSet::new())]
120 fn plain_mode_new(
121 #[case] domain_names: HashSet<DomainName>,
122 #[case] fingerprints: HashSet<OpenpgpFingerprint>,
123 ) -> TestResult {
124 let mode = PlainMode::new(domain_names.clone(), fingerprints.clone());
125
126 assert_eq!(mode.identity_domain_matches(), &domain_names);
127 assert_eq!(mode.fingerprint_matches(), &fingerprints);
128
129 Ok(())
130 }
131
132 #[rstest]
133 #[case::all_defaults(ConfigPlainMode::default(), PlainMode::default(), PlainMode::default())]
134 #[case::config_empty_lists_vs_default_defaults(
135 ConfigPlainMode::new(Some(HashSet::new()), Some(HashSet::new())),
136 PlainMode::default(),
137 PlainMode::default()
138 )]
139 #[case::default_config_vs_custom_defaults(
140 ConfigPlainMode::default(),
141 PlainMode::new(
142 HashSet::from_iter(["example.org".parse()?]),
143 HashSet::from_iter(["f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?]),
144 ),
145 PlainMode::new(
146 HashSet::from_iter(["example.org".parse()?]),
147 HashSet::from_iter(["f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?]),
148 )
149 )]
150 #[case::config_empty_lists_vs_custom_defaults(
151 ConfigPlainMode::new(Some(HashSet::new()), Some(HashSet::new())),
152 PlainMode::new(
153 HashSet::from_iter(["example.org".parse()?]),
154 HashSet::from_iter(["f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?]),
155 ),
156 PlainMode::new(
157 HashSet::new(),
158 HashSet::new(),
159 )
160 )]
161 #[case::custom_config_vs_custom_defaults(
162 ConfigPlainMode::new(
163 Some(HashSet::from_iter(["other.org".parse()?])),
164 Some(HashSet::from_iter(["e242ed3bffccdf271b7fbaf34ed72d089537b42f".parse()?]))
165 ),
166 PlainMode::new(
167 HashSet::from_iter(["example.org".parse()?]),
168 HashSet::from_iter(["f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?]),
169 ),
170 PlainMode::new(
171 HashSet::from_iter(["other.org".parse()?]),
172 HashSet::from_iter(["e242ed3bffccdf271b7fbaf34ed72d089537b42f".parse()?])
173 )
174 )]
175 fn plain_mode_from_config_with_defaults(
176 #[case] config: ConfigPlainMode,
177 #[case] defaults: PlainMode,
178 #[case] expected: PlainMode,
179 ) -> TestResult {
180 let mode = PlainMode::from_config_with_defaults(&config, &defaults);
181
182 assert_eq!(mode, expected);
183
184 Ok(())
185 }
186}