kanidmd_lib/server/
create.rs

1use crate::prelude::*;
2use crate::server::CreateEvent;
3use crate::server::{ChangeFlag, Plugins};
4
5impl QueryServerWriteTransaction<'_> {
6    #[instrument(level = "debug", skip_all)]
7    /// The create event is a raw, read only representation of the request
8    /// that was made to us, including information about the identity
9    /// performing the request.
10    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        // TODO #67: Do we need limits on number of creates, or do we constraint
21        // based on request size in the frontend?
22
23        // Copy the entries to a writeable form, this involves assigning a
24        // change id so we can track what's happening.
25        let candidates: Vec<Entry<EntryInit, EntryNew>> = ce.entries.clone();
26
27        // Do we have rights to perform these creates?
28        // create_allow_operation
29        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        // Before we assign replication metadata, we need to assert these entries
41        // are valid to create within the set of replication transitions. This
42        // means they *can not* be recycled or tombstones!
43        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        // Assign our replication metadata now, since we can proceed with this operation.
49        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        // run any pre plugins, giving them the list of mutable candidates.
55        // pre-plugins are defined here in their correct order of calling!
56        // I have no intent to make these dynamic or configurable.
57        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        // Now, normalise AND validate!
63        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                        // Then seal the changes?
73                        e.seal(&self.schema)
74                    })
75            })
76            .collect::<Result<Vec<EntrySealedNew>, _>>()?;
77
78        // Run any pre-create plugins now with schema validated entries.
79        // This is important for normalisation of certain types i.e. class
80        // or attributes for these checks.
81        Plugins::run_pre_create(self, &norm_cand, ce).map_err(|e| {
82            admin_error!("Create operation failed (plugin), {:?}", e);
83            e
84        })?;
85
86        // We may change from ce.entries later to something else?
87        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        // Run any post plugins
93        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        // We have finished all plugins and now have a successful operation - flag if
99        // schema or acp requires reload.
100        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        // We are complete, finalise logging and return
188
189        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        // We apply some member-of in the server now, so we add these before we seal.
262        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        // Indirectly via all persons
274        e.add_ava(
275            Attribute::MemberOf,
276            Value::Refer(UUID_IDM_PEOPLE_SELF_NAME_WRITE),
277        );
278        // we also add the name_history ava!
279        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        // Create on server a
300        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        // Can't clone admin here as these are two separate servers.
308        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        // Not on sb
328        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        // Now is present
335        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}