1use crate::prelude::*;
2use crate::schema::{SchemaAttribute, SchemaTransaction};
3use crate::server::assert::{AssertEvent, AssertOnce, EntryAssertion};
4use crate::server::batch_modify::{BatchModifyEvent, ModSetValid};
5use crate::server::ValueSetResolveStatus;
6use crate::valueset::*;
7use crypto_glue::s256::Sha256Output;
8use kanidm_proto::scim_v1::client::{
9 ScimEntryAssertion, ScimEntryPostGeneric, ScimEntryPutGeneric,
10};
11use kanidm_proto::scim_v1::JsonValue;
12use std::collections::{
13 BTreeMap,
15};
16
17#[derive(Debug)]
18pub struct ScimEntryPutEvent {
19 pub(crate) ident: Identity,
21
22 pub(crate) target: Uuid,
25 pub(crate) attrs: BTreeMap<Attribute, Option<ValueSet>>,
28
29 pub(crate) effective_access_check: bool,
32}
33
34impl ScimEntryPutEvent {
35 pub fn try_from(
36 ident: Identity,
37 entry: ScimEntryPutGeneric,
38 qs: &mut QueryServerWriteTransaction,
39 ) -> Result<Self, OperationError> {
40 let target = entry.id;
41
42 let attrs = entry
43 .attrs
44 .into_iter()
45 .map(|(attr, json_value)| {
46 qs.resolve_scim_json_put(&attr, json_value)
47 .map(|kani_value| (attr, kani_value))
48 })
49 .collect::<Result<_, _>>()?;
50
51 let query = entry.query;
52
53 Ok(ScimEntryPutEvent {
54 ident,
55 target,
56 attrs,
57 effective_access_check: query.ext_access_check,
58 })
59 }
60}
61
62#[derive(Debug)]
63pub struct ScimCreateEvent {
64 pub(crate) ident: Identity,
65 pub(crate) entry: EntryInitNew,
66}
67
68impl ScimCreateEvent {
69 pub fn try_from(
70 ident: Identity,
71 classes: &[EntryClass],
72 entry: ScimEntryPostGeneric,
73 qs: &mut QueryServerWriteTransaction,
74 ) -> Result<Self, OperationError> {
75 let mut entry = entry
76 .attrs
77 .into_iter()
78 .map(|(attr, json_value)| {
79 qs.resolve_scim_json_post(&attr, json_value)
80 .map(|kani_value| (attr, kani_value))
81 })
82 .collect::<Result<EntryInitNew, _>>()?;
83
84 if !classes.is_empty() {
85 let classes = ValueSetIutf8::from_iter(classes.iter().map(|cls| cls.as_ref()))
86 .ok_or(OperationError::SC0027ClassSetInvalid)?;
87
88 entry.set_ava_set(&Attribute::Class, classes);
89 }
90
91 Ok(ScimCreateEvent { ident, entry })
92 }
93}
94
95#[derive(Debug)]
96pub struct ScimDeleteEvent {
97 pub(crate) ident: Identity,
99
100 pub(crate) target: Uuid,
103
104 pub(crate) class: EntryClass,
106}
107
108impl ScimDeleteEvent {
109 pub fn new(ident: Identity, target: Uuid, class: EntryClass) -> Self {
110 ScimDeleteEvent {
111 ident,
112 target,
113 class,
114 }
115 }
116}
117
118#[derive(Debug)]
119pub struct ScimAssertEvent {
120 pub(crate) ident: Identity,
122
123 pub(crate) asserts: Vec<ScimEntryAssertion>,
125
126 pub id: Uuid,
128
129 pub nonce: Option<Sha256Output>,
131}
132
133impl ScimAssertEvent {
134 pub fn new_migration(
135 asserts: Vec<ScimEntryAssertion>,
136 id: Uuid,
137 nonce: Option<Sha256Output>,
138 ) -> Self {
139 ScimAssertEvent {
140 ident: Identity::migration(),
141 asserts,
142 id,
143 nonce,
144 }
145 }
146}
147
148impl QueryServerWriteTransaction<'_> {
149 pub fn scim_put(
154 &mut self,
155 scim_entry_put: ScimEntryPutEvent,
156 ) -> Result<ScimEntryKanidm, OperationError> {
157 let ScimEntryPutEvent {
158 ident,
159 target,
160 attrs,
161 effective_access_check,
162 } = scim_entry_put;
163
164 let mods_invalid: ModifyList<ModifyInvalid> = attrs.into();
166
167 let mods_valid = mods_invalid
168 .validate(self.get_schema())
169 .map_err(OperationError::SchemaViolation)?;
170
171 let mut modset = ModSetValid::default();
172
173 modset.insert(target, mods_valid);
174
175 let modify_event = BatchModifyEvent {
176 ident: ident.clone(),
177 modset,
178 };
179
180 self.batch_modify(&modify_event)?;
182
183 let filter_intent = filter!(f_and!([f_eq(Attribute::Uuid, PartialValue::Uuid(target))]));
186
187 let f_intent_valid = filter_intent
188 .validate(self.get_schema())
189 .map_err(OperationError::SchemaViolation)?;
190
191 let f_valid = f_intent_valid.clone().into_ignore_hidden();
192
193 let se = SearchEvent {
194 ident,
195 filter: f_valid,
196 filter_orig: f_intent_valid,
197 attrs: None,
199 effective_access_check,
200 };
201
202 let mut vs = self.search_ext(&se)?;
203 match vs.pop() {
204 Some(entry) if vs.is_empty() => entry.to_scim_kanidm(self),
205 _ => {
206 if vs.is_empty() {
207 Err(OperationError::NoMatchingEntries)
208 } else {
209 Err(OperationError::UniqueConstraintViolation)
211 }
212 }
213 }
214 }
215
216 pub fn scim_create(
217 &mut self,
218 scim_create: ScimCreateEvent,
219 ) -> Result<ScimEntryKanidm, OperationError> {
220 let ScimCreateEvent { ident, entry } = scim_create;
221
222 let create_event = CreateEvent {
223 ident,
224 entries: vec![entry],
225 return_created_uuids: true,
226 };
227
228 let changed_uuids = self.create(&create_event)?;
229
230 let mut changed_uuids = changed_uuids.ok_or(OperationError::SC0028CreatedUuidsInvalid)?;
231
232 let target = if let Some(target) = changed_uuids.pop() {
233 if !changed_uuids.is_empty() {
234 return Err(OperationError::UniqueConstraintViolation);
236 }
237
238 target
239 } else {
240 return Err(OperationError::NoMatchingEntries);
242 };
243
244 let filter_intent = filter!(f_and!([f_eq(Attribute::Uuid, PartialValue::Uuid(target))]));
247
248 let f_intent_valid = filter_intent
249 .validate(self.get_schema())
250 .map_err(OperationError::SchemaViolation)?;
251
252 let f_valid = f_intent_valid.clone().into_ignore_hidden();
253
254 let se = SearchEvent {
255 ident: create_event.ident,
256 filter: f_valid,
257 filter_orig: f_intent_valid,
258 attrs: None,
260 effective_access_check: false,
261 };
262
263 let mut vs = self.search_ext(&se)?;
264 match vs.pop() {
265 Some(entry) if vs.is_empty() => entry.to_scim_kanidm(self),
266 _ => {
267 if vs.is_empty() {
268 Err(OperationError::NoMatchingEntries)
269 } else {
270 Err(OperationError::UniqueConstraintViolation)
272 }
273 }
274 }
275 }
276
277 pub fn scim_delete(&mut self, scim_delete: ScimDeleteEvent) -> Result<(), OperationError> {
278 let ScimDeleteEvent {
279 ident,
280 target,
281 class,
282 } = scim_delete;
283
284 let filter_intent = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(target)));
285 let f_intent_valid = filter_intent
286 .validate(self.get_schema())
287 .map_err(OperationError::SchemaViolation)?;
288
289 let filter = filter!(f_and!([
290 f_eq(Attribute::Uuid, PartialValue::Uuid(target)),
291 f_eq(Attribute::Class, class.into())
292 ]));
293 let f_valid = filter
294 .validate(self.get_schema())
295 .map_err(OperationError::SchemaViolation)?;
296
297 let de = DeleteEvent {
298 ident,
299 filter: f_valid,
300 filter_orig: f_intent_valid,
301 };
302
303 self.delete(&de)
304 }
305
306 pub fn scim_assert(&mut self, scim_assert: ScimAssertEvent) -> Result<(), OperationError> {
307 let ScimAssertEvent {
308 ident,
309 asserts,
310 id,
311 nonce,
312 } = scim_assert;
313
314 let once = match nonce {
315 None => AssertOnce::No,
316 Some(nonce) => AssertOnce::Yes { id, nonce },
317 };
318
319 self.txn_name_to_uuid().extend(asserts.iter().filter_map(
322 |scim_assert| match scim_assert {
323 ScimEntryAssertion::Present { id, attrs } => {
324 attrs
325 .get(&Attribute::Name)
326 .and_then(|value| match value {
327 Some(JsonValue::String(name)) => Some(name.clone()),
329 _ => None,
330 })
331 .map(|name| (name, *id))
332 }
333 ScimEntryAssertion::Absent { .. } => None,
334 },
335 ));
336
337 let asserts = asserts
339 .into_iter()
340 .map(|scim_assert| match scim_assert {
341 ScimEntryAssertion::Present { id, attrs } => {
342 let attrs = attrs
343 .into_iter()
344 .map(|(attr, json_value)| {
345 self.resolve_scim_json_put(&attr, json_value)
346 .map(|kani_value| (attr, kani_value))
347 })
348 .collect::<Result<_, _>>()?;
349
350 Ok(EntryAssertion::Present { target: id, attrs })
351 }
352 ScimEntryAssertion::Absent { id } => Ok(EntryAssertion::Absent { target: id }),
353 })
354 .collect::<Result<Vec<_>, _>>()?;
355
356 let assert_event = AssertEvent {
357 ident,
358 asserts,
359 once,
360 };
361
362 self.assert(assert_event)
363 }
364
365 pub(crate) fn resolve_scim_json_put(
366 &mut self,
367 attr: &Attribute,
368 value: Option<JsonValue>,
369 ) -> Result<Option<ValueSet>, OperationError> {
370 let schema = self.get_schema();
371 let Some(schema_a) = schema.get_attributes().get(attr) else {
373 return Err(OperationError::InvalidAttributeName(attr.to_string()));
376 };
377
378 let Some(value) = value else {
379 return Ok(None);
382 };
383
384 self.resolve_scim_json(schema_a, value).map(Some)
385 }
386
387 pub(crate) fn resolve_scim_json_post(
388 &mut self,
389 attr: &Attribute,
390 value: JsonValue,
391 ) -> Result<ValueSet, OperationError> {
392 let schema = self.get_schema();
393 let Some(schema_a) = schema.get_attributes().get(attr) else {
395 return Err(OperationError::InvalidAttributeName(attr.to_string()));
398 };
399
400 self.resolve_scim_json(schema_a, value)
401 }
402
403 fn resolve_scim_json(
404 &mut self,
405 schema_a: &SchemaAttribute,
406 value: JsonValue,
407 ) -> Result<ValueSet, OperationError> {
408 let resolve_status = match schema_a.syntax {
409 SyntaxType::Utf8String => ValueSetUtf8::from_scim_json_put(value),
410 SyntaxType::Utf8StringInsensitive => ValueSetIutf8::from_scim_json_put(value),
411 SyntaxType::Uuid => ValueSetUuid::from_scim_json_put(value),
412 SyntaxType::Boolean => ValueSetBool::from_scim_json_put(value),
413 SyntaxType::SyntaxId => ValueSetSyntax::from_scim_json_put(value),
414 SyntaxType::IndexId => ValueSetIndex::from_scim_json_put(value),
415 SyntaxType::ReferenceUuid => ValueSetRefer::from_scim_json_put(value),
416 SyntaxType::Utf8StringIname => ValueSetIname::from_scim_json_put(value),
417 SyntaxType::NsUniqueId => ValueSetNsUniqueId::from_scim_json_put(value),
418 SyntaxType::DateTime => ValueSetDateTime::from_scim_json_put(value),
419 SyntaxType::EmailAddress => ValueSetEmailAddress::from_scim_json_put(value),
420 SyntaxType::Url => ValueSetUrl::from_scim_json_put(value),
421 SyntaxType::OauthScope => ValueSetOauthScope::from_scim_json_put(value),
422 SyntaxType::OauthScopeMap => ValueSetOauthScopeMap::from_scim_json_put(value),
423 SyntaxType::OauthClaimMap => ValueSetOauthClaimMap::from_scim_json_put(value),
424 SyntaxType::UiHint => ValueSetUiHint::from_scim_json_put(value),
425 SyntaxType::CredentialType => ValueSetCredentialType::from_scim_json_put(value),
426 SyntaxType::Certificate => ValueSetCertificate::from_scim_json_put(value),
427 SyntaxType::SshKey => ValueSetSshKey::from_scim_json_put(value),
428 SyntaxType::Uint32 => ValueSetUint32::from_scim_json_put(value),
429 SyntaxType::Int64 => ValueSetInt64::from_scim_json_put(value),
430 SyntaxType::Uint64 => ValueSetUint64::from_scim_json_put(value),
431 SyntaxType::Sha256 => ValueSetSha256::from_scim_json_put(value),
432
433 SyntaxType::JsonFilter => Err(OperationError::InvalidAttribute(
436 "Json Filters are not able to be set.".to_string(),
437 )),
438 SyntaxType::Json => Err(OperationError::InvalidAttribute(
440 "Json values are not able to be set.".to_string(),
441 )),
442 SyntaxType::Message => Err(OperationError::InvalidAttribute(
443 "Message values are not able to be set.".to_string(),
444 )),
445 SyntaxType::HexString => Err(OperationError::InvalidAttribute(
448 "Hex strings are not able to be set.".to_string(),
449 )),
450
451 SyntaxType::Image => Err(OperationError::InvalidAttribute(
454 "Images are not able to be set.".to_string(),
455 )),
456
457 SyntaxType::WebauthnAttestationCaList => Err(OperationError::InvalidAttribute(
462 "Webauthn Attestation Ca Lists are not able to be set.".to_string(),
463 )),
464
465 SyntaxType::Credential => Err(OperationError::InvalidAttribute(
467 "Credentials are not able to be set.".to_string(),
468 )),
469 SyntaxType::SecretUtf8String => Err(OperationError::InvalidAttribute(
470 "Secrets are not able to be set.".to_string(),
471 )),
472 SyntaxType::SecurityPrincipalName => Err(OperationError::InvalidAttribute(
473 "SPNs are not able to be set.".to_string(),
474 )),
475 SyntaxType::Cid => Err(OperationError::InvalidAttribute(
476 "CIDs are not able to be set.".to_string(),
477 )),
478 SyntaxType::PrivateBinary => Err(OperationError::InvalidAttribute(
479 "Private Binaries are not able to be set.".to_string(),
480 )),
481 SyntaxType::IntentToken => Err(OperationError::InvalidAttribute(
482 "Intent Tokens are not able to be set.".to_string(),
483 )),
484 SyntaxType::Passkey => Err(OperationError::InvalidAttribute(
485 "Passkeys are not able to be set.".to_string(),
486 )),
487 SyntaxType::AttestedPasskey => Err(OperationError::InvalidAttribute(
488 "Attested Passkeys are not able to be set.".to_string(),
489 )),
490 SyntaxType::Session => Err(OperationError::InvalidAttribute(
491 "Sessions are not able to be set.".to_string(),
492 )),
493 SyntaxType::JwsKeyEs256 => Err(OperationError::InvalidAttribute(
494 "Jws ES256 Private Keys are not able to be set.".to_string(),
495 )),
496 SyntaxType::JwsKeyRs256 => Err(OperationError::InvalidAttribute(
497 "Jws RS256 Private Keys are not able to be set.".to_string(),
498 )),
499 SyntaxType::Oauth2Session => Err(OperationError::InvalidAttribute(
500 "Sessions are not able to be set.".to_string(),
501 )),
502 SyntaxType::TotpSecret => Err(OperationError::InvalidAttribute(
503 "TOTP Secrets are not able to be set.".to_string(),
504 )),
505 SyntaxType::ApiToken => Err(OperationError::InvalidAttribute(
506 "API Tokens are not able to be set.".to_string(),
507 )),
508 SyntaxType::AuditLogString => Err(OperationError::InvalidAttribute(
509 "Audit Strings are not able to be set.".to_string(),
510 )),
511 SyntaxType::EcKeyPrivate => Err(OperationError::InvalidAttribute(
512 "EC Private Keys are not able to be set.".to_string(),
513 )),
514 SyntaxType::KeyInternal => Err(OperationError::InvalidAttribute(
515 "Key Internal Structures are not able to be set.".to_string(),
516 )),
517 SyntaxType::ApplicationPassword => Err(OperationError::InvalidAttribute(
518 "Application Passwords are not able to be set.".to_string(),
519 )),
520 }?;
521
522 match resolve_status {
523 ValueSetResolveStatus::Resolved(vs) => Ok(vs),
524 ValueSetResolveStatus::NeedsResolution(vs_inter) => {
525 self.resolve_valueset_intermediate(vs_inter)
526 }
527 }
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use super::{ScimAssertEvent, ScimEntryPutEvent};
534 use crate::prelude::*;
535 use kanidm_proto::scim_v1::client::{
536 ScimEntryAssertion, ScimEntryPutKanidm, ScimReference as ScimClientReference,
537 };
538 use kanidm_proto::scim_v1::server::ScimReference;
539 use kanidm_proto::scim_v1::ScimMail;
540 use std::collections::BTreeMap;
541
542 #[qs_test]
543 async fn scim_put_basic(server: &QueryServer) {
544 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
545
546 let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
547
548 let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
549
550 let group_uuid = Uuid::new_v4();
552
553 let extra1_uuid = Uuid::new_v4();
555 let extra2_uuid = Uuid::new_v4();
556 let extra3_uuid = Uuid::new_v4();
557
558 let e1 = entry_init!(
559 (Attribute::Class, EntryClass::Object.to_value()),
560 (Attribute::Class, EntryClass::Group.to_value()),
561 (Attribute::Name, Value::new_iname("testgroup")),
562 (Attribute::Uuid, Value::Uuid(group_uuid))
563 );
564
565 let e2 = entry_init!(
566 (Attribute::Class, EntryClass::Object.to_value()),
567 (Attribute::Class, EntryClass::Group.to_value()),
568 (Attribute::Name, Value::new_iname("extra_1")),
569 (Attribute::Uuid, Value::Uuid(extra1_uuid))
570 );
571
572 let e3 = entry_init!(
573 (Attribute::Class, EntryClass::Object.to_value()),
574 (Attribute::Class, EntryClass::Group.to_value()),
575 (Attribute::Name, Value::new_iname("extra_2")),
576 (Attribute::Uuid, Value::Uuid(extra2_uuid))
577 );
578
579 let e4 = entry_init!(
580 (Attribute::Class, EntryClass::Object.to_value()),
581 (Attribute::Class, EntryClass::Group.to_value()),
582 (Attribute::Name, Value::new_iname("extra_3")),
583 (Attribute::Uuid, Value::Uuid(extra3_uuid))
584 );
585
586 assert!(server_txn.internal_create(vec![e1, e2, e3, e4]).is_ok());
587
588 let test_mails = vec![
590 ScimMail {
591 primary: true,
592 value: "test@test.test".to_string(),
593 },
594 ScimMail {
595 primary: false,
596 value: "test2@test.test".to_string(),
597 },
598 ];
599 let put = ScimEntryPutKanidm {
600 id: group_uuid,
601 attrs: [
602 (Attribute::Description, Some("Group Description".into())),
603 (
604 Attribute::Mail,
605 Some(ScimValueKanidm::Mail(test_mails.clone())),
606 ),
607 ]
608 .into(),
609 };
610
611 let put_generic = put.try_into().unwrap();
612 let put_event =
613 ScimEntryPutEvent::try_from(idm_admin_ident.clone(), put_generic, &mut server_txn)
614 .expect("Failed to resolve data type");
615
616 let updated_entry = server_txn.scim_put(put_event).expect("Failed to put");
617 let desc = updated_entry.attrs.get(&Attribute::Description).unwrap();
618 let mails = updated_entry.attrs.get(&Attribute::Mail).unwrap();
619
620 match desc {
621 ScimValueKanidm::String(gdesc) if gdesc == "Group Description" => {}
622 _ => unreachable!("Expected a string"),
623 };
624
625 let ScimValueKanidm::Mail(mails) = mails else {
626 unreachable!("Expected an email")
627 };
628
629 assert!(mails.iter().all(|mail| test_mails.contains(mail)));
631
632 let put = ScimEntryPutKanidm {
634 id: group_uuid,
635 attrs: [(Attribute::Description, None)].into(),
636 };
637
638 let put_generic = put.try_into().unwrap();
639 let put_event =
640 ScimEntryPutEvent::try_from(idm_admin_ident.clone(), put_generic, &mut server_txn)
641 .expect("Failed to resolve data type");
642
643 let updated_entry = server_txn.scim_put(put_event).expect("Failed to put");
644 assert!(!updated_entry.attrs.contains_key(&Attribute::Description));
645
646 let put = ScimEntryPutKanidm {
648 id: group_uuid,
649 attrs: [(
650 Attribute::Member,
651 Some(ScimValueKanidm::EntryReferences(vec![ScimReference {
652 uuid: extra1_uuid,
653 value: String::default(),
655 }])),
656 )]
657 .into(),
658 };
659
660 let put_generic = put.try_into().unwrap();
661 let put_event =
662 ScimEntryPutEvent::try_from(idm_admin_ident.clone(), put_generic, &mut server_txn)
663 .expect("Failed to resolve data type");
664
665 let updated_entry = server_txn.scim_put(put_event).expect("Failed to put");
666 let members = updated_entry.attrs.get(&Attribute::Member).unwrap();
667
668 trace!(?members);
669
670 match members {
671 ScimValueKanidm::EntryReferences(member_set) if member_set.len() == 1 => {
672 assert!(member_set.contains(&ScimReference {
673 uuid: extra1_uuid,
674 value: "extra_1@example.com".to_string(),
675 }));
676 }
677 _ => unreachable!("Expected 1 member"),
678 };
679
680 let put = ScimEntryPutKanidm {
682 id: group_uuid,
683 attrs: [(
684 Attribute::Member,
685 Some(ScimValueKanidm::EntryReferences(vec![
686 ScimReference {
687 uuid: extra1_uuid,
688 value: String::default(),
689 },
690 ScimReference {
691 uuid: extra2_uuid,
692 value: String::default(),
693 },
694 ScimReference {
695 uuid: extra3_uuid,
696 value: String::default(),
697 },
698 ])),
699 )]
700 .into(),
701 };
702
703 let put_generic = put.try_into().unwrap();
704 let put_event =
705 ScimEntryPutEvent::try_from(idm_admin_ident.clone(), put_generic, &mut server_txn)
706 .expect("Failed to resolve data type");
707
708 let updated_entry = server_txn.scim_put(put_event).expect("Failed to put");
709 let members = updated_entry.attrs.get(&Attribute::Member).unwrap();
710
711 trace!(?members);
712
713 match members {
714 ScimValueKanidm::EntryReferences(member_set) if member_set.len() == 3 => {
715 assert!(member_set.contains(&ScimReference {
716 uuid: extra1_uuid,
717 value: "extra_1@example.com".to_string(),
718 }));
719 assert!(member_set.contains(&ScimReference {
720 uuid: extra2_uuid,
721 value: "extra_2@example.com".to_string(),
722 }));
723 assert!(member_set.contains(&ScimReference {
724 uuid: extra3_uuid,
725 value: "extra_3@example.com".to_string(),
726 }));
727 }
728 _ => unreachable!("Expected 3 members"),
729 };
730
731 let put = ScimEntryPutKanidm {
733 id: group_uuid,
734 attrs: [(
735 Attribute::Member,
736 Some(ScimValueKanidm::EntryReferences(vec![
737 ScimReference {
738 uuid: extra1_uuid,
739 value: String::default(),
740 },
741 ScimReference {
742 uuid: extra3_uuid,
743 value: String::default(),
744 },
745 ])),
746 )]
747 .into(),
748 };
749
750 let put_generic = put.try_into().unwrap();
751 let put_event =
752 ScimEntryPutEvent::try_from(idm_admin_ident.clone(), put_generic, &mut server_txn)
753 .expect("Failed to resolve data type");
754
755 let updated_entry = server_txn.scim_put(put_event).expect("Failed to put");
756 let members = updated_entry.attrs.get(&Attribute::Member).unwrap();
757
758 trace!(?members);
759
760 match members {
761 ScimValueKanidm::EntryReferences(member_set) if member_set.len() == 2 => {
762 assert!(member_set.contains(&ScimReference {
763 uuid: extra1_uuid,
764 value: "extra_1@example.com".to_string(),
765 }));
766 assert!(member_set.contains(&ScimReference {
767 uuid: extra3_uuid,
768 value: "extra_3@example.com".to_string(),
769 }));
770 assert!(!member_set.contains(&ScimReference {
772 uuid: extra2_uuid,
773 value: "extra_2@example.com".to_string(),
774 }));
775 }
776 _ => unreachable!("Expected 2 members"),
777 };
778
779 let put = ScimEntryPutKanidm {
781 id: group_uuid,
782 attrs: [(Attribute::Member, None)].into(),
783 };
784
785 let put_generic = put.try_into().unwrap();
786 let put_event =
787 ScimEntryPutEvent::try_from(idm_admin_ident.clone(), put_generic, &mut server_txn)
788 .expect("Failed to resolve data type");
789
790 let updated_entry = server_txn.scim_put(put_event).expect("Failed to put");
791 assert!(!updated_entry.attrs.contains_key(&Attribute::Member));
792 }
793
794 #[qs_test]
795 async fn scim_assert_basic(server: &QueryServer) {
796 let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
797
798 let ident = Identity::migration();
799
800 let uuid_group_1 = Uuid::new_v4();
801 let uuid_group_2 = Uuid::new_v4();
802
803 let asserts = vec![
804 ScimEntryAssertion::Present {
805 id: uuid_group_1,
806 attrs: BTreeMap::from([
807 (Attribute::Name, Some(JsonValue::String("group_1".into()))),
808 (
809 Attribute::Class,
810 Some(serde_json::to_value(vec!["group"]).unwrap()),
811 ),
812 (
813 Attribute::Member,
814 Some(serde_json::to_value(ScimClientReference::from("group_2")).unwrap()),
815 ),
816 ]),
817 },
818 ScimEntryAssertion::Present {
819 id: uuid_group_2,
820 attrs: BTreeMap::from([
821 (Attribute::Name, Some(JsonValue::String("group_2".into()))),
822 (
823 Attribute::Class,
824 Some(serde_json::to_value(vec!["group"]).unwrap()),
825 ),
826 (
827 Attribute::Member,
828 Some(serde_json::to_value(ScimClientReference::from("group_1")).unwrap()),
829 ),
830 ]),
831 },
832 ];
833
834 let scim_assert = ScimAssertEvent {
835 ident,
836 asserts,
837 id: Uuid::new_v4(),
838 nonce: None,
839 };
840
841 server_txn.scim_assert(scim_assert).expect("Must not fail!");
842 }
843}