1use std::sync::Arc;
2
3use crate::plugins::Plugin;
4use crate::prelude::*;
5
6pub struct ValueDeny {}
7
8impl Plugin for ValueDeny {
9 fn id() -> &'static str {
10 "plugin_value_deny"
11 }
12
13 #[instrument(level = "debug", name = "denied_names_pre_create_transform", skip_all)]
14 #[allow(clippy::cognitive_complexity)]
15 fn pre_create_transform(
16 qs: &mut QueryServerWriteTransaction,
17 cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
18 _ce: &CreateEvent,
19 ) -> Result<(), OperationError> {
20 let denied_names = qs.denied_names();
21
22 if denied_names.is_empty() {
23 return Ok(());
25 }
26
27 let mut pass = true;
28
29 for entry in cand {
30 if let Some(e_uuid) = entry.get_uuid() {
32 if e_uuid < DYNAMIC_RANGE_MINIMUM_UUID {
36 continue;
38 }
39 }
40
41 if let Some(name) = entry.get_ava_single_iname(Attribute::Name) {
42 if denied_names.contains(name) {
43 pass = false;
44 error!(?name, "name denied by system configuration");
45 }
46 }
47 }
48
49 if pass {
50 Ok(())
51 } else {
52 Err(OperationError::ValueDenyName)
53 }
54 }
55
56 fn pre_modify(
57 qs: &mut QueryServerWriteTransaction,
58 pre_cand: &[Arc<EntrySealedCommitted>],
59 cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
60 _me: &ModifyEvent,
61 ) -> Result<(), OperationError> {
62 Self::modify(qs, pre_cand, cand)
63 }
64
65 fn pre_batch_modify(
66 qs: &mut QueryServerWriteTransaction,
67 pre_cand: &[Arc<EntrySealedCommitted>],
68 cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
69 _me: &BatchModifyEvent,
70 ) -> Result<(), OperationError> {
71 Self::modify(qs, pre_cand, cand)
72 }
73
74 fn verify(qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
75 let denied_names = qs.denied_names().clone();
76
77 let mut results = Vec::with_capacity(0);
78
79 for denied_name in denied_names {
80 let filt = filter!(f_eq(Attribute::Name, PartialValue::new_iname(&denied_name)));
81 match qs.internal_search(filt) {
82 Ok(entries) => {
83 for entry in entries {
84 let e_uuid = entry.get_uuid();
85 if e_uuid < DYNAMIC_RANGE_MINIMUM_UUID {
88 continue;
90 }
91
92 results.push(Err(ConsistencyError::DeniedName(e_uuid)));
93 }
94 }
95 Err(err) => {
96 error!(?err);
97 results.push(Err(ConsistencyError::QueryServerSearchFailure))
98 }
99 }
100 }
101
102 results
103 }
104}
105
106impl ValueDeny {
107 #[instrument(level = "debug", name = "denied_names_modify", skip_all)]
108 fn modify(
109 qs: &mut QueryServerWriteTransaction,
110 pre_cand: &[Arc<EntrySealedCommitted>],
111 cand: &mut [EntryInvalidCommitted],
112 ) -> Result<(), OperationError> {
113 let denied_names = qs.denied_names();
114
115 if denied_names.is_empty() {
116 return Ok(());
118 }
119
120 let mut pass = true;
121
122 for (pre_entry, post_entry) in pre_cand.iter().zip(cand.iter()) {
123 let e_uuid = pre_entry.get_uuid();
125 if e_uuid < DYNAMIC_RANGE_MINIMUM_UUID {
128 continue;
130 }
131
132 let pre_name = pre_entry.get_ava_single_iname(Attribute::Name);
133 let post_name = post_entry.get_ava_single_iname(Attribute::Name);
134
135 if let Some(name) = post_name {
136 if pre_name != post_name && denied_names.contains(name) {
138 pass = false;
139 error!(?name, "name denied by system configuration");
140 }
141 }
142 }
143
144 if pass {
145 Ok(())
146 } else {
147 Err(OperationError::ValueDenyName)
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use crate::prelude::*;
155
156 async fn setup_name_deny(server: &QueryServer) {
157 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
158
159 let me_inv_m = ModifyEvent::new_internal_invalid(
160 filter!(f_eq(Attribute::Uuid, PVUUID_SYSTEM_CONFIG.clone())),
161 ModifyList::new_list(vec![
162 Modify::Present(Attribute::DeniedName, Value::new_iname("tobias")),
163 Modify::Present(Attribute::DeniedName, Value::new_iname("ellie")),
164 ]),
165 );
166 assert!(server_txn.modify(&me_inv_m).is_ok());
167
168 assert!(server_txn.commit().is_ok());
169 }
170
171 #[qs_test]
172 async fn test_valuedeny_create(server: &QueryServer) {
173 setup_name_deny(server).await;
174
175 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
176 let t_uuid = Uuid::new_v4();
177 assert!(server_txn
178 .internal_create(vec![entry_init!(
179 (Attribute::Class, EntryClass::Object.to_value()),
180 (Attribute::Class, EntryClass::Account.to_value()),
181 (Attribute::Class, EntryClass::Person.to_value()),
182 (Attribute::Name, Value::new_iname("tobias")),
183 (Attribute::Uuid, Value::Uuid(t_uuid)),
184 (Attribute::Description, Value::new_utf8s("Tobias")),
185 (Attribute::DisplayName, Value::new_utf8s("Tobias"))
186 ),])
187 .is_err());
188 }
189
190 #[qs_test]
191 async fn test_valuedeny_modify(server: &QueryServer) {
192 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
195 let e_uuid = Uuid::new_v4();
196 assert!(server_txn
197 .internal_create(vec![entry_init!(
198 (Attribute::Class, EntryClass::Object.to_value()),
199 (Attribute::Class, EntryClass::Account.to_value()),
200 (Attribute::Class, EntryClass::Person.to_value()),
201 (Attribute::Name, Value::new_iname("ellie")),
202 (Attribute::Uuid, Value::Uuid(e_uuid)),
203 (Attribute::Description, Value::new_utf8s("Ellie Meow")),
204 (Attribute::DisplayName, Value::new_utf8s("Ellie Meow"))
205 ),])
206 .is_ok());
207
208 assert!(server_txn.commit().is_ok());
209
210 setup_name_deny(server).await;
211
212 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
213
214 assert!(server_txn
218 .internal_modify_uuid(
219 e_uuid,
220 &ModifyList::new_purge_and_set(Attribute::DisplayName, Value::new_utf8s("tobias"))
221 )
222 .is_ok());
223
224 assert!(server_txn
226 .internal_modify_uuid(
227 e_uuid,
228 &ModifyList::new_purge_and_set(Attribute::Name, Value::new_iname("tobias"))
229 )
230 .is_err());
231
232 assert!(server_txn
234 .internal_modify_uuid(
235 e_uuid,
236 &ModifyList::new_purge_and_set(
237 Attribute::Name,
238 Value::new_iname("miss_meowington")
239 )
240 )
241 .is_ok());
242
243 assert!(server_txn
245 .internal_modify_uuid(
246 e_uuid,
247 &ModifyList::new_purge_and_set(Attribute::Name, Value::new_iname("tobias"))
248 )
249 .is_err());
250
251 assert!(server_txn.commit().is_ok());
252 }
253
254 #[qs_test]
255 async fn test_valuedeny_jpwarren_special(server: &QueryServer) {
256 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
258
259 let me_inv_m = ModifyEvent::new_internal_invalid(
260 filter!(f_eq(Attribute::Uuid, PVUUID_SYSTEM_CONFIG.clone())),
261 ModifyList::new_list(vec![
262 Modify::Present(Attribute::DeniedName, Value::new_iname("admin")),
263 Modify::Present(Attribute::DeniedName, Value::new_iname("idm_admin")),
264 ]),
265 );
266 assert!(server_txn.modify(&me_inv_m).is_ok());
267 assert!(server_txn.commit().is_ok());
268
269 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
270
271 assert!(server_txn
272 .internal_modify_uuid(
273 UUID_IDM_ADMIN,
274 &ModifyList::new_purge_and_set(
275 Attribute::DisplayName,
276 Value::new_utf8s("Idm Admin")
277 )
278 )
279 .is_ok());
280
281 assert!(server_txn
282 .internal_modify_uuid(
283 UUID_ADMIN,
284 &ModifyList::new_purge_and_set(Attribute::DisplayName, Value::new_utf8s("Admin"))
285 )
286 .is_ok());
287
288 assert!(server_txn.commit().is_ok());
289 }
290
291 #[qs_test]
292 async fn test_valuedeny_batch_modify(server: &QueryServer) {
293 setup_name_deny(server).await;
294
295 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
296 let t_uuid = Uuid::new_v4();
297 assert!(server_txn
298 .internal_create(vec![entry_init!(
299 (Attribute::Class, EntryClass::Object.to_value()),
300 (Attribute::Class, EntryClass::Account.to_value()),
301 (Attribute::Class, EntryClass::Person.to_value()),
302 (Attribute::Name, Value::new_iname("newname")),
303 (Attribute::Uuid, Value::Uuid(t_uuid)),
304 (Attribute::Description, Value::new_utf8s("Tobias")),
305 (Attribute::DisplayName, Value::new_utf8s("Tobias"))
306 ),])
307 .is_ok());
308
309 assert!(server_txn
312 .internal_batch_modify(
313 [(
314 t_uuid,
315 ModifyList::new_purge_and_set(Attribute::Name, Value::new_iname("tobias"))
316 )]
317 .into_iter()
318 )
319 .is_err());
320 }
321}