1use std::sync::Arc;
2
3use crate::entry::{EntryInvalidCommitted, EntrySealedCommitted};
4use crate::event::ModifyEvent;
5use crate::plugins::Plugin;
6use crate::prelude::*;
7use crate::prelude::{BatchModifyEvent, QueryServerWriteTransaction};
8use crate::repl::cid::Cid;
9use crate::value::PartialValue;
10
11pub struct NameHistory {}
12
13lazy_static! {
14 static ref CLASS_TO_UPDATE: PartialValue = PartialValue::new_iutf8(EntryClass::Account.into());
17}
18
19impl NameHistory {
20 fn is_entry_to_update<VALUE, STATE>(entry: &mut Entry<VALUE, STATE>) -> bool {
21 entry.attribute_equality(Attribute::Class, &CLASS_TO_UPDATE)
22 }
23
24 fn handle_name_updates(
25 pre_cand: &[Arc<EntrySealedCommitted>],
26 cand: &mut Vec<EntryInvalidCommitted>,
27 cid: &Cid,
28 ) -> Result<(), OperationError> {
29 for (pre, post) in pre_cand.iter().zip(cand) {
30 if Self::is_entry_to_update(post) {
32 let pre_name_option = pre.get_ava_single(Attribute::Name);
33 let post_name_option = post.get_ava_single(Attribute::Name);
34 if let (Some(pre_name), Some(post_name)) = (pre_name_option, post_name_option) {
35 if pre_name != post_name {
36 match post_name {
37 Value::Iname(n) => post.add_ava_if_not_exist(
38 Attribute::NameHistory,
39 Value::AuditLogString(cid.clone(), n),
40 ),
41 _ => return Err(OperationError::InvalidValueState),
42 }
43 }
44 }
45 }
46 }
47 Ok(())
48 }
49
50 fn handle_name_creation(
51 cands: &mut [EntryInvalidNew],
52 cid: &Cid,
53 ) -> Result<(), OperationError> {
54 for cand in cands.iter_mut() {
55 if Self::is_entry_to_update(cand) {
56 if let Some(name) = cand.get_ava_single(Attribute::Name) {
57 match name {
58 Value::Iname(n) => cand.add_ava_if_not_exist(
59 Attribute::NameHistory,
60 Value::AuditLogString(cid.clone(), n),
61 ),
62 _ => return Err(OperationError::InvalidValueState),
63 }
64 }
65 }
66 }
67
68 Ok(())
69 }
70}
71
72impl Plugin for NameHistory {
73 fn id() -> &'static str {
74 "plugin_name_history"
75 }
76
77 #[instrument(level = "debug", name = "name_history::pre_create_transform", skip_all)]
78 fn pre_create_transform(
79 qs: &mut QueryServerWriteTransaction,
80 cand: &mut Vec<EntryInvalidNew>,
81 _ce: &CreateEvent,
82 ) -> Result<(), OperationError> {
83 Self::handle_name_creation(cand, qs.get_txn_cid())
84 }
85
86 #[instrument(level = "debug", name = "name_history::pre_modify", skip_all)]
87 fn pre_modify(
88 qs: &mut QueryServerWriteTransaction,
89 pre_cand: &[Arc<EntrySealedCommitted>],
90 cand: &mut Vec<EntryInvalidCommitted>,
91 _me: &ModifyEvent,
92 ) -> Result<(), OperationError> {
93 Self::handle_name_updates(pre_cand, cand, qs.get_txn_cid())
94 }
95
96 #[instrument(level = "debug", name = "name_history::pre_batch_modify", skip_all)]
97 fn pre_batch_modify(
98 qs: &mut QueryServerWriteTransaction,
99 pre_cand: &[Arc<EntrySealedCommitted>],
100 cand: &mut Vec<EntryInvalidCommitted>,
101 _me: &BatchModifyEvent,
102 ) -> Result<(), OperationError> {
103 Self::handle_name_updates(pre_cand, cand, qs.get_txn_cid())
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use std::time::Duration;
110
111 use crate::entry::{Entry, EntryInit, EntryNew};
112 use crate::prelude::entries::Attribute;
113 use crate::prelude::{uuid, EntryClass};
114 use crate::repl::cid::Cid;
115 use crate::value::Value;
116 use crate::valueset::AUDIT_LOG_STRING_CAPACITY;
117
118 #[test]
119 fn name_purge_and_set() {
120 let cid = Cid::new(
122 uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee"),
123 Duration::new(20, 2),
124 );
125 let ea = entry_init!(
126 (Attribute::Class, EntryClass::Account.to_value()),
127 (Attribute::Class, EntryClass::PosixAccount.to_value()),
128 (Attribute::Name, Value::new_iname("old_name")),
129 (
130 Attribute::Uuid,
131 Value::Uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee"))
132 ),
133 (
134 Attribute::NameHistory,
135 Value::new_audit_log_string((cid.clone(), "old_name".to_string())).unwrap()
136 ),
137 (Attribute::Description, Value::new_utf8s("testperson")),
138 (Attribute::DisplayName, Value::new_utf8s("old name person"))
139 );
140 let preload = vec![ea];
141 run_modify_test!(
142 Ok(()),
143 preload,
144 filter!(f_eq(Attribute::Name, PartialValue::new_iname("old_name"))),
145 modlist!([
146 m_purge(Attribute::Name),
147 m_pres(Attribute::Name, &Value::new_iname("new_name_1"))
148 ]),
149 None,
150 |_| {},
151 |qs: &mut QueryServerWriteTransaction| {
152 let e = qs
153 .internal_search_uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee"))
154 .expect("failed to get entry");
155 let c = e
156 .get_ava_set(Attribute::NameHistory)
157 .expect("failed to get primary cred.");
158 trace!("{:?}", c.clone());
159 assert!(
160 c.contains(&PartialValue::new_utf8s("old_name"))
161 && c.contains(&PartialValue::new_utf8s("new_name_1"))
162 )
163 }
164 );
165 }
166
167 #[test]
168 fn name_creation() {
169 let ea = entry_init!(
171 (Attribute::Class, EntryClass::Account.to_value()),
172 (Attribute::Class, EntryClass::PosixAccount.to_value()),
173 (Attribute::Name, Value::new_iname("old_name")),
174 (
175 Attribute::Uuid,
176 Value::Uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47e1"))
177 ),
178 (Attribute::Description, Value::new_utf8s("testperson")),
179 (Attribute::DisplayName, Value::new_utf8s("old name person"))
180 );
181 let preload = Vec::with_capacity(0);
182 let create = vec![ea];
183 run_create_test!(
184 Ok(()),
185 preload,
186 create,
187 None,
188 |qs: &mut QueryServerWriteTransaction| {
189 let e = qs
190 .internal_search_uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47e1"))
191 .expect("failed to get entry");
192 trace!("{:?}", e.get_ava());
193 let name_history = e
194 .get_ava_set(Attribute::NameHistory)
195 .expect("failed to get name_history ava");
196
197 assert!(name_history.contains(&PartialValue::new_utf8s("old_name")))
198 }
199 );
200 }
201
202 #[test]
203 fn name_purge_and_set_with_filled_history() {
204 let mut cids: Vec<Cid> = Vec::with_capacity(0);
205 for i in 1..AUDIT_LOG_STRING_CAPACITY {
206 cids.push(Cid::new(
207 uuid!("d2b496bd-8493-47b7-8142-f568b5cf47e1"),
208 Duration::new(20 + i as u64, 0),
209 ))
210 }
211 let mut ea = entry_init!(
213 (Attribute::Class, EntryClass::Account.to_value()),
214 (Attribute::Class, EntryClass::PosixAccount.to_value()),
215 (Attribute::Name, Value::new_iname("old_name8")),
216 (
217 Attribute::Uuid,
218 Value::Uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee"))
219 ),
220 (Attribute::Description, Value::new_utf8s("testperson")),
221 (Attribute::DisplayName, Value::new_utf8s("old name person"))
222 );
223 for (i, cid) in cids.iter().enumerate() {
224 let index = 1 + i;
225 let name = format!("old_name{index}");
226 ea.add_ava(
227 Attribute::NameHistory,
228 Value::AuditLogString(cid.clone(), name),
229 )
230 }
231 let preload = vec![ea];
232 run_modify_test!(
233 Ok(()),
234 preload,
235 filter!(f_eq(Attribute::Name, PartialValue::new_iname("old_name8"))),
236 modlist!([
237 m_purge(Attribute::Name),
238 m_pres(Attribute::Name, &Value::new_iname("new_name"))
239 ]),
240 None,
241 |_| {},
242 |qs: &mut QueryServerWriteTransaction| {
243 let e = qs
244 .internal_search_uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee"))
245 .expect("failed to get entry");
246 let c = e
247 .get_ava_set(Attribute::NameHistory)
248 .expect("failed to get name_history ava :/");
249 trace!(?c);
250 assert!(
251 !c.contains(&PartialValue::new_utf8s("old_name1"))
252 && c.contains(&PartialValue::new_utf8s("new_name"))
253 )
254 }
255 );
256 }
257}