kanidmd_lib/valueset/
address.rs

1use crate::be::dbvalue::DbValueAddressV1;
2use crate::prelude::*;
3use crate::schema::SchemaAttribute;
4use crate::utils::trigraph_iter;
5use crate::value::{Address, VALIDATE_EMAIL_RE};
6use crate::valueset::{
7    DbValueSetV2, ScimResolveStatus, ValueSet, ValueSetResolveStatus, ValueSetScimPut,
8};
9use kanidm_proto::scim_v1::client::ScimAddress as ScimAddressClient;
10use kanidm_proto::scim_v1::JsonValue;
11use kanidm_proto::scim_v1::{server::ScimAddress, ScimMail};
12use smolset::SmolSet;
13use std::collections::BTreeSet;
14
15#[derive(Debug, Clone)]
16pub struct ValueSetAddress {
17    set: SmolSet<[Address; 1]>,
18}
19
20impl ValueSetAddress {
21    pub fn new(b: Address) -> Box<Self> {
22        let mut set = SmolSet::new();
23        set.insert(b);
24        Box::new(ValueSetAddress { set })
25    }
26
27    pub fn push(&mut self, b: Address) -> bool {
28        self.set.insert(b)
29    }
30
31    pub fn from_dbvs2(data: Vec<DbValueAddressV1>) -> Result<ValueSet, OperationError> {
32        let set = data
33            .into_iter()
34            .map(
35                |DbValueAddressV1 {
36                     formatted,
37                     street_address,
38                     locality,
39                     region,
40                     postal_code,
41                     country,
42                 }| {
43                    Address {
44                        formatted,
45                        street_address,
46                        locality,
47                        region,
48                        postal_code,
49                        country,
50                    }
51                },
52            )
53            .collect();
54        Ok(Box::new(ValueSetAddress { set }))
55    }
56}
57
58impl ValueSetScimPut for ValueSetAddress {
59    fn from_scim_json_put(value: JsonValue) -> Result<ValueSetResolveStatus, OperationError> {
60        let addresses: Vec<ScimAddressClient> = serde_json::from_value(value).map_err(|err| {
61            error!(?err, "SCIM Address syntax invalid");
62            OperationError::SC0011AddressSyntaxInvalid
63        })?;
64
65        let set = addresses
66            .into_iter()
67            .map(
68                |ScimAddressClient {
69                     street_address,
70                     locality,
71                     region,
72                     postal_code,
73                     country,
74                 }| {
75                    let formatted =
76                        format!("{street_address}, {locality}, {region}, {postal_code}, {country}");
77                    Address {
78                        formatted,
79                        street_address,
80                        locality,
81                        region,
82                        postal_code,
83                        country,
84                    }
85                },
86            )
87            .collect();
88
89        Ok(ValueSetResolveStatus::Resolved(Box::new(ValueSetAddress {
90            set,
91        })))
92    }
93}
94
95impl FromIterator<Address> for Option<Box<ValueSetAddress>> {
96    fn from_iter<T>(iter: T) -> Option<Box<ValueSetAddress>>
97    where
98        T: IntoIterator<Item = Address>,
99    {
100        let set = iter.into_iter().collect();
101        Some(Box::new(ValueSetAddress { set }))
102    }
103}
104
105impl ValueSetT for ValueSetAddress {
106    fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
107        match value {
108            Value::Address(u) => Ok(self.set.insert(u)),
109            _ => {
110                debug_assert!(false);
111                Err(OperationError::InvalidValueState)
112            }
113        }
114    }
115
116    fn clear(&mut self) {
117        self.set.clear();
118    }
119
120    fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
121        match pv {
122            PartialValue::Address(_) => {
123                unreachable!()
124            }
125            _ => {
126                debug_assert!(false);
127                true
128            }
129        }
130    }
131
132    fn contains(&self, pv: &PartialValue) -> bool {
133        match pv {
134            PartialValue::Address(_) => {
135                unreachable!()
136            }
137            _ => false,
138        }
139    }
140
141    fn substring(&self, _pv: &PartialValue) -> bool {
142        false
143    }
144
145    fn startswith(&self, _pv: &PartialValue) -> bool {
146        false
147    }
148
149    fn endswith(&self, _pv: &PartialValue) -> bool {
150        false
151    }
152
153    fn lessthan(&self, _pv: &PartialValue) -> bool {
154        false
155    }
156
157    fn len(&self) -> usize {
158        self.set.len()
159    }
160
161    fn generate_idx_eq_keys(&self) -> Vec<String> {
162        unreachable!();
163        // self.set.iter().map(|b| b.to_string()).collect()
164    }
165
166    fn syntax(&self) -> SyntaxType {
167        unreachable!();
168    }
169
170    fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
171        true
172    }
173
174    fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
175        Box::new(self.set.iter().map(|a| a.formatted.clone()))
176    }
177
178    fn to_scim_value(&self) -> Option<ScimResolveStatus> {
179        Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
180            self.set
181                .iter()
182                .map(|a| ScimAddress {
183                    formatted: a.formatted.clone(),
184                    street_address: a.street_address.clone(),
185                    locality: a.locality.clone(),
186                    region: a.region.clone(),
187                    postal_code: a.postal_code.clone(),
188                    country: a.country.clone(),
189                })
190                .collect::<Vec<_>>(),
191        )))
192    }
193
194    fn to_db_valueset_v2(&self) -> DbValueSetV2 {
195        DbValueSetV2::Address(
196            self.set
197                .iter()
198                .map(|a| DbValueAddressV1 {
199                    formatted: a.formatted.clone(),
200                    street_address: a.street_address.clone(),
201                    locality: a.locality.clone(),
202                    region: a.region.clone(),
203                    postal_code: a.postal_code.clone(),
204                    country: a.country.clone(),
205                })
206                .collect(),
207        )
208    }
209
210    fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
211        Box::new(
212            self.set
213                .iter()
214                .map(|s| PartialValue::Address(s.formatted.clone())),
215        )
216    }
217
218    fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
219        Box::new(self.set.iter().cloned().map(Value::Address))
220    }
221
222    fn equal(&self, other: &ValueSet) -> bool {
223        if let Some(other) = other.as_address_set() {
224            &self.set == other
225        } else {
226            debug_assert!(false);
227            false
228        }
229    }
230
231    fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
232        if let Some(b) = other.as_address_set() {
233            mergesets!(self.set, b)
234        } else {
235            debug_assert!(false);
236            Err(OperationError::InvalidValueState)
237        }
238    }
239
240    /*
241    fn to_address_single(&self) -> Option<&Address> {
242        if self.set.len() == 1 {
243            self.set.iter().take(1).next()
244        } else {
245            None
246        }
247    }
248    */
249
250    fn as_address_set(&self) -> Option<&SmolSet<[Address; 1]>> {
251        Some(&self.set)
252    }
253}
254
255#[derive(Debug, Clone)]
256pub struct ValueSetEmailAddress {
257    primary: String,
258    set: BTreeSet<String>,
259}
260
261impl ValueSetEmailAddress {
262    pub fn new(primary: String) -> Box<Self> {
263        let mut set = BTreeSet::new();
264        set.insert(primary.clone());
265        Box::new(ValueSetEmailAddress { primary, set })
266    }
267
268    pub fn push(&mut self, a: String, primary: bool) -> bool {
269        if primary {
270            self.primary.clone_from(&a);
271        }
272        self.set.insert(a)
273    }
274
275    pub fn from_dbvs2(primary: String, data: Vec<String>) -> Result<ValueSet, OperationError> {
276        let set: BTreeSet<_> = data.into_iter().collect();
277
278        if set.contains(&primary) {
279            Ok(Box::new(ValueSetEmailAddress { primary, set }))
280        } else {
281            Err(OperationError::InvalidValueState)
282        }
283    }
284
285    pub fn from_repl_v1(primary: &str, data: &[String]) -> Result<ValueSet, OperationError> {
286        let set: BTreeSet<_> = data.iter().cloned().collect();
287
288        if set.contains(primary) {
289            Ok(Box::new(ValueSetEmailAddress {
290                primary: primary.to_string(),
291                set,
292            }))
293        } else {
294            Err(OperationError::InvalidValueState)
295        }
296    }
297
298    // We need to allow this, because rust doesn't allow us to impl FromIterator on foreign
299    // types, and tuples are always foreign.
300    #[allow(clippy::should_implement_trait)]
301    pub fn from_iter<T>(iter: T) -> Option<Box<ValueSetEmailAddress>>
302    where
303        T: IntoIterator<Item = (String, bool)>,
304    {
305        let mut primary = None;
306        let set = iter
307            .into_iter()
308            .map(|(a, p)| {
309                if p {
310                    primary = Some(a.clone());
311                }
312                a
313            })
314            .collect();
315
316        if let Some(primary) = primary {
317            Some(Box::new(ValueSetEmailAddress { primary, set }))
318        } else {
319            set.iter()
320                .next()
321                .cloned()
322                .map(|primary| Box::new(ValueSetEmailAddress { primary, set }))
323        }
324    }
325}
326
327impl ValueSetScimPut for ValueSetEmailAddress {
328    fn from_scim_json_put(value: JsonValue) -> Result<ValueSetResolveStatus, OperationError> {
329        let scim_mails: Vec<ScimMail> = serde_json::from_value(value).map_err(|err| {
330            error!(?err, "SCIM Mail Attribute Syntax Invalid");
331            OperationError::SC0003MailSyntaxInvalid
332        })?;
333
334        let mut primary = None;
335        let set: BTreeSet<_> = scim_mails
336            .into_iter()
337            .map(
338                |ScimMail {
339                     value,
340                     primary: is_primary,
341                 }| {
342                    if is_primary {
343                        primary = Some(value.clone());
344                    }
345                    value
346                },
347            )
348            .collect();
349
350        let primary = primary
351            .or_else(|| set.iter().next().cloned())
352            .ok_or_else(|| {
353                error!(
354                    "Mail attribute has no values that can be used as the primary mail address."
355                );
356                OperationError::SC0003MailSyntaxInvalid
357            })?;
358
359        Ok(ValueSetResolveStatus::Resolved(Box::new(
360            ValueSetEmailAddress { primary, set },
361        )))
362    }
363}
364
365impl ValueSetT for ValueSetEmailAddress {
366    fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
367        match value {
368            Value::EmailAddress(a, p) => {
369                // if the set was empty, we need to force update primary.
370                if p || self.set.is_empty() {
371                    self.primary.clone_from(&a);
372                }
373                Ok(self.set.insert(a))
374            }
375            _ => {
376                debug_assert!(false);
377                Err(OperationError::InvalidValueState)
378            }
379        }
380    }
381
382    fn clear(&mut self) {
383        self.set.clear();
384    }
385
386    fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
387        match pv {
388            PartialValue::EmailAddress(a) => {
389                let r = self.set.remove(a);
390                if &self.primary == a {
391                    // if we can, inject another former address into primary.
392                    if let Some(n) = self.set.iter().take(1).next().cloned() {
393                        self.primary = n
394                    }
395                }
396                r
397            }
398            _ => false,
399        }
400    }
401
402    fn contains(&self, pv: &PartialValue) -> bool {
403        match pv {
404            PartialValue::EmailAddress(a) => self.set.contains(a),
405            _ => false,
406        }
407    }
408
409    fn substring(&self, pv: &PartialValue) -> bool {
410        match pv {
411            PartialValue::EmailAddress(s2) => {
412                // We lowercase as LDAP and similar expect case insensitive searches here.
413                let s2_lower = s2.to_lowercase();
414                self.set
415                    .iter()
416                    .any(|s1| s1.to_lowercase().contains(&s2_lower))
417            }
418            _ => {
419                debug_assert!(false);
420                false
421            }
422        }
423    }
424
425    fn startswith(&self, pv: &PartialValue) -> bool {
426        match pv {
427            PartialValue::EmailAddress(s2) => {
428                // We lowercase as LDAP and similar expect case insensitive searches here.
429                let s2_lower = s2.to_lowercase();
430                self.set
431                    .iter()
432                    .any(|s1| s1.to_lowercase().starts_with(&s2_lower))
433            }
434            _ => {
435                debug_assert!(false);
436                false
437            }
438        }
439    }
440
441    fn endswith(&self, pv: &PartialValue) -> bool {
442        match pv {
443            PartialValue::EmailAddress(s2) => {
444                // We lowercase as LDAP and similar expect case insensitive searches here.
445                let s2_lower = s2.to_lowercase();
446                self.set
447                    .iter()
448                    .any(|s1| s1.to_lowercase().ends_with(&s2_lower))
449            }
450            _ => {
451                debug_assert!(false);
452                false
453            }
454        }
455    }
456
457    fn lessthan(&self, _pv: &PartialValue) -> bool {
458        false
459    }
460
461    fn len(&self) -> usize {
462        self.set.len()
463    }
464
465    fn generate_idx_eq_keys(&self) -> Vec<String> {
466        self.set.iter().cloned().collect()
467    }
468
469    fn generate_idx_sub_keys(&self) -> Vec<String> {
470        let lower: Vec<_> = self.set.iter().map(|s| s.to_lowercase()).collect();
471        let mut trigraphs: Vec<_> = lower.iter().flat_map(|v| trigraph_iter(v)).collect();
472
473        trigraphs.sort_unstable();
474        trigraphs.dedup();
475
476        trigraphs.into_iter().map(String::from).collect()
477    }
478
479    fn syntax(&self) -> SyntaxType {
480        SyntaxType::EmailAddress
481    }
482
483    fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
484        self.set.contains(&self.primary)
485            && self
486                .set
487                .iter()
488                .all(|mail| VALIDATE_EMAIL_RE.is_match(mail.as_str()))
489    }
490
491    fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
492        if self.primary.is_empty() {
493            Box::new(self.set.iter().cloned())
494        } else {
495            Box::new(
496                std::iter::once(self.primary.clone()).chain(
497                    self.set
498                        .iter()
499                        .filter(|mail| **mail != self.primary)
500                        .cloned(),
501                ),
502            )
503        }
504    }
505
506    fn to_scim_value(&self) -> Option<ScimResolveStatus> {
507        Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
508            self.set
509                .iter()
510                .map(|mail| {
511                    let primary = **mail == self.primary;
512                    ScimMail {
513                        primary,
514                        value: mail.clone(),
515                    }
516                })
517                .collect::<Vec<_>>(),
518        )))
519    }
520
521    fn to_db_valueset_v2(&self) -> DbValueSetV2 {
522        DbValueSetV2::EmailAddress(self.primary.clone(), self.set.iter().cloned().collect())
523    }
524
525    fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
526        Box::new(self.set.iter().cloned().map(PartialValue::EmailAddress))
527    }
528
529    fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
530        Box::new(self.set.iter().cloned().map(|a| {
531            let p = a == self.primary;
532            Value::EmailAddress(a, p)
533        }))
534    }
535
536    fn equal(&self, other: &ValueSet) -> bool {
537        if let Some((p_b, set_b)) = other.as_emailaddress_set() {
538            &self.set == set_b && &self.primary == p_b
539        } else {
540            debug_assert!(false);
541            false
542        }
543    }
544
545    fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
546        if let Some((_p, set_b)) = other.as_emailaddress_set() {
547            mergesets!(self.set, set_b)
548        } else {
549            debug_assert!(false);
550            Err(OperationError::InvalidValueState)
551        }
552    }
553
554    fn as_emailaddress_set(&self) -> Option<(&String, &BTreeSet<String>)> {
555        if self.set.is_empty() {
556            None
557        } else {
558            Some((&self.primary, &self.set))
559        }
560    }
561
562    fn to_email_address_primary_str(&self) -> Option<&str> {
563        if self.set.is_empty() {
564            None
565        } else {
566            Some(self.primary.as_str())
567        }
568    }
569
570    fn as_email_str_iter(&self) -> Option<Box<dyn Iterator<Item = &str> + '_>> {
571        Some(Box::new(self.set.iter().map(|s| s.as_str())))
572    }
573}
574
575/*
576#[derive(Debug, Clone)]
577pub struct ValueSetPhoneNumber {
578    primary: String,
579    set: BTreeSet<String>,
580}
581*/
582
583#[cfg(test)]
584mod tests {
585    use super::{ValueSetAddress, ValueSetEmailAddress};
586    use crate::repl::cid::Cid;
587    use crate::value::{Address, PartialValue, Value};
588    use crate::valueset::{self, ValueSet};
589
590    #[test]
591    fn test_valueset_emailaddress() {
592        // Can be created
593        //
594        let mut vs: ValueSet = ValueSetEmailAddress::new("claire@example.com".to_string());
595
596        assert_eq!(vs.len(), 1);
597        assert_eq!(
598            vs.to_email_address_primary_str(),
599            Some("claire@example.com")
600        );
601
602        // Add another, still not primary.
603        assert!(
604            vs.insert_checked(
605                Value::new_email_address_s("alice@example.com").expect("Invalid Email")
606            ) == Ok(true)
607        );
608
609        assert_eq!(vs.len(), 2);
610        assert_eq!(
611            vs.to_email_address_primary_str(),
612            Some("claire@example.com")
613        );
614
615        // Update primary
616        assert!(
617            vs.insert_checked(
618                Value::new_email_address_primary_s("primary@example.com").expect("Invalid Email")
619            ) == Ok(true)
620        );
621        assert_eq!(
622            vs.to_email_address_primary_str(),
623            Some("primary@example.com")
624        );
625
626        // Restore from dbv1, ensure correct primary
627        let vs2 = valueset::from_db_valueset_v2(vs.to_db_valueset_v2())
628            .expect("Failed to construct vs2 from dbvalue");
629
630        assert_eq!(&vs, &vs2);
631        assert_eq!(
632            vs.to_email_address_primary_str(),
633            vs2.to_email_address_primary_str()
634        );
635
636        // Remove primary, assert it's gone and that the "first" address is assigned.
637        assert!(vs.remove(
638            &PartialValue::new_email_address_s("primary@example.com"),
639            &Cid::new_zero()
640        ));
641        assert_eq!(vs.len(), 2);
642        assert_eq!(vs.to_email_address_primary_str(), Some("alice@example.com"));
643
644        // Restore from dbv1, alice persisted.
645        let vs3 = valueset::from_db_valueset_v2(vs.to_db_valueset_v2())
646            .expect("Failed to construct vs2 from dbvalue");
647        assert_eq!(&vs, &vs3);
648        assert_eq!(vs3.len(), 2);
649        assert!(vs3
650            .as_emailaddress_set()
651            .map(|(_p, s)| s)
652            .unwrap()
653            .contains("alice@example.com"));
654        assert!(vs3
655            .as_emailaddress_set()
656            .map(|(_p, s)| s)
657            .unwrap()
658            .contains("claire@example.com"));
659
660        // If we clear, no primary.
661        vs.clear();
662        assert_eq!(vs.len(), 0);
663        assert!(vs.to_email_address_primary_str().is_none());
664    }
665
666    #[test]
667    fn test_scim_emailaddress() {
668        let mut vs: ValueSet = ValueSetEmailAddress::new("claire@example.com".to_string());
669        // Add another, still not primary.
670        assert!(
671            vs.insert_checked(
672                Value::new_email_address_s("alice@example.com").expect("Invalid Email")
673            ) == Ok(true)
674        );
675
676        let data = r#"[
677          {
678            "primary": false,
679            "value": "alice@example.com"
680          },
681          {
682            "primary": true,
683            "value": "claire@example.com"
684          }
685        ]"#;
686        crate::valueset::scim_json_reflexive(&vs, data);
687
688        // Test that we can parse json values into a valueset.
689        crate::valueset::scim_json_put_reflexive::<ValueSetEmailAddress>(&vs, &[])
690    }
691
692    #[test]
693    fn test_scim_address() {
694        let vs: ValueSet = ValueSetAddress::new(Address {
695            formatted: "1 No Where Lane, Doesn't Exist, Brisbane, 0420, Australia".to_string(),
696            street_address: "1 No Where Lane".to_string(),
697            locality: "Doesn't Exist".to_string(),
698            region: "Brisbane".to_string(),
699            postal_code: "0420".to_string(),
700            country: "Australia".to_string(),
701        });
702
703        let data = r#"[
704          {
705            "country": "Australia",
706            "formatted": "1 No Where Lane, Doesn't Exist, Brisbane, 0420, Australia",
707            "locality": "Doesn't Exist",
708            "postalCode": "0420",
709            "region": "Brisbane",
710            "streetAddress": "1 No Where Lane"
711          }
712        ]"#;
713
714        crate::valueset::scim_json_reflexive(&vs, data);
715
716        // Test that we can parse json values into a valueset.
717        crate::valueset::scim_json_put_reflexive::<ValueSetAddress>(&vs, &[])
718    }
719}