use crate::prelude::*;
use crate::server::CreateEvent;
use crate::server::{ChangeFlag, Plugins};
impl<'a> QueryServerWriteTransaction<'a> {
#[instrument(level = "debug", skip_all)]
pub fn create(&mut self, ce: &CreateEvent) -> Result<(), OperationError> {
if !ce.ident.is_internal() {
security_info!(name = %ce.ident, "create initiator");
}
if ce.entries.is_empty() {
request_error!("create: empty create request");
return Err(OperationError::EmptyRequest);
}
let candidates: Vec<Entry<EntryInit, EntryNew>> = ce.entries.clone();
let access = self.get_accesscontrols();
let op_allow = access
.create_allow_operation(ce, &candidates)
.map_err(|e| {
admin_error!("Failed to check create access {:?}", e);
e
})?;
if !op_allow {
return Err(OperationError::AccessDenied);
}
if candidates.iter().any(|e| e.mask_recycled_ts().is_none()) {
admin_warn!("Refusing to create invalid entries that are attempting to bypass replication state machine.");
return Err(OperationError::AccessDenied);
}
let mut candidates: Vec<Entry<EntryInvalid, EntryNew>> = candidates
.into_iter()
.map(|e| e.assign_cid(self.cid.clone(), &self.schema))
.collect();
Plugins::run_pre_create_transform(self, &mut candidates, ce).map_err(|e| {
admin_error!("Create operation failed (pre_transform plugin), {:?}", e);
e
})?;
let norm_cand = candidates
.into_iter()
.map(|e| {
e.validate(&self.schema)
.map_err(|e| {
admin_error!("Schema Violation in create validate {:?}", e);
OperationError::SchemaViolation(e)
})
.map(|e| {
e.seal(&self.schema)
})
})
.collect::<Result<Vec<EntrySealedNew>, _>>()?;
Plugins::run_pre_create(self, &norm_cand, ce).map_err(|e| {
admin_error!("Create operation failed (plugin), {:?}", e);
e
})?;
let commit_cand = self.be_txn.create(&self.cid, norm_cand).map_err(|e| {
admin_error!("betxn create failure {:?}", e);
e
})?;
Plugins::run_post_create(self, &commit_cand, ce).map_err(|e| {
admin_error!("Create operation failed (post plugin), {:?}", e);
e
})?;
if !self.changed_flags.contains(ChangeFlag::SCHEMA)
&& commit_cand.iter().any(|e| {
e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into())
|| e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into())
})
{
self.changed_flags.insert(ChangeFlag::SCHEMA)
}
if !self.changed_flags.contains(ChangeFlag::ACP)
&& commit_cand.iter().any(|e| {
e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into())
})
{
self.changed_flags.insert(ChangeFlag::ACP)
}
if !self.changed_flags.contains(ChangeFlag::APPLICATION)
&& commit_cand
.iter()
.any(|e| e.attribute_equality(Attribute::Class, &EntryClass::Application.into()))
{
self.changed_flags.insert(ChangeFlag::APPLICATION)
}
if !self.changed_flags.contains(ChangeFlag::OAUTH2)
&& commit_cand.iter().any(|e| {
e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into())
})
{
self.changed_flags.insert(ChangeFlag::OAUTH2)
}
if !self.changed_flags.contains(ChangeFlag::DOMAIN)
&& commit_cand
.iter()
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO))
{
self.changed_flags.insert(ChangeFlag::DOMAIN)
}
if !self.changed_flags.contains(ChangeFlag::SYSTEM_CONFIG)
&& commit_cand
.iter()
.any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_SYSTEM_CONFIG))
{
self.changed_flags.insert(ChangeFlag::SYSTEM_CONFIG)
}
if !self.changed_flags.contains(ChangeFlag::SYNC_AGREEMENT)
&& commit_cand
.iter()
.any(|e| e.attribute_equality(Attribute::Class, &EntryClass::SyncAccount.into()))
{
self.changed_flags.insert(ChangeFlag::SYNC_AGREEMENT)
}
if !self.changed_flags.contains(ChangeFlag::KEY_MATERIAL)
&& commit_cand.iter().any(|e| {
e.attribute_equality(Attribute::Class, &EntryClass::KeyProvider.into())
|| e.attribute_equality(Attribute::Class, &EntryClass::KeyObject.into())
})
{
self.changed_flags.insert(ChangeFlag::KEY_MATERIAL)
}
self.changed_uuid
.extend(commit_cand.iter().map(|e| e.get_uuid()));
trace!(
changed = ?self.changed_flags.iter_names().collect::<Vec<_>>(),
);
if ce.ident.is_internal() {
trace!("Create operation success");
} else {
admin_info!("Create operation success");
}
Ok(())
}
pub fn internal_create(
&mut self,
entries: Vec<Entry<EntryInit, EntryNew>>,
) -> Result<(), OperationError> {
let ce = CreateEvent::new_internal(entries);
self.create(&ce)
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
use std::sync::Arc;
#[qs_test]
async fn test_create_user(server: &QueryServer) {
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let filt = filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson")));
let admin = server_txn.internal_search_uuid(UUID_ADMIN).expect("failed");
let se1 = SearchEvent::new_impersonate_entry(admin, filt);
let mut e = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Name, Value::new_iname("testperson")),
(
Attribute::Spn,
Value::new_spn_str("testperson", "example.com")
),
(
Attribute::Uuid,
Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
),
(Attribute::Description, Value::new_utf8s("testperson")),
(Attribute::DisplayName, Value::new_utf8s("testperson"))
);
let ce = CreateEvent::new_internal(vec![e.clone()]);
let r1 = server_txn.search(&se1).expect("search failure");
assert!(r1.is_empty());
let cr = server_txn.create(&ce);
assert!(cr.is_ok());
let r2 = server_txn.search(&se1).expect("search failure");
debug!("--> {:?}", r2);
assert_eq!(r2.len(), 1);
e.add_ava(Attribute::Class, EntryClass::MemberOf.into());
e.add_ava(Attribute::MemberOf, Value::Refer(UUID_IDM_ALL_PERSONS));
e.add_ava(
Attribute::DirectMemberOf,
Value::Refer(UUID_IDM_ALL_PERSONS),
);
e.add_ava(Attribute::MemberOf, Value::Refer(UUID_IDM_ALL_ACCOUNTS));
e.add_ava(
Attribute::DirectMemberOf,
Value::Refer(UUID_IDM_ALL_ACCOUNTS),
);
e.add_ava(
Attribute::MemberOf,
Value::Refer(UUID_IDM_PEOPLE_SELF_NAME_WRITE),
);
e.add_ava(
Attribute::NameHistory,
Value::AuditLogString(server_txn.get_txn_cid().clone(), "testperson".to_string()),
);
let key = r2
.first()
.unwrap()
.get_ava_single_eckey_private(Attribute::IdVerificationEcKey)
.unwrap();
e.add_ava(
Attribute::IdVerificationEcKey,
Value::EcKeyPrivate(key.clone()),
);
let expected = vec![Arc::new(e.into_sealed_committed())];
assert_eq!(r2, expected);
assert!(server_txn.commit().is_ok());
}
#[qs_pair_test]
async fn test_pair_create_user(server_a: &QueryServer, server_b: &QueryServer) {
let mut server_a_txn = server_a.write(duration_from_epoch_now()).await.unwrap();
let mut server_b_txn = server_b.write(duration_from_epoch_now()).await.unwrap();
let filt = filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson")));
let admin = server_a_txn
.internal_search_uuid(UUID_ADMIN)
.expect("failed");
let se_a = SearchEvent::new_impersonate_entry(admin, filt.clone());
let admin = server_b_txn
.internal_search_uuid(UUID_ADMIN)
.expect("failed");
let se_b = SearchEvent::new_impersonate_entry(admin, filt);
let e = entry_init!(
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Name, Value::new_iname("testperson")),
(Attribute::Description, Value::new_utf8s("testperson")),
(Attribute::DisplayName, Value::new_utf8s("testperson"))
);
let cr = server_a_txn.internal_create(vec![e.clone()]);
assert!(cr.is_ok());
let r1 = server_a_txn.search(&se_a).expect("search failure");
assert!(!r1.is_empty());
let r2 = server_b_txn.search(&se_b).expect("search failure");
assert!(r2.is_empty());
let cr = server_b_txn.internal_create(vec![e]);
assert!(cr.is_ok());
let r2 = server_b_txn.search(&se_b).expect("search failure");
assert!(!r2.is_empty());
assert!(server_a_txn.commit().is_ok());
assert!(server_b_txn.commit().is_ok());
}
}