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 }
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 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 #[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 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 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 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 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 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#[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 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 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 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 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 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 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 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 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 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 crate::valueset::scim_json_put_reflexive::<ValueSetAddress>(&vs, &[])
718 }
719}