signstar_config/config/
utils.rs1use std::collections::{BTreeSet, HashSet};
4
5use ssh_key::PublicKey;
6
7use crate::config::{
8 BackendDomainFilter,
9 BackendKeyIdFilter,
10 BackendUserIdFilter,
11 BackendUserIdKind,
12 MappingAuthorizedKeyEntry,
13 MappingBackendDomain,
14 MappingBackendKeyId,
15 MappingBackendUserIds,
16 MappingSystemUserId,
17};
18
19fn collect_duplicates<'a, T>(data: impl Iterator<Item = &'a T>) -> Vec<&'a T>
21where
22 T: Eq + std::hash::Hash + Ord + 'a,
23{
24 let duplicates = {
25 let mut seen = HashSet::new();
26 let mut duplicates = HashSet::new();
27
28 for thing in data {
29 if !seen.insert(thing) {
30 duplicates.insert(thing);
31 }
32 }
33 duplicates
34 };
35
36 let mut output = Vec::from_iter(duplicates);
37 output.sort();
38 output
39}
40
41pub(crate) fn duplicate_system_user_ids(
45 mappings: &BTreeSet<impl MappingSystemUserId>,
46) -> Option<String> {
47 let duplicates = collect_duplicates(
48 mappings
49 .iter()
50 .filter_map(|mapping| mapping.system_user_id()),
51 );
52
53 if duplicates.is_empty() {
54 None
55 } else {
56 Some(format!(
57 "the duplicate system user ID{} {}",
58 if duplicates.len() > 1 { "s" } else { "" },
59 duplicates
60 .iter()
61 .map(|id| format!("\"{id}\""))
62 .collect::<Vec<_>>()
63 .join(", ")
64 ))
65 }
66}
67
68pub(crate) fn duplicate_authorized_keys(
81 mappings: &BTreeSet<impl MappingAuthorizedKeyEntry>,
82) -> Option<String> {
83 let all_key_data = mappings
84 .iter()
85 .filter_map(|mapping| mapping.authorized_key_entry())
86 .map(|authorized_key_entry| authorized_key_entry.as_ref().public_key().key_data());
87 let duplicates = collect_duplicates(all_key_data);
88
89 if duplicates.is_empty() {
90 None
91 } else {
92 Some(format!(
93 "the duplicate SSH public key{} {}",
94 if duplicates.len() > 1 { "s" } else { "" },
95 duplicates
96 .into_iter()
97 .map(|key_data| format!("\"{}\"", PublicKey::from(key_data.clone()).to_string()))
98 .collect::<Vec<_>>()
99 .join(", ")
100 ))
101 }
102}
103
104pub(crate) fn duplicate_backend_user_ids(
108 mappings: &BTreeSet<impl MappingBackendUserIds>,
109) -> Option<String> {
110 let all_backend_user_ids = mappings
111 .iter()
112 .flat_map(|mapping| {
113 mapping.backend_user_ids(BackendUserIdFilter {
114 backend_user_id_kind: BackendUserIdKind::Any,
115 })
116 })
117 .collect::<Vec<_>>();
118 let duplicates = collect_duplicates(all_backend_user_ids.iter());
119
120 if duplicates.is_empty() {
121 None
122 } else {
123 Some(format!(
124 "the duplicate backend user ID{} {}",
125 if duplicates.len() > 1 { "s" } else { "" },
126 duplicates
127 .iter()
128 .map(|id| format!("\"{id}\""))
129 .collect::<Vec<_>>()
130 .join(", ")
131 ))
132 }
133}
134
135pub(crate) fn duplicate_key_ids<T>(
142 mappings: &BTreeSet<impl MappingBackendKeyId<T>>,
143 filter: &T,
144 key_type: Option<String>,
145) -> Option<String>
146where
147 T: BackendKeyIdFilter,
148{
149 let all_system_wide_key_ids = mappings
150 .iter()
151 .filter_map(|mapping| mapping.backend_key_id(filter))
152 .collect::<Vec<_>>();
153 let duplicates = collect_duplicates(all_system_wide_key_ids.iter());
154
155 if duplicates.is_empty() {
156 None
157 } else {
158 Some(format!(
159 "the duplicate{} key ID{} {}",
160 key_type.unwrap_or_default(),
161 if duplicates.len() > 1 { "s" } else { "" },
162 duplicates
163 .iter()
164 .map(|id| format!("\"{id}\""))
165 .collect::<Vec<_>>()
166 .join(", ")
167 ))
168 }
169}
170
171pub(crate) fn duplicate_domains<T>(
178 mappings: &BTreeSet<impl MappingBackendDomain<T>>,
179 filter: Option<&T>,
180 domain_context: Option<String>,
181 domain_name: Option<&str>,
182) -> Option<String>
183where
184 T: BackendDomainFilter,
185{
186 let all_domains = mappings
187 .iter()
188 .filter_map(|mapping| mapping.backend_domain(filter))
189 .collect::<Vec<String>>();
190 let duplicates = collect_duplicates(all_domains.iter());
191
192 if duplicates.is_empty() {
193 None
194 } else {
195 Some(format!(
196 "the duplicate{} {}{} {}",
197 domain_context.unwrap_or_default(),
198 domain_name.unwrap_or("domain"),
199 if duplicates.len() > 1 { "s" } else { "" },
200 duplicates
201 .iter()
202 .map(|domain| format!("\"{domain}\""))
203 .collect::<Vec<_>>()
204 .join(", ")
205 ))
206 }
207}