1use crate::be::dbvalue::{
2 DbValueAttestedPasskeyV1, DbValueCredV1, DbValueIntentTokenStateV1, DbValuePasskeyV1,
3};
4use crate::credential::Credential;
5use crate::prelude::*;
6use crate::schema::SchemaAttribute;
7use crate::utils::trigraph_iter;
8use crate::value::{CredUpdateSessionPerms, CredentialType, IntentTokenState};
9use crate::valueset::{
10 DbValueSetV2, ScimResolveStatus, ValueSet, ValueSetResolveStatus, ValueSetScimPut,
11};
12use kanidm_proto::scim_v1::server::{ScimIntentToken, ScimIntentTokenState};
13use kanidm_proto::scim_v1::JsonValue;
14use smolset::SmolSet;
15use std::collections::btree_map::Entry as BTreeEntry;
16use std::collections::BTreeMap;
17use time::OffsetDateTime;
18use webauthn_rs::prelude::{
19 AttestationCaList, AttestedPasskey as AttestedPasskeyV4, Passkey as PasskeyV4,
20};
21
22#[derive(Debug, Clone)]
23pub struct ValueSetCredential {
24 map: BTreeMap<String, Credential>,
25}
26
27impl ValueSetCredential {
28 pub fn new(t: String, c: Credential) -> Box<Self> {
29 let mut map = BTreeMap::new();
30 map.insert(t, c);
31 Box::new(ValueSetCredential { map })
32 }
33
34 pub fn push(&mut self, t: String, c: Credential) -> bool {
35 self.map.insert(t, c).is_none()
36 }
37
38 pub fn from_dbvs2(data: Vec<DbValueCredV1>) -> Result<ValueSet, OperationError> {
39 let map = data
40 .into_iter()
41 .map(|dc| {
42 let t = dc.tag.clone();
43 Credential::try_from(dc.data)
44 .map_err(|()| OperationError::InvalidValueState)
45 .map(|c| (t, c))
46 })
47 .collect::<Result<_, _>>()?;
48 Ok(Box::new(ValueSetCredential { map }))
49 }
50
51 #[allow(clippy::should_implement_trait)]
54 pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
55 where
56 T: IntoIterator<Item = (String, Credential)>,
57 {
58 let map = iter.into_iter().collect();
59 Some(Box::new(ValueSetCredential { map }))
60 }
61}
62
63impl ValueSetT for ValueSetCredential {
64 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
65 match value {
66 Value::Cred(t, c) => {
67 if let BTreeEntry::Vacant(e) = self.map.entry(t) {
68 e.insert(c);
69 Ok(true)
70 } else {
71 Ok(false)
72 }
73 }
74 _ => Err(OperationError::InvalidValueState),
75 }
76 }
77
78 fn clear(&mut self) {
79 self.map.clear();
80 }
81
82 fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
83 match pv {
84 PartialValue::Cred(t) => self.map.remove(t.as_str()).is_some(),
85 _ => false,
86 }
87 }
88
89 fn contains(&self, pv: &PartialValue) -> bool {
90 match pv {
91 PartialValue::Cred(t) => self.map.contains_key(t.as_str()),
92 _ => false,
93 }
94 }
95
96 fn substring(&self, _pv: &PartialValue) -> bool {
97 false
98 }
99
100 fn startswith(&self, _pv: &PartialValue) -> bool {
101 false
102 }
103
104 fn endswith(&self, _pv: &PartialValue) -> bool {
105 false
106 }
107
108 fn lessthan(&self, _pv: &PartialValue) -> bool {
109 false
110 }
111
112 fn len(&self) -> usize {
113 self.map.len()
114 }
115
116 fn generate_idx_eq_keys(&self) -> Vec<String> {
117 self.map.keys().cloned().collect()
118 }
119
120 fn generate_idx_sub_keys(&self) -> Vec<String> {
121 let lower: Vec<_> = self.map.keys().map(|s| s.to_lowercase()).collect();
122 let mut trigraphs: Vec<_> = lower.iter().flat_map(|v| trigraph_iter(v)).collect();
123
124 trigraphs.sort_unstable();
125 trigraphs.dedup();
126
127 trigraphs.into_iter().map(String::from).collect()
128 }
129
130 fn syntax(&self) -> SyntaxType {
131 SyntaxType::Credential
132 }
133
134 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
135 self.map
136 .iter()
137 .all(|(s, _)| Value::validate_str_escapes(s) && Value::validate_singleline(s))
138 }
139
140 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
141 Box::new(self.map.keys().cloned())
142 }
143
144 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
145 None
148 }
149
150 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
151 DbValueSetV2::Credential(
152 self.map
153 .iter()
154 .map(|(tag, cred)| DbValueCredV1 {
155 tag: tag.clone(),
156 data: cred.to_db_valuev1(),
157 })
158 .collect(),
159 )
160 }
161
162 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
163 Box::new(self.map.keys().cloned().map(PartialValue::Cred))
164 }
165
166 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
167 Box::new(
168 self.map
169 .iter()
170 .map(|(t, c)| Value::Cred(t.clone(), c.clone())),
171 )
172 }
173
174 fn equal(&self, other: &ValueSet) -> bool {
175 if let Some(other) = other.as_credential_map() {
177 &self.map == other
178 } else {
179 false
181 }
182 }
183
184 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
185 if let Some(b) = other.as_credential_map() {
186 mergemaps!(self.map, b)
187 } else {
188 debug_assert!(false);
189 Err(OperationError::InvalidValueState)
190 }
191 }
192
193 fn to_credential_single(&self) -> Option<&Credential> {
194 if self.map.len() == 1 {
195 self.map.values().take(1).next()
196 } else {
197 None
198 }
199 }
200
201 fn as_credential_map(&self) -> Option<&BTreeMap<String, Credential>> {
202 Some(&self.map)
203 }
204}
205
206#[derive(Debug, Clone)]
207pub struct ValueSetIntentToken {
208 map: BTreeMap<String, IntentTokenState>,
209}
210
211impl ValueSetIntentToken {
212 pub fn new(t: String, s: IntentTokenState) -> Box<Self> {
213 let mut map = BTreeMap::new();
214 map.insert(t, s);
215 Box::new(ValueSetIntentToken { map })
216 }
217
218 pub fn push(&mut self, t: String, s: IntentTokenState) -> bool {
219 self.map.insert(t, s).is_none()
220 }
221
222 pub fn from_dbvs2(
223 data: Vec<(String, DbValueIntentTokenStateV1)>,
224 ) -> Result<ValueSet, OperationError> {
225 let map = data
226 .into_iter()
227 .map(|(s, dits)| {
228 let ts = match dits {
229 DbValueIntentTokenStateV1::Valid {
230 max_ttl,
231 ext_cred_portal_can_view,
232 primary_can_edit,
233 passkeys_can_edit,
234 attested_passkeys_can_edit,
235 unixcred_can_edit,
236 sshpubkey_can_edit,
237 } => IntentTokenState::Valid {
238 max_ttl,
239 perms: CredUpdateSessionPerms {
240 ext_cred_portal_can_view,
241 primary_can_edit,
242 passkeys_can_edit,
243 attested_passkeys_can_edit,
244 unixcred_can_edit,
245 sshpubkey_can_edit,
246 },
247 },
248 DbValueIntentTokenStateV1::InProgress {
249 max_ttl,
250 session_id,
251 session_ttl,
252 ext_cred_portal_can_view,
253 primary_can_edit,
254 passkeys_can_edit,
255 attested_passkeys_can_edit,
256 unixcred_can_edit,
257 sshpubkey_can_edit,
258 } => IntentTokenState::InProgress {
259 max_ttl,
260 session_id,
261 session_ttl,
262 perms: CredUpdateSessionPerms {
263 ext_cred_portal_can_view,
264 primary_can_edit,
265 passkeys_can_edit,
266 attested_passkeys_can_edit,
267 unixcred_can_edit,
268 sshpubkey_can_edit,
269 },
270 },
271 DbValueIntentTokenStateV1::Consumed { max_ttl } => {
272 IntentTokenState::Consumed { max_ttl }
273 }
274 };
275 (s, ts)
276 })
277 .collect();
278 Ok(Box::new(ValueSetIntentToken { map }))
279 }
280
281 #[allow(clippy::should_implement_trait)]
284 pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
285 where
286 T: IntoIterator<Item = (String, IntentTokenState)>,
287 {
288 let map = iter.into_iter().collect();
289 Some(Box::new(ValueSetIntentToken { map }))
290 }
291}
292
293impl ValueSetT for ValueSetIntentToken {
294 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
295 match value {
296 Value::IntentToken(u, s) => {
297 if let BTreeEntry::Vacant(e) = self.map.entry(u) {
298 e.insert(s);
299 Ok(true)
300 } else {
301 Ok(false)
302 }
303 }
304 _ => Err(OperationError::InvalidValueState),
305 }
306 }
307
308 fn clear(&mut self) {
309 self.map.clear();
310 }
311
312 fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
313 match pv {
314 PartialValue::IntentToken(u) => self.map.remove(u).is_some(),
315 _ => false,
316 }
317 }
318
319 fn purge(&mut self, _cid: &Cid) -> bool {
320 true
322 }
323
324 fn contains(&self, pv: &PartialValue) -> bool {
325 match pv {
326 PartialValue::IntentToken(u) => self.map.contains_key(u),
327 _ => false,
328 }
329 }
330
331 fn substring(&self, _pv: &PartialValue) -> bool {
332 false
333 }
334
335 fn startswith(&self, _pv: &PartialValue) -> bool {
336 false
337 }
338
339 fn endswith(&self, _pv: &PartialValue) -> bool {
340 false
341 }
342
343 fn lessthan(&self, _pv: &PartialValue) -> bool {
344 false
345 }
346
347 fn len(&self) -> usize {
348 self.map.len()
349 }
350
351 fn generate_idx_eq_keys(&self) -> Vec<String> {
352 self.map.keys().cloned().collect()
353 }
354
355 fn syntax(&self) -> SyntaxType {
356 SyntaxType::IntentToken
357 }
358
359 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
360 self.map
361 .iter()
362 .all(|(s, _)| Value::validate_str_escapes(s) && Value::validate_singleline(s))
363 }
364
365 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
366 Box::new(self.map.keys().cloned())
367 }
368
369 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
370 Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
371 self.map
372 .iter()
373 .map(|(token_id, intent_token_state)| {
374 let (state, max_ttl) = match intent_token_state {
375 IntentTokenState::Valid { max_ttl, .. } => {
376 (ScimIntentTokenState::Valid, *max_ttl)
377 }
378 IntentTokenState::InProgress { max_ttl, .. } => {
379 (ScimIntentTokenState::InProgress, *max_ttl)
380 }
381 IntentTokenState::Consumed { max_ttl } => {
382 (ScimIntentTokenState::Consumed, *max_ttl)
383 }
384 };
385
386 let odt: OffsetDateTime = OffsetDateTime::UNIX_EPOCH + max_ttl;
387
388 ScimIntentToken {
389 token_id: token_id.clone(),
390 state,
391 expires: odt,
392 }
393 })
394 .collect::<Vec<_>>(),
395 )))
396 }
397
398 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
399 DbValueSetV2::IntentToken(
400 self.map
401 .iter()
402 .map(|(u, s)| {
403 (
404 u.clone(),
405 match s {
406 IntentTokenState::Valid {
407 max_ttl,
408 perms:
409 CredUpdateSessionPerms {
410 ext_cred_portal_can_view,
411 primary_can_edit,
412 passkeys_can_edit,
413 attested_passkeys_can_edit,
414 unixcred_can_edit,
415 sshpubkey_can_edit,
416 },
417 } => DbValueIntentTokenStateV1::Valid {
418 max_ttl: *max_ttl,
419 ext_cred_portal_can_view: *ext_cred_portal_can_view,
420 primary_can_edit: *primary_can_edit,
421 passkeys_can_edit: *passkeys_can_edit,
422 attested_passkeys_can_edit: *attested_passkeys_can_edit,
423 unixcred_can_edit: *unixcred_can_edit,
424 sshpubkey_can_edit: *sshpubkey_can_edit,
425 },
426 IntentTokenState::InProgress {
427 max_ttl,
428 session_id,
429 session_ttl,
430 perms:
431 CredUpdateSessionPerms {
432 ext_cred_portal_can_view,
433 primary_can_edit,
434 passkeys_can_edit,
435 attested_passkeys_can_edit,
436 unixcred_can_edit,
437 sshpubkey_can_edit,
438 },
439 } => DbValueIntentTokenStateV1::InProgress {
440 max_ttl: *max_ttl,
441 session_id: *session_id,
442 session_ttl: *session_ttl,
443 ext_cred_portal_can_view: *ext_cred_portal_can_view,
444 primary_can_edit: *primary_can_edit,
445 passkeys_can_edit: *passkeys_can_edit,
446 attested_passkeys_can_edit: *attested_passkeys_can_edit,
447 unixcred_can_edit: *unixcred_can_edit,
448 sshpubkey_can_edit: *sshpubkey_can_edit,
449 },
450 IntentTokenState::Consumed { max_ttl } => {
451 DbValueIntentTokenStateV1::Consumed { max_ttl: *max_ttl }
452 }
453 },
454 )
455 })
456 .collect(),
457 )
458 }
459
460 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
461 Box::new(self.map.keys().cloned().map(PartialValue::IntentToken))
462 }
463
464 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
465 Box::new(
466 self.map
467 .iter()
468 .map(|(u, s)| Value::IntentToken(u.clone(), s.clone())),
469 )
470 }
471
472 fn equal(&self, other: &ValueSet) -> bool {
473 if let Some(other) = other.as_intenttoken_map() {
474 &self.map == other
475 } else {
476 debug_assert!(false);
477 false
478 }
479 }
480
481 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
482 if let Some(b) = other.as_intenttoken_map() {
483 mergemaps!(self.map, b)
484 } else {
485 debug_assert!(false);
486 Err(OperationError::InvalidValueState)
487 }
488 }
489
490 fn repl_merge_valueset(&self, _older: &ValueSet, _trim_cid: &Cid) -> Option<ValueSet> {
491 None
493 }
494
495 fn as_intenttoken_map(&self) -> Option<&BTreeMap<String, IntentTokenState>> {
496 Some(&self.map)
497 }
498}
499
500#[derive(Debug, Clone)]
501pub struct ValueSetPasskey {
502 map: BTreeMap<Uuid, (String, PasskeyV4)>,
503}
504
505impl ValueSetPasskey {
506 pub fn new(u: Uuid, t: String, k: PasskeyV4) -> Box<Self> {
507 let mut map = BTreeMap::new();
508 map.insert(u, (t, k));
509 Box::new(ValueSetPasskey { map })
510 }
511
512 pub fn push(&mut self, u: Uuid, t: String, k: PasskeyV4) -> bool {
513 self.map.insert(u, (t, k)).is_none()
514 }
515
516 pub fn from_dbvs2(data: Vec<DbValuePasskeyV1>) -> Result<ValueSet, OperationError> {
517 let map = data
518 .into_iter()
519 .map(|k| match k {
520 DbValuePasskeyV1::V4 { u, t, k } => Ok((u, (t, k))),
521 })
522 .collect::<Result<_, _>>()?;
523 Ok(Box::new(ValueSetPasskey { map }))
524 }
525
526 #[allow(clippy::should_implement_trait)]
529 pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
530 where
531 T: IntoIterator<Item = (Uuid, String, PasskeyV4)>,
532 {
533 let map = iter.into_iter().map(|(u, t, k)| (u, (t, k))).collect();
534 Some(Box::new(ValueSetPasskey { map }))
535 }
536}
537
538impl ValueSetT for ValueSetPasskey {
539 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
540 match value {
541 Value::Passkey(u, t, k) => {
542 if let BTreeEntry::Vacant(e) = self.map.entry(u) {
543 e.insert((t, k));
544 Ok(true)
545 } else {
546 Ok(false)
547 }
548 }
549 _ => Err(OperationError::InvalidValueState),
550 }
551 }
552
553 fn clear(&mut self) {
554 self.map.clear();
555 }
556
557 fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
558 match pv {
559 PartialValue::Passkey(u) => self.map.remove(u).is_some(),
560 _ => false,
561 }
562 }
563
564 fn contains(&self, pv: &PartialValue) -> bool {
565 match pv {
566 PartialValue::Passkey(u) => self.map.contains_key(u),
567 _ => false,
568 }
569 }
570
571 fn substring(&self, _pv: &PartialValue) -> bool {
572 false
573 }
574
575 fn startswith(&self, _pv: &PartialValue) -> bool {
576 false
577 }
578
579 fn endswith(&self, _pv: &PartialValue) -> bool {
580 false
581 }
582
583 fn lessthan(&self, _pv: &PartialValue) -> bool {
584 false
585 }
586
587 fn len(&self) -> usize {
588 self.map.len()
589 }
590
591 fn generate_idx_eq_keys(&self) -> Vec<String> {
592 self.map
593 .keys()
594 .map(|u| u.as_hyphenated().to_string())
595 .collect()
596 }
597
598 fn syntax(&self) -> SyntaxType {
599 SyntaxType::Passkey
600 }
601
602 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
603 self.map
604 .iter()
605 .all(|(_, (s, _))| Value::validate_str_escapes(s) && Value::validate_singleline(s))
606 }
607
608 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
609 Box::new(self.map.values().map(|(t, _)| t).cloned())
610 }
611
612 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
613 None
614 }
615
616 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
617 DbValueSetV2::Passkey(
618 self.map
619 .iter()
620 .map(|(u, (t, k))| DbValuePasskeyV1::V4 {
621 u: *u,
622 t: t.clone(),
623 k: k.clone(),
624 })
625 .collect(),
626 )
627 }
628
629 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
630 Box::new(self.map.keys().cloned().map(PartialValue::Passkey))
631 }
632
633 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
634 Box::new(
635 self.map
636 .iter()
637 .map(|(u, (t, k))| Value::Passkey(*u, t.clone(), k.clone())),
638 )
639 }
640
641 fn equal(&self, other: &ValueSet) -> bool {
642 if let Some(other) = other.as_passkey_map() {
644 &self.map == other
645 } else {
646 false
648 }
649 }
650
651 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
652 if let Some(b) = other.as_passkey_map() {
653 mergemaps!(self.map, b)
654 } else {
655 debug_assert!(false);
656 Err(OperationError::InvalidValueState)
657 }
658 }
659
660 fn to_passkey_single(&self) -> Option<&PasskeyV4> {
661 if self.map.len() == 1 {
662 self.map.values().take(1).next().map(|(_, k)| k)
663 } else {
664 None
665 }
666 }
667
668 fn as_passkey_map(&self) -> Option<&BTreeMap<Uuid, (String, PasskeyV4)>> {
669 Some(&self.map)
670 }
671}
672
673#[derive(Debug, Clone)]
674pub struct ValueSetAttestedPasskey {
675 map: BTreeMap<Uuid, (String, AttestedPasskeyV4)>,
676}
677
678impl ValueSetAttestedPasskey {
679 pub fn new(u: Uuid, t: String, k: AttestedPasskeyV4) -> Box<Self> {
680 let mut map = BTreeMap::new();
681 map.insert(u, (t, k));
682 Box::new(ValueSetAttestedPasskey { map })
683 }
684
685 pub fn push(&mut self, u: Uuid, t: String, k: AttestedPasskeyV4) -> bool {
686 self.map.insert(u, (t, k)).is_none()
687 }
688
689 pub fn from_dbvs2(data: Vec<DbValueAttestedPasskeyV1>) -> Result<ValueSet, OperationError> {
690 let map = data
691 .into_iter()
692 .map(|k| match k {
693 DbValueAttestedPasskeyV1::V4 { u, t, k } => Ok((u, (t, k))),
694 })
695 .collect::<Result<_, _>>()?;
696 Ok(Box::new(ValueSetAttestedPasskey { map }))
697 }
698
699 #[allow(clippy::should_implement_trait)]
702 pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
703 where
704 T: IntoIterator<Item = (Uuid, String, AttestedPasskeyV4)>,
705 {
706 let map = iter.into_iter().map(|(u, t, k)| (u, (t, k))).collect();
707 Some(Box::new(ValueSetAttestedPasskey { map }))
708 }
709}
710
711impl ValueSetT for ValueSetAttestedPasskey {
712 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
713 match value {
714 Value::AttestedPasskey(u, t, k) => {
715 if let BTreeEntry::Vacant(e) = self.map.entry(u) {
716 e.insert((t, k));
717 Ok(true)
718 } else {
719 Ok(false)
720 }
721 }
722 _ => Err(OperationError::InvalidValueState),
723 }
724 }
725
726 fn clear(&mut self) {
727 self.map.clear();
728 }
729
730 fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
731 match pv {
732 PartialValue::AttestedPasskey(u) => self.map.remove(u).is_some(),
733 _ => false,
734 }
735 }
736
737 fn contains(&self, pv: &PartialValue) -> bool {
738 match pv {
739 PartialValue::AttestedPasskey(u) => self.map.contains_key(u),
740 _ => false,
741 }
742 }
743
744 fn substring(&self, _pv: &PartialValue) -> bool {
745 false
746 }
747
748 fn startswith(&self, _pv: &PartialValue) -> bool {
749 false
750 }
751
752 fn endswith(&self, _pv: &PartialValue) -> bool {
753 false
754 }
755
756 fn lessthan(&self, _pv: &PartialValue) -> bool {
757 false
758 }
759
760 fn len(&self) -> usize {
761 self.map.len()
762 }
763
764 fn generate_idx_eq_keys(&self) -> Vec<String> {
765 self.map
766 .keys()
767 .map(|u| u.as_hyphenated().to_string())
768 .collect()
769 }
770
771 fn syntax(&self) -> SyntaxType {
772 SyntaxType::AttestedPasskey
773 }
774
775 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
776 self.map
777 .iter()
778 .all(|(_, (s, _))| Value::validate_str_escapes(s) && Value::validate_singleline(s))
779 }
780
781 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
782 Box::new(self.map.values().map(|(t, _)| t).cloned())
783 }
784
785 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
786 None
787 }
788
789 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
790 DbValueSetV2::AttestedPasskey(
791 self.map
792 .iter()
793 .map(|(u, (t, k))| DbValueAttestedPasskeyV1::V4 {
794 u: *u,
795 t: t.clone(),
796 k: k.clone(),
797 })
798 .collect(),
799 )
800 }
801
802 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
803 Box::new(self.map.keys().copied().map(PartialValue::AttestedPasskey))
804 }
805
806 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
807 Box::new(
808 self.map
809 .iter()
810 .map(|(u, (t, k))| Value::AttestedPasskey(*u, t.clone(), k.clone())),
811 )
812 }
813
814 fn equal(&self, other: &ValueSet) -> bool {
815 if let Some(other) = other.as_attestedpasskey_map() {
817 &self.map == other
818 } else {
819 false
821 }
822 }
823
824 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
825 if let Some(b) = other.as_attestedpasskey_map() {
826 mergemaps!(self.map, b)
827 } else {
828 debug_assert!(false);
829 Err(OperationError::InvalidValueState)
830 }
831 }
832
833 fn as_attestedpasskey_map(&self) -> Option<&BTreeMap<Uuid, (String, AttestedPasskeyV4)>> {
844 Some(&self.map)
845 }
846}
847
848#[derive(Debug, Clone)]
849pub struct ValueSetCredentialType {
850 set: SmolSet<[CredentialType; 1]>,
851}
852
853impl ValueSetCredentialType {
854 pub fn new(u: CredentialType) -> Box<Self> {
855 let mut set = SmolSet::new();
856 set.insert(u);
857 Box::new(ValueSetCredentialType { set })
858 }
859
860 pub fn push(&mut self, u: CredentialType) -> bool {
861 self.set.insert(u)
862 }
863
864 pub fn from_dbvs2(data: Vec<u16>) -> Result<ValueSet, OperationError> {
865 let set: Result<_, _> = data.into_iter().map(CredentialType::try_from).collect();
866 let set = set.map_err(|_| OperationError::InvalidValueState)?;
867 Ok(Box::new(ValueSetCredentialType { set }))
868 }
869
870 #[allow(clippy::should_implement_trait)]
873 pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
874 where
875 T: IntoIterator<Item = CredentialType>,
876 {
877 let set = iter.into_iter().collect();
878 Some(Box::new(ValueSetCredentialType { set }))
879 }
880}
881
882impl ValueSetScimPut for ValueSetCredentialType {
883 fn from_scim_json_put(value: JsonValue) -> Result<ValueSetResolveStatus, OperationError> {
884 let value = serde_json::from_value::<String>(value)
885 .map_err(|err| {
886 error!(?err, "SCIM CredentialType syntax invalid");
887 OperationError::SC0015CredentialTypeSyntaxInvalid
888 })
889 .and_then(|value| {
890 CredentialType::try_from(value.as_str()).map_err(|()| {
891 error!("SCIM CredentialType syntax invalid - value");
892 OperationError::SC0015CredentialTypeSyntaxInvalid
893 })
894 })?;
895
896 let mut set = SmolSet::new();
897 set.insert(value);
898
899 Ok(ValueSetResolveStatus::Resolved(Box::new(
900 ValueSetCredentialType { set },
901 )))
902 }
903}
904
905impl ValueSetT for ValueSetCredentialType {
906 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
907 match value {
908 Value::CredentialType(u) => Ok(self.set.insert(u)),
909 _ => {
910 debug_assert!(false);
911 Err(OperationError::InvalidValueState)
912 }
913 }
914 }
915
916 fn clear(&mut self) {
917 self.set.clear();
918 }
919
920 fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
921 match pv {
922 PartialValue::CredentialType(u) => self.set.remove(u),
923 _ => {
924 debug_assert!(false);
925 true
926 }
927 }
928 }
929
930 fn contains(&self, pv: &PartialValue) -> bool {
931 match pv {
932 PartialValue::CredentialType(u) => self.set.contains(u),
933 _ => false,
934 }
935 }
936
937 fn substring(&self, _pv: &PartialValue) -> bool {
938 false
939 }
940
941 fn startswith(&self, _pv: &PartialValue) -> bool {
942 false
943 }
944
945 fn endswith(&self, _pv: &PartialValue) -> bool {
946 false
947 }
948
949 fn lessthan(&self, _pv: &PartialValue) -> bool {
950 false
951 }
952
953 fn len(&self) -> usize {
954 self.set.len()
955 }
956
957 fn generate_idx_eq_keys(&self) -> Vec<String> {
958 self.set.iter().map(|u| u.to_string()).collect()
959 }
960
961 fn syntax(&self) -> SyntaxType {
962 SyntaxType::CredentialType
963 }
964
965 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
966 true
967 }
968
969 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
970 Box::new(self.set.iter().map(|ct| ct.to_string()))
971 }
972
973 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
974 self.set
975 .iter()
976 .next()
977 .map(|ct| ScimResolveStatus::Resolved(ScimValueKanidm::from(ct.to_string())))
978 }
979
980 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
981 DbValueSetV2::CredentialType(self.set.iter().map(|s| *s as u16).collect())
982 }
983
984 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
985 Box::new(self.set.iter().copied().map(PartialValue::CredentialType))
986 }
987
988 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
989 Box::new(self.set.iter().copied().map(Value::CredentialType))
990 }
991
992 fn equal(&self, other: &ValueSet) -> bool {
993 if let Some(other) = other.as_credentialtype_set() {
994 &self.set == other
995 } else {
996 debug_assert!(false);
997 false
998 }
999 }
1000
1001 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
1002 if let Some(b) = other.as_credentialtype_set() {
1003 mergesets!(self.set, b)
1004 } else {
1005 debug_assert!(false);
1006 Err(OperationError::InvalidValueState)
1007 }
1008 }
1009
1010 fn to_credentialtype_single(&self) -> Option<CredentialType> {
1011 if self.set.len() == 1 {
1012 self.set.iter().copied().take(1).next()
1013 } else {
1014 None
1015 }
1016 }
1017
1018 fn as_credentialtype_set(&self) -> Option<&SmolSet<[CredentialType; 1]>> {
1019 Some(&self.set)
1020 }
1021}
1022
1023#[derive(Debug, Clone)]
1024pub struct ValueSetWebauthnAttestationCaList {
1025 ca_list: AttestationCaList,
1026}
1027
1028impl ValueSetWebauthnAttestationCaList {
1029 pub fn new(ca_list: AttestationCaList) -> Box<Self> {
1030 Box::new(ValueSetWebauthnAttestationCaList { ca_list })
1031 }
1032
1033 pub fn from_dbvs2(ca_list: AttestationCaList) -> Result<ValueSet, OperationError> {
1034 Ok(Box::new(ValueSetWebauthnAttestationCaList { ca_list }))
1035 }
1036}
1037
1038impl ValueSetT for ValueSetWebauthnAttestationCaList {
1039 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
1040 match value {
1041 Value::WebauthnAttestationCaList(u) => {
1042 self.ca_list.union(&u);
1043 Ok(true)
1044 }
1045 _ => {
1046 debug_assert!(false);
1047 Err(OperationError::InvalidValueState)
1048 }
1049 }
1050 }
1051
1052 fn clear(&mut self) {
1053 self.ca_list.clear();
1054 }
1055
1056 fn remove(&mut self, _pv: &PartialValue, _cid: &Cid) -> bool {
1057 debug_assert!(false);
1058 true
1059 }
1060
1061 fn contains(&self, _pv: &PartialValue) -> bool {
1062 false
1063 }
1064
1065 fn substring(&self, _pv: &PartialValue) -> bool {
1066 false
1067 }
1068
1069 fn startswith(&self, _pv: &PartialValue) -> bool {
1070 false
1071 }
1072
1073 fn endswith(&self, _pv: &PartialValue) -> bool {
1074 false
1075 }
1076
1077 fn lessthan(&self, _pv: &PartialValue) -> bool {
1078 false
1079 }
1080
1081 fn len(&self) -> usize {
1082 self.ca_list.len()
1083 }
1084
1085 fn generate_idx_eq_keys(&self) -> Vec<String> {
1086 Vec::with_capacity(0)
1088 }
1089
1090 fn syntax(&self) -> SyntaxType {
1091 SyntaxType::WebauthnAttestationCaList
1092 }
1093
1094 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
1095 true
1098 }
1099
1100 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
1101 Box::new(
1102 self.ca_list
1103 .cas()
1104 .values()
1105 .flat_map(|att_ca| att_ca.aaguids().values())
1106 .map(|device| device.description_en().to_string()),
1107 )
1108 }
1109
1110 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
1111 DbValueSetV2::WebauthnAttestationCaList {
1112 ca_list: self.ca_list.clone(),
1113 }
1114 }
1115
1116 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
1117 Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
1118 self.ca_list
1119 .cas()
1120 .values()
1121 .flat_map(|att_ca| att_ca.aaguids().values())
1122 .map(|device| device.description_en().to_string())
1123 .collect::<Vec<_>>(),
1124 )))
1125 }
1126
1127 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
1128 Box::new(std::iter::empty::<PartialValue>())
1129 }
1130
1131 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
1132 Box::new(std::iter::once(Value::WebauthnAttestationCaList(
1133 self.ca_list.clone(),
1134 )))
1135 }
1136
1137 fn equal(&self, other: &ValueSet) -> bool {
1138 if let Some(other) = other.as_webauthn_attestation_ca_list() {
1139 &self.ca_list == other
1140 } else {
1141 debug_assert!(false);
1142 false
1143 }
1144 }
1145
1146 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
1147 if let Some(b) = other.as_webauthn_attestation_ca_list() {
1148 self.ca_list.union(b);
1149 Ok(())
1150 } else {
1151 debug_assert!(false);
1152 Err(OperationError::InvalidValueState)
1153 }
1154 }
1155
1156 fn as_webauthn_attestation_ca_list(&self) -> Option<&AttestationCaList> {
1157 Some(&self.ca_list)
1158 }
1159}
1160
1161#[cfg(test)]
1162mod tests {
1163 use super::{CredentialType, IntentTokenState, ValueSetCredentialType, ValueSetIntentToken};
1164 use crate::prelude::ValueSet;
1165 use std::time::Duration;
1166
1167 #[test]
1168 fn test_scim_intent_token() {
1169 let vs: ValueSet = ValueSetIntentToken::new(
1171 "ca6f29d1-034b-41fb-abc1-4bb9f0548e67".to_string(),
1172 IntentTokenState::Consumed {
1173 max_ttl: Duration::from_secs(300),
1174 },
1175 );
1176
1177 let data = r#"
1178[
1179 {
1180 "expires": "1970-01-01T00:05:00Z",
1181 "state": "consumed",
1182 "tokenId": "ca6f29d1-034b-41fb-abc1-4bb9f0548e67"
1183 }
1184]
1185 "#;
1186 crate::valueset::scim_json_reflexive(&vs, data);
1187 }
1188
1189 #[test]
1190 fn test_scim_credential_type() {
1191 let vs: ValueSet = ValueSetCredentialType::new(CredentialType::Mfa);
1192 crate::valueset::scim_json_reflexive(&vs, r#""mfa""#);
1193
1194 crate::valueset::scim_json_put_reflexive::<ValueSetCredentialType>(&vs, &[])
1196 }
1197}