1use crate::valueset::ScimResolveStatus;
2use std::collections::btree_map::Entry as BTreeEntry;
3use std::collections::{BTreeMap, BTreeSet};
4
5use crate::be::dbvalue::{DbValueOauthClaimMap, DbValueOauthScopeMapV1};
6use crate::prelude::*;
7use crate::schema::SchemaAttribute;
8use crate::value::{OauthClaimMapJoin, OAUTHSCOPE_RE};
9use crate::valueset::{
10 uuid_to_proto_string, DbValueSetV2, ResolvedValueSetOauth2ClaimMap,
11 ResolvedValueSetOauth2ScopeMap, ScimValueIntermediate, UnresolvedScimValueOauth2ClaimMap,
12 UnresolvedScimValueOauth2ScopeMap, UnresolvedValueSetOauth2ClaimMap,
13 UnresolvedValueSetOauth2ScopeMap, ValueSet, ValueSetIntermediate, ValueSetResolveStatus,
14 ValueSetScimPut,
15};
16use kanidm_proto::scim_v1::client::ScimOAuth2ClaimMap as ClientScimOAuth2ClaimMap;
17use kanidm_proto::scim_v1::client::ScimOAuth2ScopeMap as ClientScimOAuth2ScopeMap;
18use kanidm_proto::scim_v1::JsonValue;
19
20#[derive(Debug, Clone)]
21pub struct ValueSetOauthScope {
22 set: BTreeSet<String>,
23}
24
25impl ValueSetOauthScope {
26 pub fn new(s: String) -> Box<Self> {
27 let mut set = BTreeSet::new();
28 set.insert(s);
29 Box::new(ValueSetOauthScope { set })
30 }
31
32 pub fn push(&mut self, s: String) -> bool {
33 self.set.insert(s)
34 }
35
36 pub fn from_dbvs2(data: Vec<String>) -> Result<ValueSet, OperationError> {
37 let set = data.into_iter().collect();
38 Ok(Box::new(ValueSetOauthScope { set }))
39 }
40
41 #[allow(clippy::should_implement_trait)]
44 pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
45 where
46 T: IntoIterator<Item = String>,
47 {
48 let set = iter.into_iter().collect();
49 Some(Box::new(ValueSetOauthScope { set }))
50 }
51}
52
53impl ValueSetScimPut for ValueSetOauthScope {
54 fn from_scim_json_put(value: JsonValue) -> Result<ValueSetResolveStatus, OperationError> {
55 let set: BTreeSet<String> = serde_json::from_value(value).map_err(|err| {
56 error!(?err, "SCIM Oauth2Scope syntax invalid");
57 OperationError::SC0019Oauth2ScopeSyntaxInvalid
58 })?;
59
60 Ok(ValueSetResolveStatus::Resolved(Box::new(
61 ValueSetOauthScope { set },
62 )))
63 }
64}
65
66impl ValueSetT for ValueSetOauthScope {
67 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
68 match value {
69 Value::OauthScope(s) => Ok(self.set.insert(s)),
70 _ => {
71 debug_assert!(false);
72 Err(OperationError::InvalidValueState)
73 }
74 }
75 }
76
77 fn clear(&mut self) {
78 self.set.clear();
79 }
80
81 fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
82 match pv {
83 PartialValue::OauthScope(s) => self.set.remove(s.as_str()),
84 _ => {
85 debug_assert!(false);
86 true
87 }
88 }
89 }
90
91 fn contains(&self, pv: &PartialValue) -> bool {
92 match pv {
93 PartialValue::OauthScope(s) => self.set.contains(s.as_str()),
94 _ => false,
95 }
96 }
97
98 fn substring(&self, _pv: &PartialValue) -> bool {
99 false
100 }
101
102 fn startswith(&self, _pv: &PartialValue) -> bool {
103 false
104 }
105
106 fn endswith(&self, _pv: &PartialValue) -> bool {
107 false
108 }
109
110 fn lessthan(&self, _pv: &PartialValue) -> bool {
111 false
112 }
113
114 fn len(&self) -> usize {
115 self.set.len()
116 }
117
118 fn generate_idx_eq_keys(&self) -> Vec<String> {
119 self.set.iter().cloned().collect()
120 }
121
122 fn syntax(&self) -> SyntaxType {
123 SyntaxType::OauthScope
124 }
125
126 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
127 self.set.iter().all(|s| OAUTHSCOPE_RE.is_match(s))
128 }
129
130 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
131 Box::new(self.set.iter().cloned())
132 }
133
134 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
135 Some(ScimResolveStatus::Resolved(ScimValueKanidm::ArrayString(
136 self.set.iter().cloned().collect(),
137 )))
138 }
139
140 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
141 DbValueSetV2::OauthScope(self.set.iter().cloned().collect())
142 }
143
144 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
145 Box::new(self.set.iter().cloned().map(PartialValue::OauthScope))
146 }
147
148 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
149 Box::new(self.set.iter().cloned().map(Value::OauthScope))
150 }
151
152 fn equal(&self, other: &ValueSet) -> bool {
153 if let Some(other) = other.as_oauthscope_set() {
154 &self.set == other
155 } else {
156 debug_assert!(false);
157 false
158 }
159 }
160
161 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
162 if let Some(b) = other.as_oauthscope_set() {
163 mergesets!(self.set, b)
164 } else {
165 debug_assert!(false);
166 Err(OperationError::InvalidValueState)
167 }
168 }
169
170 fn as_oauthscope_set(&self) -> Option<&BTreeSet<String>> {
181 Some(&self.set)
182 }
183
184 fn as_oauthscope_iter(&self) -> Option<Box<dyn Iterator<Item = &str> + '_>> {
185 Some(Box::new(self.set.iter().map(|s| s.as_str())))
186 }
187}
188
189#[derive(Debug, Clone)]
190pub struct ValueSetOauthScopeMap {
191 map: BTreeMap<Uuid, BTreeSet<String>>,
192}
193
194impl ValueSetOauthScopeMap {
195 pub fn new(u: Uuid, m: BTreeSet<String>) -> Box<Self> {
196 let mut map = BTreeMap::new();
197 map.insert(u, m);
198 Box::new(ValueSetOauthScopeMap { map })
199 }
200
201 pub fn push(&mut self, u: Uuid, m: BTreeSet<String>) -> bool {
202 self.map.insert(u, m).is_none()
203 }
204
205 pub fn from_dbvs2(data: Vec<DbValueOauthScopeMapV1>) -> Result<ValueSet, OperationError> {
206 let map = data
207 .into_iter()
208 .map(|DbValueOauthScopeMapV1 { refer, data }| (refer, data.into_iter().collect()))
209 .collect();
210 Ok(Box::new(ValueSetOauthScopeMap { map }))
211 }
212
213 #[allow(clippy::should_implement_trait)]
216 pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
217 where
218 T: IntoIterator<Item = (Uuid, BTreeSet<String>)>,
219 {
220 let map = iter.into_iter().collect();
221 Some(Box::new(ValueSetOauthScopeMap { map }))
222 }
223
224 pub(crate) fn from_set(resolved: Vec<ResolvedValueSetOauth2ScopeMap>) -> ValueSet {
225 let map = resolved
226 .into_iter()
227 .map(|ResolvedValueSetOauth2ScopeMap { group_uuid, scopes }| (group_uuid, scopes))
228 .collect();
229
230 Box::new(ValueSetOauthScopeMap { map })
231 }
232}
233
234impl ValueSetScimPut for ValueSetOauthScopeMap {
235 fn from_scim_json_put(value: JsonValue) -> Result<ValueSetResolveStatus, OperationError> {
236 let scope_maps: Vec<ClientScimOAuth2ScopeMap> =
237 serde_json::from_value(value).map_err(|err| {
238 error!(?err, "SCIM Oauth2ScopeMap syntax invalid");
239 OperationError::SC0020Oauth2ScopeMapSyntaxInvalid
240 })?;
241
242 let mut resolved = Vec::with_capacity(scope_maps.len());
246 let mut unresolved = Vec::with_capacity(scope_maps.len());
247
248 for ClientScimOAuth2ScopeMap {
249 group,
250 group_uuid,
251 scopes,
252 } in scope_maps.into_iter()
253 {
254 match (group_uuid, group) {
255 (None, None) => {
256 error!("SCIM Oauth2ScopeMap a group name or uuid must be present");
257 return Err(OperationError::SC0021Oauth2ScopeMapMissingGroupIdentifier);
258 }
259 (Some(group_uuid), _) => {
260 resolved.push(ResolvedValueSetOauth2ScopeMap { group_uuid, scopes })
261 }
262 (None, Some(group_name)) => {
263 unresolved.push(UnresolvedValueSetOauth2ScopeMap { group_name, scopes })
264 }
265 }
266 }
267
268 Ok(ValueSetResolveStatus::NeedsResolution(
269 ValueSetIntermediate::Oauth2ScopeMap {
270 resolved,
271 unresolved,
272 },
273 ))
274 }
275}
276
277impl ValueSetT for ValueSetOauthScopeMap {
278 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
279 match value {
280 Value::OauthScopeMap(u, m) => {
281 match self.map.entry(u) {
282 BTreeEntry::Vacant(e) => {
284 e.insert(m);
285 Ok(true)
286 }
287 BTreeEntry::Occupied(mut e) => {
293 if m.is_empty() {
294 e.remove();
295 } else {
296 e.insert(m);
297 }
298
299 Ok(true)
300 }
301 }
302 }
303 _ => Err(OperationError::InvalidValueState),
304 }
305 }
306
307 fn clear(&mut self) {
308 self.map.clear();
309 }
310
311 fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
312 match pv {
313 PartialValue::Refer(u) => self.map.remove(u).is_some(),
314 _ => false,
315 }
316 }
317
318 fn contains(&self, pv: &PartialValue) -> bool {
319 match pv {
320 PartialValue::Refer(u) => self.map.contains_key(u),
321 _ => false,
322 }
323 }
324
325 fn substring(&self, _pv: &PartialValue) -> bool {
326 false
327 }
328
329 fn startswith(&self, _pv: &PartialValue) -> bool {
330 false
331 }
332
333 fn endswith(&self, _pv: &PartialValue) -> bool {
334 false
335 }
336
337 fn lessthan(&self, _pv: &PartialValue) -> bool {
338 false
339 }
340
341 fn len(&self) -> usize {
342 self.map.len()
343 }
344
345 fn generate_idx_eq_keys(&self) -> Vec<String> {
346 self.map
347 .keys()
348 .map(|u| u.as_hyphenated().to_string())
349 .collect()
350 }
351
352 fn syntax(&self) -> SyntaxType {
353 SyntaxType::OauthScopeMap
354 }
355
356 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
357 self.map
358 .values()
359 .flat_map(|set| set.iter())
360 .all(|s| OAUTHSCOPE_RE.is_match(s))
361 }
362
363 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
364 Box::new(
365 self.map
366 .iter()
367 .map(|(u, m)| format!("{}: {:?}", uuid_to_proto_string(*u), m)),
368 )
369 }
370
371 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
372 let unresolved_maps = self
373 .map
374 .iter()
375 .map(|(group_uuid, scopes)| UnresolvedScimValueOauth2ScopeMap {
376 group_uuid: *group_uuid,
377 scopes: scopes.clone(),
378 })
379 .collect::<Vec<_>>();
380
381 Some(ScimResolveStatus::NeedsResolution(
382 ScimValueIntermediate::Oauth2ScopeMap(unresolved_maps),
383 ))
384 }
385
386 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
387 DbValueSetV2::OauthScopeMap(
388 self.map
389 .iter()
390 .map(|(u, m)| DbValueOauthScopeMapV1 {
391 refer: *u,
392 data: m.iter().cloned().collect(),
393 })
394 .collect(),
395 )
396 }
397
398 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
399 Box::new(self.map.keys().cloned().map(PartialValue::Refer))
400 }
401
402 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
403 Box::new(
404 self.map
405 .iter()
406 .map(|(u, m)| Value::OauthScopeMap(*u, m.clone())),
407 )
408 }
409
410 fn equal(&self, other: &ValueSet) -> bool {
411 if let Some(other) = other.as_oauthscopemap() {
412 &self.map == other
413 } else {
414 debug_assert!(false);
415 false
416 }
417 }
418
419 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
420 if let Some(b) = other.as_oauthscopemap() {
421 mergemaps!(self.map, b)
422 } else {
423 debug_assert!(false);
424 Err(OperationError::InvalidValueState)
425 }
426 }
427
428 fn as_oauthscopemap(&self) -> Option<&BTreeMap<Uuid, BTreeSet<String>>> {
429 Some(&self.map)
430 }
431
432 fn as_ref_uuid_iter(&self) -> Option<Box<dyn Iterator<Item = Uuid> + '_>> {
433 Some(Box::new(self.map.keys().copied()))
435 }
436}
437
438#[derive(Debug, Clone, PartialEq)]
439pub struct OauthClaimMapping {
440 join: OauthClaimMapJoin,
441 values: BTreeMap<Uuid, BTreeSet<String>>,
442}
443
444impl OauthClaimMapping {
445 pub(crate) fn join(&self) -> OauthClaimMapJoin {
446 self.join
447 }
448
449 pub(crate) fn values(&self) -> &BTreeMap<Uuid, BTreeSet<String>> {
450 &self.values
451 }
452}
453
454#[derive(Debug, Clone)]
455pub struct ValueSetOauthClaimMap {
456 map: BTreeMap<String, OauthClaimMapping>,
458}
459
460impl ValueSetOauthClaimMap {
461 pub(crate) fn new(claim: String, join: OauthClaimMapJoin) -> Box<Self> {
462 let mapping = OauthClaimMapping {
463 join,
464 values: BTreeMap::default(),
465 };
466 let mut map = BTreeMap::new();
467 map.insert(claim, mapping);
468 Box::new(ValueSetOauthClaimMap { map })
469 }
470
471 pub(crate) fn new_value(claim: String, group: Uuid, claims: BTreeSet<String>) -> Box<Self> {
472 let mut values = BTreeMap::default();
473 values.insert(group, claims);
474
475 let mapping = OauthClaimMapping {
476 join: OauthClaimMapJoin::default(),
477 values,
478 };
479
480 let mut map = BTreeMap::new();
481 map.insert(claim, mapping);
482 Box::new(ValueSetOauthClaimMap { map })
483 }
484
485 pub(crate) fn from_dbvs2(data: Vec<DbValueOauthClaimMap>) -> Result<ValueSet, OperationError> {
486 let map = data
487 .into_iter()
488 .map(|db_claim_map| match db_claim_map {
489 DbValueOauthClaimMap::V1 { name, join, values } => (
490 name.clone(),
491 OauthClaimMapping {
492 join: join.into(),
493 values: values.clone(),
494 },
495 ),
496 })
497 .collect();
498 Ok(Box::new(ValueSetOauthClaimMap { map }))
499 }
500
501 pub(crate) fn from_set(resolved: Vec<ResolvedValueSetOauth2ClaimMap>) -> ValueSet {
502 let mut map = BTreeMap::new();
503
504 for ResolvedValueSetOauth2ClaimMap {
505 group_uuid,
506 claim,
507 join_char,
508 claim_values,
509 } in resolved.into_iter()
510 {
511 match map.entry(claim) {
512 BTreeEntry::Vacant(e) => {
513 let mut values = BTreeMap::default();
514 values.insert(group_uuid, claim_values);
515
516 let claim_map = OauthClaimMapping {
517 join: join_char,
518 values,
519 };
520 e.insert(claim_map);
521 }
522 BTreeEntry::Occupied(mut e) => {
523 let mapping_mut = e.get_mut();
525 match mapping_mut.values.entry(group_uuid) {
526 BTreeEntry::Vacant(e) => {
527 e.insert(claim_values);
528 }
529 BTreeEntry::Occupied(mut e) => {
530 e.insert(claim_values);
531 }
532 }
533 }
534 }
535 }
536
537 Box::new(ValueSetOauthClaimMap { map })
538 }
539
540 fn trim(&mut self) {
541 self.map
542 .values_mut()
543 .for_each(|mapping_mut| mapping_mut.values.retain(|_k, v| !v.is_empty()));
544
545 self.map.retain(|_k, v| !v.values.is_empty());
546 }
547}
548
549impl ValueSetScimPut for ValueSetOauthClaimMap {
550 fn from_scim_json_put(value: JsonValue) -> Result<ValueSetResolveStatus, OperationError> {
551 let claim_maps: Vec<ClientScimOAuth2ClaimMap> =
552 serde_json::from_value(value).map_err(|err| {
553 error!(?err, "SCIM Oauth2ClaimMap syntax invalid");
554 OperationError::SC0022Oauth2ClaimMapSyntaxInvalid
555 })?;
556
557 let mut resolved = Vec::with_capacity(claim_maps.len());
561 let mut unresolved = Vec::with_capacity(claim_maps.len());
562
563 for ClientScimOAuth2ClaimMap {
564 group,
565 group_uuid,
566 claim,
567 join_char,
568 values: claim_values,
569 } in claim_maps.into_iter()
570 {
571 let join_char = OauthClaimMapJoin::from(join_char);
572
573 match (group_uuid, group) {
574 (None, None) => {
575 error!("SCIM Oauth2ClaimMap a group name or uuid must be present");
576 return Err(OperationError::SC0023Oauth2ClaimMapMissingGroupIdentifier);
577 }
578 (Some(group_uuid), _) => resolved.push(ResolvedValueSetOauth2ClaimMap {
579 group_uuid,
580 claim,
581 join_char,
582 claim_values,
583 }),
584 (None, Some(group_name)) => unresolved.push(UnresolvedValueSetOauth2ClaimMap {
585 group_name,
586 claim,
587 join_char,
588 claim_values,
589 }),
590 }
591 }
592
593 Ok(ValueSetResolveStatus::NeedsResolution(
594 ValueSetIntermediate::Oauth2ClaimMap {
595 resolved,
596 unresolved,
597 },
598 ))
599 }
600}
601
602impl ValueSetT for ValueSetOauthClaimMap {
603 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
604 match value {
605 Value::OauthClaimValue(name, uuid, claims) => {
606 match self.map.entry(name) {
608 BTreeEntry::Vacant(e) => {
609 let mut values = BTreeMap::default();
611 values.insert(uuid, claims);
612
613 let claim_map = OauthClaimMapping {
614 join: OauthClaimMapJoin::default(),
615 values,
616 };
617 e.insert(claim_map);
618 Ok(true)
619 }
620 BTreeEntry::Occupied(mut e) => {
621 let mapping_mut = e.get_mut();
623 match mapping_mut.values.entry(uuid) {
624 BTreeEntry::Vacant(e) => {
625 e.insert(claims);
626 Ok(true)
627 }
628 BTreeEntry::Occupied(mut e) => {
629 e.insert(claims);
630 Ok(true)
631 }
632 }
633 }
634 }
635 }
636 Value::OauthClaimMap(name, join) => {
637 match self.map.entry(name) {
638 BTreeEntry::Vacant(e) => {
639 let claim_map = OauthClaimMapping {
641 join,
642 values: BTreeMap::default(),
643 };
644 e.insert(claim_map);
645 Ok(true)
646 }
647 BTreeEntry::Occupied(mut e) => {
648 e.get_mut().join = join;
650 Ok(true)
651 }
652 }
653 }
654 _ => Err(OperationError::InvalidValueState),
655 }
656 }
657
658 fn clear(&mut self) {
659 self.map.clear();
660 }
661
662 fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
663 let res = match pv {
664 PartialValue::Iutf8(s) => self.map.remove(s).is_some(),
666 PartialValue::Refer(u) => {
668 let mut contained = false;
669 for mapping_mut in self.map.values_mut() {
670 contained |= mapping_mut.values.remove(u).is_some();
671 }
672 contained
673 }
674 PartialValue::OauthClaim(s, u) => {
675 if let Some(mapping_mut) = self.map.get_mut(s) {
677 mapping_mut.values.remove(u).is_some()
678 } else {
679 false
680 }
681 }
682 PartialValue::OauthClaimValue(s, u, v) => {
683 if let Some(mapping_mut) = self.map.get_mut(s) {
685 if let Some(claim_mut) = mapping_mut.values.get_mut(u) {
686 claim_mut.remove(v)
687 } else {
688 false
689 }
690 } else {
691 false
692 }
693 }
694 _ => false,
695 };
696
697 self.trim();
699
700 res
701 }
702
703 fn contains(&self, pv: &PartialValue) -> bool {
704 match pv {
705 PartialValue::Iutf8(s) => self.map.contains_key(s),
706 PartialValue::Refer(u) => {
707 let mut contained = false;
708 for mapping in self.map.values() {
709 contained |= mapping.values.contains_key(u);
710 }
711 contained
712 }
713 _ => false,
714 }
715 }
716
717 fn substring(&self, _pv: &PartialValue) -> bool {
718 false
719 }
720
721 fn startswith(&self, _pv: &PartialValue) -> bool {
722 false
723 }
724
725 fn endswith(&self, _pv: &PartialValue) -> bool {
726 false
727 }
728
729 fn lessthan(&self, _pv: &PartialValue) -> bool {
730 false
731 }
732
733 fn len(&self) -> usize {
734 self.map.len()
735 }
736
737 fn generate_idx_eq_keys(&self) -> Vec<String> {
738 self.map
739 .keys()
740 .cloned()
741 .chain(
742 self.map.values().flat_map(|mapping| {
743 mapping.values.keys().map(|u| u.as_hyphenated().to_string())
744 }),
745 )
746 .collect()
747 }
748
749 fn syntax(&self) -> SyntaxType {
750 SyntaxType::OauthClaimMap
751 }
752
753 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
754 self.map.keys().all(|s| OAUTHSCOPE_RE.is_match(s))
755 && self
756 .map
757 .values()
758 .flat_map(|mapping| {
759 mapping
760 .values
761 .values()
762 .map(|claim_values| claim_values.is_empty())
763 })
764 .all(|is_empty| !is_empty)
765 && self
766 .map
767 .values()
768 .flat_map(|mapping| {
769 mapping
770 .values
771 .values()
772 .flat_map(|claim_values| claim_values.iter())
773 })
774 .all(|s| OAUTHSCOPE_RE.is_match(s))
775 }
776
777 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
778 Box::new(self.map.iter().flat_map(|(name, mapping)| {
779 mapping.values.iter().map(move |(group, claims)| {
780 let join_str = mapping.join.to_str();
781
782 let joined = str_concat!(claims, join_str);
783
784 format!(
785 "{}: {} \"{:?}\"",
786 name,
787 uuid_to_proto_string(*group),
788 joined
789 )
790 })
791 }))
792 }
793
794 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
795 let unresolved_maps = self
796 .map
797 .iter()
798 .flat_map(|(claim_name, mappings)| {
799 mappings.values.iter().map(|(group_uuid, claim_values)| {
800 UnresolvedScimValueOauth2ClaimMap {
801 group_uuid: *group_uuid,
802 claim: claim_name.to_string(),
803 join_char: mappings.join.into(),
804 values: claim_values.clone(),
805 }
806 })
807 })
808 .collect::<Vec<_>>();
809
810 Some(ScimResolveStatus::NeedsResolution(
811 ScimValueIntermediate::Oauth2ClaimMap(unresolved_maps),
812 ))
813 }
814
815 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
816 DbValueSetV2::OauthClaimMap(
817 self.map
818 .iter()
819 .map(|(name, mapping)| DbValueOauthClaimMap::V1 {
820 name: name.clone(),
821 join: mapping.join.into(),
822 values: mapping.values.clone(),
823 })
824 .collect(),
825 )
826 }
827
828 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
829 Box::new(self.map.keys().cloned().map(PartialValue::Iutf8))
830 }
831
832 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
833 debug_assert!(false);
834 Box::new(
835 std::iter::empty(), )
841 }
842
843 fn equal(&self, other: &ValueSet) -> bool {
844 if let Some(other) = other.as_oauthclaim_map() {
845 &self.map == other
846 } else {
847 debug_assert!(false);
848 false
849 }
850 }
851
852 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
853 if let Some(b) = other.as_oauthclaim_map() {
854 mergemaps!(self.map, b)
855 } else {
856 debug_assert!(false);
857 Err(OperationError::InvalidValueState)
858 }
859 }
860
861 fn as_oauthclaim_map(&self) -> Option<&BTreeMap<String, OauthClaimMapping>> {
862 Some(&self.map)
863 }
864
865 fn as_ref_uuid_iter(&self) -> Option<Box<dyn Iterator<Item = Uuid> + '_>> {
866 Some(Box::new(
868 self.map
869 .values()
870 .flat_map(|mapping| mapping.values.keys())
871 .copied(),
872 ))
873 }
874}
875
876#[cfg(test)]
877mod tests {
878 use super::{ValueSetOauthClaimMap, ValueSetOauthScope, ValueSetOauthScopeMap};
879 use crate::prelude::*;
880 use std::collections::BTreeSet;
881
882 #[test]
883 fn test_oauth_claim_invalid_str_concat_when_empty() {
884 let group_uuid = uuid::uuid!("5a6b8783-3f67-4ebb-b6aa-77fd6e66589f");
885 let vs =
886 ValueSetOauthClaimMap::new_value("claim".to_string(), group_uuid, BTreeSet::default());
887
888 let proto_value = vs.to_proto_string_clone_iter().next().unwrap();
890
891 assert_eq!(
892 &proto_value,
893 "claim: 5a6b8783-3f67-4ebb-b6aa-77fd6e66589f \"\"\"\""
894 );
895 }
896
897 #[test]
898 fn test_scim_oauth2_scope() {
899 let vs: ValueSet = ValueSetOauthScope::new("fully_sick_scope_m8".to_string());
900 let data = r#"["fully_sick_scope_m8"]"#;
901 crate::valueset::scim_json_reflexive(&vs, data);
902
903 crate::valueset::scim_json_put_reflexive::<ValueSetOauthScope>(&vs, &[])
905 }
906
907 #[qs_test]
908 async fn test_scim_oauth2_scope_map(server: &QueryServer) {
909 let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
910
911 let g_uuid = uuid::uuid!("4d21d04a-dc0e-42eb-b850-34dd180b107f");
912 assert!(write_txn
913 .internal_create(vec![entry_init!(
914 (Attribute::Class, EntryClass::Object.to_value()),
915 (Attribute::Class, EntryClass::Group.to_value()),
916 (Attribute::Name, Value::new_iname("testgroup")),
917 (Attribute::Uuid, Value::Uuid(g_uuid))
918 ),])
919 .is_ok());
920
921 let set = ["read".to_string(), "write".to_string()].into();
922 let vs: ValueSet = ValueSetOauthScopeMap::new(g_uuid, set);
923
924 let data = r#"
925[
926 {
927 "scopes": ["read", "write"],
928 "group": "testgroup@example.com",
929 "groupUuid": "4d21d04a-dc0e-42eb-b850-34dd180b107f"
930 }
931]
932 "#;
933 crate::valueset::scim_json_reflexive_unresolved(&mut write_txn, &vs, data);
934
935 crate::valueset::scim_json_put_reflexive_unresolved::<ValueSetOauthScopeMap>(
937 &mut write_txn,
938 &vs,
939 &[],
940 );
941
942 assert!(write_txn.commit().is_ok());
943 }
944
945 #[qs_test]
946 async fn test_scim_oauth2_claim_map(server: &QueryServer) {
947 let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
948
949 let g_uuid = uuid::uuid!("4d21d04a-dc0e-42eb-b850-34dd180b107f");
950 assert!(write_txn
951 .internal_create(vec![entry_init!(
952 (Attribute::Class, EntryClass::Object.to_value()),
953 (Attribute::Class, EntryClass::Group.to_value()),
954 (Attribute::Name, Value::new_iname("testgroup")),
955 (Attribute::Uuid, Value::Uuid(g_uuid))
956 ),])
957 .is_ok());
958
959 let set = ["read".to_string(), "write".to_string()].into();
960 let vs: ValueSet = ValueSetOauthClaimMap::new_value("claim".to_string(), g_uuid, set);
961
962 let data = r#"
963[
964 {
965 "claim": "claim",
966 "group": "testgroup@example.com",
967 "groupUuid": "4d21d04a-dc0e-42eb-b850-34dd180b107f",
968 "joinChar": ";",
969 "values": ["read", "write"]
970 }
971]
972 "#;
973 crate::valueset::scim_json_reflexive_unresolved(&mut write_txn, &vs, data);
974
975 crate::valueset::scim_json_put_reflexive_unresolved::<ValueSetOauthClaimMap>(
977 &mut write_txn,
978 &vs,
979 &[],
980 );
981
982 assert!(write_txn.commit().is_ok());
983 }
984}