1use crate::prelude::*;
2use crate::server::CreateEvent;
3use crate::server::{ChangeFlag, Plugins};
4
5impl QueryServerWriteTransaction<'_> {
6 #[instrument(level = "debug", skip_all)]
7 pub fn create(&mut self, ce: &CreateEvent) -> Result<Option<Vec<Uuid>>, OperationError> {
11 if !ce.ident.is_internal() {
12 security_info!(name = %ce.ident, "create initiator");
13 }
14
15 if ce.entries.is_empty() {
16 request_error!("create: empty create request");
17 return Err(OperationError::EmptyRequest);
18 }
19
20 let candidates: Vec<Entry<EntryInit, EntryNew>> = ce.entries.clone();
26
27 let access = self.get_accesscontrols();
30 let op_allow = access
31 .create_allow_operation(ce, &candidates)
32 .map_err(|e| {
33 admin_error!("Failed to check create access {:?}", e);
34 e
35 })?;
36 if !op_allow {
37 return Err(OperationError::AccessDenied);
38 }
39
40 if candidates.iter().any(|e| e.mask_recycled_ts().is_none()) {
44 warn!("Refusing to create invalid entries that are attempting to bypass replication state machine.");
45 return Err(OperationError::AccessDenied);
46 }
47
48 let mut candidates: Vec<Entry<EntryInvalid, EntryNew>> = candidates
50 .into_iter()
51 .map(|e| e.assign_cid(self.cid.clone(), &self.schema))
52 .collect();
53
54 Plugins::run_pre_create_transform(self, &mut candidates, ce).map_err(|e| {
58 admin_error!("Create operation failed (pre_transform plugin), {:?}", e);
59 e
60 })?;
61
62 let norm_cand = candidates
64 .into_iter()
65 .map(|e| {
66 e.validate(&self.schema)
67 .map_err(|e| {
68 admin_error!("Schema Violation in create validate {:?}", e);
69 OperationError::SchemaViolation(e)
70 })
71 .map(|e| {
72 e.seal(&self.schema)
74 })
75 })
76 .collect::<Result<Vec<EntrySealedNew>, _>>()?;
77
78 Plugins::run_pre_create(self, &norm_cand, ce).map_err(|e| {
82 admin_error!("Create operation failed (plugin), {:?}", e);
83 e
84 })?;
85
86 let commit_cand = self.be_txn.create(&self.cid, norm_cand).map_err(|e| {
88 admin_error!("betxn create failure {:?}", e);
89 e
90 })?;
91
92 Plugins::run_post_create(self, &commit_cand, ce).map_err(|e| {
94 admin_error!("Create operation failed (post plugin), {:?}", e);
95 e
96 })?;
97
98 if !self.changed_flags.contains(ChangeFlag::SCHEMA)
101 && commit_cand.iter().any(|e| {
102 e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into())
103 || e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into())
104 })
105 {
106 self.changed_flags.insert(ChangeFlag::SCHEMA)
107 }
108 if !self.changed_flags.contains(ChangeFlag::ACP)
109 && commit_cand.iter().any(|e| {
110 e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into())
111 })
112 {
113 self.changed_flags.insert(ChangeFlag::ACP)
114 }
115
116 if !self.changed_flags.contains(ChangeFlag::APPLICATION)
117 && commit_cand
118 .iter()
119 .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::Application.into()))
120 {
121 self.changed_flags.insert(ChangeFlag::APPLICATION)
122 }
123
124 if !self.changed_flags.contains(ChangeFlag::OAUTH2)
125 && commit_cand.iter().any(|e| {
126 e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into())
127 })
128 {
129 self.changed_flags.insert(ChangeFlag::OAUTH2)
130 }
131
132 if !self.changed_flags.contains(ChangeFlag::OAUTH2_CLIENT)
133 && commit_cand
134 .iter()
135 .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::OAuth2Client.into()))
136 {
137 self.changed_flags.insert(ChangeFlag::OAUTH2_CLIENT)
138 }
139
140 if !self.changed_flags.contains(ChangeFlag::FEATURE)
141 && commit_cand
142 .iter()
143 .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::Feature.into()))
144 {
145 self.changed_flags.insert(ChangeFlag::FEATURE)
146 }
147
148 if !self.changed_flags.contains(ChangeFlag::DOMAIN)
149 && commit_cand
150 .iter()
151 .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO))
152 {
153 self.changed_flags.insert(ChangeFlag::DOMAIN)
154 }
155 if !self.changed_flags.contains(ChangeFlag::SYSTEM_CONFIG)
156 && commit_cand
157 .iter()
158 .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG))
159 {
160 self.changed_flags.insert(ChangeFlag::SYSTEM_CONFIG)
161 }
162
163 if !self.changed_flags.contains(ChangeFlag::SYNC_AGREEMENT)
164 && commit_cand
165 .iter()
166 .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::SyncAccount.into()))
167 {
168 self.changed_flags.insert(ChangeFlag::SYNC_AGREEMENT)
169 }
170
171 if !self.changed_flags.contains(ChangeFlag::KEY_MATERIAL)
172 && commit_cand.iter().any(|e| {
173 e.attribute_equality(Attribute::Class, &EntryClass::KeyProvider.into())
174 || e.attribute_equality(Attribute::Class, &EntryClass::KeyObject.into())
175 })
176 {
177 self.changed_flags.insert(ChangeFlag::KEY_MATERIAL)
178 }
179
180 self.changed_uuid
181 .extend(commit_cand.iter().map(|e| e.get_uuid()));
182
183 trace!(
184 changed = ?self.changed_flags.iter_names().collect::<Vec<_>>(),
185 );
186
187 if ce.ident.is_internal() {
190 trace!("Create operation success");
191 } else {
192 admin_info!("Create operation success");
193 }
194
195 if ce.return_created_uuids {
196 Ok(Some(commit_cand.iter().map(|e| e.get_uuid()).collect()))
197 } else {
198 Ok(None)
199 }
200 }
201
202 pub fn internal_create(&mut self, entries: Vec<EntryInitNew>) -> Result<(), OperationError> {
203 let ce = CreateEvent::new_internal(entries);
204 self.create(&ce).map(|_| ())
205 }
206
207 pub fn impersonate_create(
208 &mut self,
209 ident: &Identity,
210 entries: Vec<EntryInitNew>,
211 ) -> Result<(), OperationError> {
212 let ce = CreateEvent::new_impersonate_identity(ident.clone(), entries);
213 self.create(&ce).map(|_| ())
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use crate::prelude::*;
220 use std::sync::Arc;
221
222 #[qs_test]
223 async fn test_create_user(server: &QueryServer) {
224 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
225 let filt = filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson")));
226 let idm_admin = server_txn
227 .internal_search_uuid(UUID_IDM_ADMIN)
228 .expect("failed");
229
230 let se1 = SearchEvent::new_impersonate_entry(idm_admin, filt);
231
232 let mut e = entry_init!(
233 (Attribute::Class, EntryClass::Object.to_value()),
234 (Attribute::Class, EntryClass::Person.to_value()),
235 (Attribute::Class, EntryClass::Account.to_value()),
236 (Attribute::Name, Value::new_iname("testperson")),
237 (
238 Attribute::Spn,
239 Value::new_spn_str("testperson", "example.com")
240 ),
241 (
242 Attribute::Uuid,
243 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
244 ),
245 (Attribute::Description, Value::new_utf8s("testperson")),
246 (Attribute::DisplayName, Value::new_utf8s("testperson"))
247 );
248
249 let ce = CreateEvent::new_internal(vec![e.clone()]);
250
251 let r1 = server_txn.search(&se1).expect("search failure");
252 assert!(r1.is_empty());
253
254 let cr = server_txn.create(&ce);
255 assert!(cr.is_ok());
256
257 let r2 = server_txn.search(&se1).expect("search failure");
258 debug!("--> {:?}", r2);
259 assert_eq!(r2.len(), 1);
260
261 e.add_ava(Attribute::Class, EntryClass::MemberOf.into());
263 e.add_ava(Attribute::MemberOf, Value::Refer(UUID_IDM_ALL_PERSONS));
264 e.add_ava(
265 Attribute::DirectMemberOf,
266 Value::Refer(UUID_IDM_ALL_PERSONS),
267 );
268 e.add_ava(Attribute::MemberOf, Value::Refer(UUID_IDM_ALL_ACCOUNTS));
269 e.add_ava(
270 Attribute::DirectMemberOf,
271 Value::Refer(UUID_IDM_ALL_ACCOUNTS),
272 );
273 e.add_ava(
275 Attribute::MemberOf,
276 Value::Refer(UUID_IDM_PEOPLE_SELF_NAME_WRITE),
277 );
278 e.add_ava(
280 Attribute::NameHistory,
281 Value::AuditLogString(server_txn.get_txn_cid().clone(), "testperson".to_string()),
282 );
283
284 let expected = vec![Arc::new(e.into_sealed_committed())];
285
286 error!("{:#?}", r2);
287 error!("{:#?}", expected);
288
289 assert_eq!(r2, expected);
290
291 assert!(server_txn.commit().is_ok());
292 }
293
294 #[qs_pair_test]
295 async fn test_pair_create_user(server_a: &QueryServer, server_b: &QueryServer) {
296 let mut server_a_txn = server_a.write(duration_from_epoch_now()).await.unwrap();
297 let mut server_b_txn = server_b.write(duration_from_epoch_now()).await.unwrap();
298
299 let filt = filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson")));
301
302 let idm_admin = server_a_txn
303 .internal_search_uuid(UUID_IDM_ADMIN)
304 .expect("failed");
305 let se_a = SearchEvent::new_impersonate_entry(idm_admin, filt.clone());
306
307 let idm_admin = server_b_txn
309 .internal_search_uuid(UUID_IDM_ADMIN)
310 .expect("failed");
311 let se_b = SearchEvent::new_impersonate_entry(idm_admin, filt);
312
313 let e = entry_init!(
314 (Attribute::Class, EntryClass::Person.to_value()),
315 (Attribute::Class, EntryClass::Account.to_value()),
316 (Attribute::Name, Value::new_iname("testperson")),
317 (Attribute::Description, Value::new_utf8s("testperson")),
318 (Attribute::DisplayName, Value::new_utf8s("testperson"))
319 );
320
321 let cr = server_a_txn.internal_create(vec![e.clone()]);
322 assert!(cr.is_ok());
323
324 let r1 = server_a_txn.search(&se_a).expect("search failure");
325 assert!(!r1.is_empty());
326
327 let r2 = server_b_txn.search(&se_b).expect("search failure");
329 assert!(r2.is_empty());
330
331 let cr = server_b_txn.internal_create(vec![e]);
332 assert!(cr.is_ok());
333
334 let r2 = server_b_txn.search(&se_b).expect("search failure");
336 assert!(!r2.is_empty());
337
338 assert!(server_a_txn.commit().is_ok());
339 assert!(server_b_txn.commit().is_ok());
340 }
341}