1use std::collections::BTreeSet;
4use std::iter::once;
5use std::sync::Arc;
6
7use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntrySealed};
8use crate::event::{CreateEvent, ModifyEvent};
9use crate::plugins::Plugin;
10use crate::prelude::*;
11
12pub struct Spn {}
13
14impl Plugin for Spn {
15 fn id() -> &'static str {
16 "plugin_spn"
17 }
18
19 #[instrument(level = "debug", name = "spn_pre_create_transform", skip_all)]
21 fn pre_create_transform(
22 qs: &mut QueryServerWriteTransaction,
23 cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
24 _ce: &CreateEvent,
25 ) -> Result<(), OperationError> {
26 Self::modify_inner(qs, cand)
30 }
31
32 #[instrument(level = "debug", name = "spn_pre_modify", skip_all)]
33 fn pre_modify(
34 qs: &mut QueryServerWriteTransaction,
35 _pre_cand: &[Arc<EntrySealedCommitted>],
36 cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
37 _me: &ModifyEvent,
38 ) -> Result<(), OperationError> {
39 Self::modify_inner(qs, cand)
40 }
41
42 #[instrument(level = "debug", name = "spn_pre_batch_modify", skip_all)]
43 fn pre_batch_modify(
44 qs: &mut QueryServerWriteTransaction,
45 _pre_cand: &[Arc<EntrySealedCommitted>],
46 cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
47 _me: &BatchModifyEvent,
48 ) -> Result<(), OperationError> {
49 Self::modify_inner(qs, cand)
50 }
51
52 #[instrument(level = "debug", name = "spn_post_modify", skip_all)]
53 fn post_modify(
54 qs: &mut QueryServerWriteTransaction,
55 pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
57 cand: &[Entry<EntrySealed, EntryCommitted>],
58 _ce: &ModifyEvent,
59 ) -> Result<(), OperationError> {
60 Self::post_modify_inner(qs, pre_cand, cand)
61 }
62
63 #[instrument(level = "debug", name = "spn_post_batch_modify", skip_all)]
64 fn post_batch_modify(
65 qs: &mut QueryServerWriteTransaction,
66 pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
68 cand: &[Entry<EntrySealed, EntryCommitted>],
69 _ce: &BatchModifyEvent,
70 ) -> Result<(), OperationError> {
71 Self::post_modify_inner(qs, pre_cand, cand)
72 }
73
74 #[instrument(level = "debug", name = "spn_post_repl_incremental", skip_all)]
75 fn post_repl_incremental(
76 qs: &mut QueryServerWriteTransaction,
77 pre_cand: &[Arc<EntrySealedCommitted>],
78 cand: &[EntrySealedCommitted],
79 _conflict_uuids: &BTreeSet<Uuid>,
80 ) -> Result<(), OperationError> {
81 Self::post_modify_inner(qs, pre_cand, cand)
82 }
83
84 #[instrument(level = "debug", name = "spn::verify", skip_all)]
85 fn verify(qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
86 let domain_name = qs.get_domain_name().to_string();
92
93 let filt_in = filter!(f_or!([
94 f_eq(Attribute::Class, EntryClass::Group.into()),
95 f_eq(Attribute::Class, EntryClass::Account.into()),
96 ]));
97
98 let all_cand = match qs
99 .internal_search(filt_in)
100 .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
101 {
102 Ok(all_cand) => all_cand,
103 Err(e) => return vec![e],
104 };
105
106 let mut r = Vec::with_capacity(0);
107
108 for e in all_cand {
109 let Some(g_spn) = e.generate_spn(&domain_name) else {
110 admin_error!(
111 uuid = ?e.get_uuid(),
112 "Entry SPN could not be generated (missing name!?)",
113 );
114 debug_assert!(false);
115 r.push(Err(ConsistencyError::InvalidSpn(e.get_id())));
116 continue;
117 };
118 match e.get_ava_single(Attribute::Spn) {
119 Some(r_spn) => {
120 trace!("verify spn: s {:?} == ex {:?} ?", r_spn, g_spn);
121 if r_spn != g_spn {
122 admin_error!(
123 uuid = ?e.get_uuid(),
124 "Entry SPN does not match expected s {:?} != ex {:?}",
125 r_spn,
126 g_spn,
127 );
128 debug_assert!(false);
129 r.push(Err(ConsistencyError::InvalidSpn(e.get_id())))
130 }
131 }
132 None => {
133 admin_error!(uuid = ?e.get_uuid(), "Entry does not contain an SPN");
134 r.push(Err(ConsistencyError::InvalidSpn(e.get_id())))
135 }
136 }
137 }
138 r
139 }
140}
141
142impl Spn {
143 fn modify_inner<T: Clone + std::fmt::Debug>(
144 qs: &mut QueryServerWriteTransaction,
145 cand: &mut [Entry<EntryInvalid, T>],
146 ) -> Result<(), OperationError> {
147 let domain_name = qs.get_domain_name();
148
149 for ent in cand.iter_mut() {
150 if ent.attribute_equality(Attribute::Class, &EntryClass::Group.into())
151 || ent.attribute_equality(Attribute::Class, &EntryClass::Account.into())
152 {
153 let spn = ent
154 .generate_spn(domain_name)
155 .ok_or(OperationError::InvalidEntryState)
156 .map_err(|e| {
157 admin_error!(
158 "Account or group missing name, unable to generate spn!? {:?} entry_id = {:?}",
159 e, ent.get_uuid()
160 );
161 e
162 })?;
163 trace!(
164 "plugin_{}: set {} to {:?}",
165 Attribute::Spn,
166 Attribute::Spn,
167 spn
168 );
169 ent.set_ava(&Attribute::Spn, once(spn));
170 }
171 }
172 Ok(())
173 }
174
175 fn post_modify_inner(
176 qs: &mut QueryServerWriteTransaction,
177 pre_cand: &[Arc<Entry<EntrySealed, EntryCommitted>>],
178 cand: &[Entry<EntrySealed, EntryCommitted>],
179 ) -> Result<(), OperationError> {
180 let domain_name_changed = cand.iter().zip(pre_cand.iter()).find_map(|(post, pre)| {
183 let domain_name = post.get_ava_single(Attribute::DomainName);
184 if post.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO)
185 && domain_name != pre.get_ava_single(Attribute::DomainName)
186 {
187 domain_name
188 } else {
189 None
190 }
191 });
192
193 let Some(domain_name) = domain_name_changed else {
194 return Ok(());
195 };
196
197 qs.reload_domain_info()?;
202
203 admin_info!(
204 "IMPORTANT!!! Changing domain name to \"{:?}\". THIS MAY TAKE A LONG TIME ...",
205 domain_name
206 );
207
208 qs.internal_modify(
211 &filter!(f_pres(Attribute::Spn)),
212 &modlist!([m_purge(Attribute::Spn)]),
213 )
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use crate::prelude::*;
220
221 #[test]
222 fn test_spn_generate_create() {
223 let e: Entry<EntryInit, EntryNew> = entry_init!(
225 (Attribute::Class, EntryClass::Account.to_value()),
226 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
227 (Attribute::Name, Value::new_iname("testperson")),
228 (Attribute::Description, Value::new_utf8s("testperson")),
229 (Attribute::DisplayName, Value::new_utf8s("testperson"))
230 );
231
232 let create = vec![e];
233 let preload = Vec::with_capacity(0);
234
235 run_create_test!(
236 Ok(()),
237 preload,
238 create,
239 None,
240 |_qs_write: &QueryServerWriteTransaction| {}
241 );
242 }
244
245 #[test]
246 fn test_spn_generate_modify() {
247 let e: Entry<EntryInit, EntryNew> = entry_init!(
249 (Attribute::Class, EntryClass::Account.to_value()),
250 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
251 (Attribute::Name, Value::new_iname("testperson")),
252 (Attribute::Description, Value::new_utf8s("testperson")),
253 (Attribute::DisplayName, Value::new_utf8s("testperson"))
254 );
255
256 let preload = vec![e];
257
258 run_modify_test!(
259 Ok(()),
260 preload,
261 filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))),
262 modlist!([m_purge(Attribute::Spn)]),
263 None,
264 |_| {},
265 |_| {}
266 );
267 }
268
269 #[test]
270 fn test_spn_validate_create() {
271 let e: Entry<EntryInit, EntryNew> = entry_init!(
274 (Attribute::Class, EntryClass::Account.to_value()),
275 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
276 (
277 Attribute::Spn,
278 Value::new_utf8s("testperson@invalid_domain.com")
279 ),
280 (Attribute::Name, Value::new_iname("testperson")),
281 (Attribute::Description, Value::new_utf8s("testperson")),
282 (Attribute::DisplayName, Value::new_utf8s("testperson"))
283 );
284
285 let create = vec![e];
286 let preload = Vec::with_capacity(0);
287
288 run_create_test!(
289 Ok(()),
290 preload,
291 create,
292 None,
293 |_qs_write: &QueryServerWriteTransaction| {}
294 );
295 }
296
297 #[test]
298 fn test_spn_validate_modify() {
299 let e: Entry<EntryInit, EntryNew> = entry_init!(
302 (Attribute::Class, EntryClass::Account.to_value()),
303 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
304 (Attribute::Name, Value::new_iname("testperson")),
305 (Attribute::Description, Value::new_utf8s("testperson")),
306 (Attribute::DisplayName, Value::new_utf8s("testperson"))
307 );
308
309 let preload = vec![e];
310
311 run_modify_test!(
312 Ok(()),
313 preload,
314 filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))),
315 modlist!([
316 m_purge(Attribute::Spn),
317 m_pres(
318 Attribute::Spn,
319 &Value::new_spn_str("invalid", Attribute::Spn.as_ref())
320 )
321 ]),
322 None,
323 |_| {},
324 |_| {}
325 );
326 }
327
328 #[qs_test]
329 async fn test_spn_regen_domain_rename(server: &QueryServer) {
330 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
331
332 let ex1 = Value::new_spn_str("a_testperson1", "example.com");
333 let ex2 = Value::new_spn_str("a_testperson1", "new.example.com");
334
335 let t_uuid = Uuid::new_v4();
336 let g_uuid = Uuid::new_v4();
337
338 assert!(server_txn
339 .internal_create(vec![
340 entry_init!(
341 (Attribute::Class, EntryClass::Object.to_value()),
342 (Attribute::Class, EntryClass::Account.to_value()),
343 (Attribute::Class, EntryClass::Person.to_value()),
344 (Attribute::Name, Value::new_iname("a_testperson1")),
345 (Attribute::Uuid, Value::Uuid(t_uuid)),
346 (Attribute::Description, Value::new_utf8s("testperson1")),
347 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
348 ),
349 entry_init!(
350 (Attribute::Class, EntryClass::Object.to_value()),
351 (Attribute::Class, EntryClass::Group.to_value()),
352 (Attribute::Name, Value::new_iname("testgroup")),
353 (Attribute::Uuid, Value::Uuid(g_uuid)),
354 (Attribute::Member, Value::Refer(t_uuid))
355 ),
356 ])
357 .is_ok());
358
359 assert!(server_txn.commit().is_ok());
360 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
361
362 let e_pre = server_txn
365 .internal_search_uuid(t_uuid)
366 .expect("must not fail");
367
368 let e_pre_spn = e_pre.get_ava_single(Attribute::Spn).expect("must not fail");
369 assert_eq!(e_pre_spn, ex1);
370
371 server_txn
375 .danger_domain_rename("new.example.com")
376 .expect("should not fail!");
377
378 assert!(server_txn.commit().is_ok());
379 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
380
381 let e_post = server_txn
383 .internal_search_uuid(t_uuid)
384 .expect("must not fail");
385
386 let e_post_spn = e_post
387 .get_ava_single(Attribute::Spn)
388 .expect("must not fail");
389 debug!("{:?}", e_post_spn);
390 debug!("{:?}", ex2);
391 assert_eq!(e_post_spn, ex2);
392
393 let testuser_spn = server_txn
395 .uuid_to_spn(t_uuid)
396 .expect("Must be able to retrieve the spn")
397 .expect("Value must not be none");
398 assert_eq!(testuser_spn, ex2);
399
400 assert!(server_txn.commit().is_ok());
401 }
402}