kanidmd_lib/valueset/
cred.rs

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    // We need to allow this, because rust doesn't allow us to impl FromIterator on foreign
52    // types, and tuples are always foreign.
53    #[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        // Currently I think we don't need to yield cred info as that's part of the
146        // cred update session instead.
147        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        // Looks like we may not need this?
176        if let Some(other) = other.as_credential_map() {
177            &self.map == other
178        } else {
179            // debug_assert!(false);
180            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    // We need to allow this, because rust doesn't allow us to impl FromIterator on foreign
282    // types, and tuples are always foreign.
283    #[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        // Could consider making this a TS capable entry.
321        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        // Im not sure this actually needs repl handling ...
492        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    // We need to allow this, because rust doesn't allow us to impl FromIterator on foreign
527    // types, and tuples are always foreign.
528    #[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        // Looks like we may not need this?
643        if let Some(other) = other.as_passkey_map() {
644            &self.map == other
645        } else {
646            // debug_assert!(false);
647            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    // We need to allow this, because rust doesn't allow us to impl FromIterator on foreign
700    // types, and tuples are always foreign.
701    #[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        // Looks like we may not need this?
816        if let Some(other) = other.as_attestedpasskey_map() {
817            &self.map == other
818        } else {
819            // debug_assert!(false);
820            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    /*
834    fn to_attestedpasskey_single(&self) -> Option<&AttestedPasskeyV4> {
835        if self.map.len() == 1 {
836            self.map.values().take(1).next().map(|(_, k)| k)
837        } else {
838            None
839        }
840    }
841    */
842
843    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    // We need to allow this, because rust doesn't allow us to impl FromIterator on foreign
871    // types, and uuid is foreign.
872    #[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        // self.set.iter().map(|u| u.to_string()).collect()
1087        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        // Should we actually be looking through the ca-list as given and eliminate
1096        // known vuln devices?
1097        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        // I seem to recall this shouldn't have a value returned?
1170        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        // Test that we can parse json values into a valueset.
1195        crate::valueset::scim_json_put_reflexive::<ValueSetCredentialType>(&vs, &[])
1196    }
1197}