1use std::fmt;
2use uuid::Uuid;
3
4#[derive(Clone, Debug, PartialEq, Eq, Hash)]
10pub enum ScopeValue {
11 Uuid(Uuid),
13 String(String),
15 Int(i64),
17 Bool(bool),
19}
20
21impl ScopeValue {
22 #[must_use]
27 pub fn as_uuid(&self) -> Option<Uuid> {
28 match self {
29 Self::Uuid(u) => Some(*u),
30 Self::String(s) => Uuid::parse_str(s).ok(),
31 Self::Int(_) | Self::Bool(_) => None,
32 }
33 }
34}
35
36impl fmt::Display for ScopeValue {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 match self {
39 Self::Uuid(u) => write!(f, "{u}"),
40 Self::String(s) => write!(f, "{s}"),
41 Self::Int(n) => write!(f, "{n}"),
42 Self::Bool(b) => write!(f, "{b}"),
43 }
44 }
45}
46
47impl From<Uuid> for ScopeValue {
48 #[inline]
49 fn from(u: Uuid) -> Self {
50 Self::Uuid(u)
51 }
52}
53
54impl From<&Uuid> for ScopeValue {
55 #[inline]
56 fn from(u: &Uuid) -> Self {
57 Self::Uuid(*u)
58 }
59}
60
61impl From<String> for ScopeValue {
62 #[inline]
63 fn from(s: String) -> Self {
64 Self::String(s)
65 }
66}
67
68impl From<&str> for ScopeValue {
69 #[inline]
70 fn from(s: &str) -> Self {
71 Self::String(s.to_owned())
72 }
73}
74
75impl From<i64> for ScopeValue {
76 #[inline]
77 fn from(n: i64) -> Self {
78 Self::Int(n)
79 }
80}
81
82impl From<bool> for ScopeValue {
83 #[inline]
84 fn from(b: bool) -> Self {
85 Self::Bool(b)
86 }
87}
88
89pub mod pep_properties {
95 pub const OWNER_TENANT_ID: &str = "owner_tenant_id";
97
98 pub const RESOURCE_ID: &str = "id";
100
101 pub const OWNER_ID: &str = "owner_id";
103}
104
105#[derive(Clone, Debug, PartialEq, Eq)]
120pub enum ScopeFilter {
121 Eq(EqScopeFilter),
123 In(InScopeFilter),
125}
126
127#[derive(Clone, Debug, PartialEq, Eq, Hash)]
129pub struct EqScopeFilter {
130 property: String,
132 value: ScopeValue,
134}
135
136#[derive(Clone, Debug, PartialEq, Eq)]
138pub struct InScopeFilter {
139 property: String,
141 values: Vec<ScopeValue>,
143}
144
145impl EqScopeFilter {
146 #[must_use]
148 pub fn new(property: impl Into<String>, value: impl Into<ScopeValue>) -> Self {
149 Self {
150 property: property.into(),
151 value: value.into(),
152 }
153 }
154
155 #[inline]
157 #[must_use]
158 pub fn property(&self) -> &str {
159 &self.property
160 }
161
162 #[inline]
164 #[must_use]
165 pub fn value(&self) -> &ScopeValue {
166 &self.value
167 }
168}
169
170impl InScopeFilter {
171 #[must_use]
173 pub fn new(property: impl Into<String>, values: Vec<ScopeValue>) -> Self {
174 Self {
175 property: property.into(),
176 values,
177 }
178 }
179
180 #[must_use]
182 pub fn from_values<V: Into<ScopeValue>>(
183 property: impl Into<String>,
184 values: impl IntoIterator<Item = V>,
185 ) -> Self {
186 Self {
187 property: property.into(),
188 values: values.into_iter().map(Into::into).collect(),
189 }
190 }
191
192 #[inline]
194 #[must_use]
195 pub fn property(&self) -> &str {
196 &self.property
197 }
198
199 #[inline]
201 #[must_use]
202 pub fn values(&self) -> &[ScopeValue] {
203 &self.values
204 }
205}
206
207impl ScopeFilter {
208 #[must_use]
210 pub fn eq(property: impl Into<String>, value: impl Into<ScopeValue>) -> Self {
211 Self::Eq(EqScopeFilter::new(property, value))
212 }
213
214 #[must_use]
216 pub fn r#in(property: impl Into<String>, values: Vec<ScopeValue>) -> Self {
217 Self::In(InScopeFilter::new(property, values))
218 }
219
220 #[must_use]
222 pub fn in_uuids(property: impl Into<String>, uuids: Vec<Uuid>) -> Self {
223 Self::In(InScopeFilter::new(
224 property,
225 uuids.into_iter().map(ScopeValue::Uuid).collect(),
226 ))
227 }
228
229 #[must_use]
231 pub fn property(&self) -> &str {
232 match self {
233 Self::Eq(f) => f.property(),
234 Self::In(f) => f.property(),
235 }
236 }
237
238 #[must_use]
242 pub fn values(&self) -> ScopeFilterValues<'_> {
243 match self {
244 Self::Eq(f) => ScopeFilterValues::Single(&f.value),
245 Self::In(f) => ScopeFilterValues::Multiple(&f.values),
246 }
247 }
248
249 #[must_use]
254 pub fn uuid_values(&self) -> Vec<Uuid> {
255 self.values()
256 .iter()
257 .filter_map(ScopeValue::as_uuid)
258 .collect()
259 }
260}
261
262#[derive(Clone, Debug)]
267pub enum ScopeFilterValues<'a> {
268 Single(&'a ScopeValue),
270 Multiple(&'a [ScopeValue]),
272}
273
274impl<'a> ScopeFilterValues<'a> {
275 #[must_use]
277 pub fn iter(&self) -> ScopeFilterValuesIter<'a> {
278 match self {
279 Self::Single(v) => ScopeFilterValuesIter::Single(Some(v)),
280 Self::Multiple(vs) => ScopeFilterValuesIter::Multiple(vs.iter()),
281 }
282 }
283
284 #[must_use]
286 pub fn contains(&self, value: &ScopeValue) -> bool {
287 self.iter().any(|v| v == value)
288 }
289}
290
291impl<'a> IntoIterator for ScopeFilterValues<'a> {
292 type Item = &'a ScopeValue;
293 type IntoIter = ScopeFilterValuesIter<'a>;
294
295 fn into_iter(self) -> Self::IntoIter {
296 self.iter()
297 }
298}
299
300impl<'a> IntoIterator for &ScopeFilterValues<'a> {
301 type Item = &'a ScopeValue;
302 type IntoIter = ScopeFilterValuesIter<'a>;
303
304 fn into_iter(self) -> Self::IntoIter {
305 self.iter()
306 }
307}
308
309pub enum ScopeFilterValuesIter<'a> {
311 Single(Option<&'a ScopeValue>),
313 Multiple(std::slice::Iter<'a, ScopeValue>),
315}
316
317impl<'a> Iterator for ScopeFilterValuesIter<'a> {
318 type Item = &'a ScopeValue;
319
320 fn next(&mut self) -> Option<Self::Item> {
321 match self {
322 Self::Single(v) => v.take(),
323 Self::Multiple(iter) => iter.next(),
324 }
325 }
326}
327
328#[derive(Clone, Debug, PartialEq)]
333pub struct ScopeConstraint {
334 filters: Vec<ScopeFilter>,
335}
336
337impl ScopeConstraint {
338 #[must_use]
340 pub fn new(filters: Vec<ScopeFilter>) -> Self {
341 Self { filters }
342 }
343
344 #[inline]
346 #[must_use]
347 pub fn filters(&self) -> &[ScopeFilter] {
348 &self.filters
349 }
350
351 #[inline]
353 #[must_use]
354 pub fn is_empty(&self) -> bool {
355 self.filters.is_empty()
356 }
357}
358
359#[derive(Clone, Debug, PartialEq)]
381pub struct AccessScope {
382 constraints: Vec<ScopeConstraint>,
383 unconstrained: bool,
384}
385
386impl Default for AccessScope {
387 fn default() -> Self {
389 Self::deny_all()
390 }
391}
392
393impl AccessScope {
394 #[must_use]
398 pub fn from_constraints(constraints: Vec<ScopeConstraint>) -> Self {
399 Self {
400 constraints,
401 unconstrained: false,
402 }
403 }
404
405 #[must_use]
407 pub fn single(constraint: ScopeConstraint) -> Self {
408 Self::from_constraints(vec![constraint])
409 }
410
411 #[must_use]
416 pub fn allow_all() -> Self {
417 Self {
418 constraints: Vec::new(),
419 unconstrained: true,
420 }
421 }
422
423 #[must_use]
425 pub fn deny_all() -> Self {
426 Self {
427 constraints: Vec::new(),
428 unconstrained: false,
429 }
430 }
431
432 #[must_use]
436 pub fn for_tenants(ids: Vec<Uuid>) -> Self {
437 Self::single(ScopeConstraint::new(vec![ScopeFilter::in_uuids(
438 pep_properties::OWNER_TENANT_ID,
439 ids,
440 )]))
441 }
442
443 #[must_use]
445 pub fn for_tenant(id: Uuid) -> Self {
446 Self::for_tenants(vec![id])
447 }
448
449 #[must_use]
451 pub fn for_resources(ids: Vec<Uuid>) -> Self {
452 Self::single(ScopeConstraint::new(vec![ScopeFilter::in_uuids(
453 pep_properties::RESOURCE_ID,
454 ids,
455 )]))
456 }
457
458 #[must_use]
460 pub fn for_resource(id: Uuid) -> Self {
461 Self::for_resources(vec![id])
462 }
463
464 #[inline]
468 #[must_use]
469 pub fn constraints(&self) -> &[ScopeConstraint] {
470 &self.constraints
471 }
472
473 #[inline]
475 #[must_use]
476 pub fn is_unconstrained(&self) -> bool {
477 self.unconstrained
478 }
479
480 #[must_use]
484 pub fn is_deny_all(&self) -> bool {
485 !self.unconstrained && self.constraints.is_empty()
486 }
487
488 #[must_use]
490 pub fn all_values_for(&self, property: &str) -> Vec<&ScopeValue> {
491 let mut result = Vec::new();
492 for constraint in &self.constraints {
493 for filter in constraint.filters() {
494 if filter.property() == property {
495 result.extend(filter.values());
496 }
497 }
498 }
499 result
500 }
501
502 #[must_use]
506 pub fn all_uuid_values_for(&self, property: &str) -> Vec<Uuid> {
507 let mut result = Vec::new();
508 for constraint in &self.constraints {
509 for filter in constraint.filters() {
510 if filter.property() == property {
511 result.extend(filter.uuid_values());
512 }
513 }
514 }
515 result
516 }
517
518 #[must_use]
520 pub fn contains_value(&self, property: &str, value: &ScopeValue) -> bool {
521 self.constraints.iter().any(|c| {
522 c.filters()
523 .iter()
524 .any(|f| f.property() == property && f.values().contains(value))
525 })
526 }
527
528 #[must_use]
535 pub fn contains_uuid(&self, property: &str, id: Uuid) -> bool {
536 self.constraints.iter().any(|c| {
537 c.filters().iter().any(|f| {
538 f.property() == property && f.values().iter().any(|v| v.as_uuid() == Some(id))
539 })
540 })
541 }
542
543 #[must_use]
545 pub fn has_property(&self, property: &str) -> bool {
546 self.constraints
547 .iter()
548 .any(|c| c.filters().iter().any(|f| f.property() == property))
549 }
550
551 #[must_use]
561 pub fn tenant_only(&self) -> Self {
562 self.retain_properties(&[pep_properties::OWNER_TENANT_ID])
563 }
564
565 #[must_use]
574 pub fn tenant_and_owner(&self) -> Self {
575 self.retain_properties(&[pep_properties::OWNER_TENANT_ID, pep_properties::OWNER_ID])
576 }
577
578 #[must_use]
598 pub fn ensure_owner(&self, owner_id: Uuid) -> Self {
599 if self.is_deny_all() {
600 return Self::deny_all();
601 }
602
603 let owner_filter = ScopeFilter::eq(pep_properties::OWNER_ID, owner_id);
604
605 if self.unconstrained {
606 return Self::single(ScopeConstraint::new(vec![owner_filter]));
607 }
608
609 let constraints = self
610 .constraints
611 .iter()
612 .filter_map(|c| {
613 let owner_filters: Vec<&ScopeFilter> = c
614 .filters()
615 .iter()
616 .filter(|f| f.property() == pep_properties::OWNER_ID)
617 .collect();
618
619 if owner_filters.is_empty() {
620 let mut filters = c.filters().to_vec();
621 filters.push(owner_filter.clone());
622 return Some(ScopeConstraint::new(filters));
623 }
624
625 let all_match = owner_filters
628 .iter()
629 .all(|f| f.values().iter().any(|v| v.as_uuid() == Some(owner_id)));
630 if !all_match {
631 return None;
632 }
633
634 if owner_filters.len() == 1 && matches!(owner_filters[0], ScopeFilter::Eq(_)) {
636 return Some(c.clone());
637 }
638
639 let mut filters: Vec<ScopeFilter> = c
641 .filters()
642 .iter()
643 .filter(|f| f.property() != pep_properties::OWNER_ID)
644 .cloned()
645 .collect();
646 filters.push(owner_filter.clone());
647 Some(ScopeConstraint::new(filters))
648 })
649 .collect();
650
651 Self::from_constraints(constraints)
652 }
653
654 fn retain_properties(&self, properties: &[&str]) -> Self {
657 if self.unconstrained {
658 return Self::deny_all();
659 }
660
661 let constraints = self
662 .constraints
663 .iter()
664 .filter_map(|c| {
665 let kept: Vec<ScopeFilter> = c
666 .filters()
667 .iter()
668 .filter(|f| properties.contains(&f.property()))
669 .cloned()
670 .collect();
671
672 if kept.is_empty() {
673 None
674 } else {
675 Some(ScopeConstraint::new(kept))
676 }
677 })
678 .collect();
679
680 Self::from_constraints(constraints)
681 }
682}
683
684#[cfg(test)]
685#[cfg_attr(coverage_nightly, coverage(off))]
686mod tests {
687 use super::*;
688 use uuid::Uuid;
689
690 const T1: &str = "11111111-1111-1111-1111-111111111111";
691 const T2: &str = "22222222-2222-2222-2222-222222222222";
692
693 fn uid(s: &str) -> Uuid {
694 Uuid::parse_str(s).unwrap()
695 }
696
697 #[test]
700 fn scope_filter_eq_constructor() {
701 let f = ScopeFilter::eq(pep_properties::OWNER_TENANT_ID, uid(T1));
702 assert_eq!(f.property(), pep_properties::OWNER_TENANT_ID);
703 assert!(matches!(f, ScopeFilter::Eq(_)));
704 assert!(f.values().contains(&ScopeValue::Uuid(uid(T1))));
705 }
706
707 #[test]
708 fn all_values_for_works_with_eq() {
709 let scope = AccessScope::single(ScopeConstraint::new(vec![ScopeFilter::eq(
710 pep_properties::OWNER_TENANT_ID,
711 uid(T1),
712 )]));
713 assert_eq!(
714 scope.all_uuid_values_for(pep_properties::OWNER_TENANT_ID),
715 &[uid(T1)]
716 );
717 }
718
719 #[test]
720 fn all_values_for_works_with_mixed_eq_and_in() {
721 let scope = AccessScope::from_constraints(vec![
722 ScopeConstraint::new(vec![ScopeFilter::eq(
723 pep_properties::OWNER_TENANT_ID,
724 uid(T1),
725 )]),
726 ScopeConstraint::new(vec![ScopeFilter::in_uuids(
727 pep_properties::OWNER_TENANT_ID,
728 vec![uid(T2)],
729 )]),
730 ]);
731 let values = scope.all_uuid_values_for(pep_properties::OWNER_TENANT_ID);
732 assert_eq!(values, &[uid(T1), uid(T2)]);
733 }
734
735 #[test]
736 fn contains_value_works_with_eq() {
737 let scope = AccessScope::single(ScopeConstraint::new(vec![ScopeFilter::eq(
738 pep_properties::OWNER_TENANT_ID,
739 uid(T1),
740 )]));
741 assert!(scope.contains_uuid(pep_properties::OWNER_TENANT_ID, uid(T1)));
742 assert!(!scope.contains_uuid(pep_properties::OWNER_TENANT_ID, uid(T2)));
743 }
744
745 #[test]
748 fn tenant_only_strips_owner_id() {
749 let scope = AccessScope::single(ScopeConstraint::new(vec![
750 ScopeFilter::eq(pep_properties::OWNER_TENANT_ID, uid(T1)),
751 ScopeFilter::eq(pep_properties::OWNER_ID, uid(T2)),
752 ]));
753
754 let tenant_scope = scope.tenant_only();
755 assert!(tenant_scope.contains_uuid(pep_properties::OWNER_TENANT_ID, uid(T1)));
756 assert!(!tenant_scope.has_property(pep_properties::OWNER_ID));
757 }
758
759 #[test]
760 fn tenant_only_unconstrained_becomes_deny_all() {
761 let scope = AccessScope::allow_all();
762 let tenant_scope = scope.tenant_only();
763 assert!(tenant_scope.is_deny_all());
764 }
765
766 #[test]
767 fn tenant_only_deny_all_when_no_tenant_filters() {
768 let scope = AccessScope::single(ScopeConstraint::new(vec![ScopeFilter::eq(
769 pep_properties::OWNER_ID,
770 uid(T1),
771 )]));
772
773 let tenant_scope = scope.tenant_only();
774 assert!(tenant_scope.is_deny_all());
775 }
776
777 #[test]
778 fn tenant_only_on_deny_all_stays_deny_all() {
779 let scope = AccessScope::deny_all();
780 let tenant_scope = scope.tenant_only();
781 assert!(tenant_scope.is_deny_all());
782 }
783
784 #[test]
787 fn tenant_and_owner_keeps_both_properties() {
788 let scope = AccessScope::single(ScopeConstraint::new(vec![
789 ScopeFilter::eq(pep_properties::OWNER_TENANT_ID, uid(T1)),
790 ScopeFilter::eq(pep_properties::OWNER_ID, uid(T2)),
791 ScopeFilter::eq(pep_properties::RESOURCE_ID, uid(T1)),
792 ]));
793
794 let narrowed = scope.tenant_and_owner();
795 assert!(narrowed.contains_uuid(pep_properties::OWNER_TENANT_ID, uid(T1)));
796 assert!(narrowed.contains_uuid(pep_properties::OWNER_ID, uid(T2)));
797 assert!(!narrowed.has_property(pep_properties::RESOURCE_ID));
798 }
799
800 #[test]
801 fn tenant_and_owner_unconstrained_becomes_deny_all() {
802 let scope = AccessScope::allow_all();
803 assert!(scope.tenant_and_owner().is_deny_all());
804 }
805
806 #[test]
807 fn tenant_and_owner_deny_all_when_no_matching_filters() {
808 let scope = AccessScope::single(ScopeConstraint::new(vec![ScopeFilter::eq(
809 pep_properties::RESOURCE_ID,
810 uid(T1),
811 )]));
812 assert!(scope.tenant_and_owner().is_deny_all());
813 }
814
815 #[test]
818 fn ensure_owner_adds_owner_when_missing() {
819 let scope = AccessScope::for_tenant(uid(T1));
820 let owner_id = uid(T2);
821
822 let scoped = scope.ensure_owner(owner_id);
823 assert!(scoped.contains_uuid(pep_properties::OWNER_TENANT_ID, uid(T1)));
824 assert!(scoped.contains_uuid(pep_properties::OWNER_ID, owner_id));
825 }
826
827 #[test]
828 fn ensure_owner_keeps_existing_owner() {
829 let existing_owner = uid(T2);
830 let scope = AccessScope::single(ScopeConstraint::new(vec![
831 ScopeFilter::eq(pep_properties::OWNER_TENANT_ID, uid(T1)),
832 ScopeFilter::eq(pep_properties::OWNER_ID, existing_owner),
833 ]));
834
835 let scoped = scope.ensure_owner(existing_owner);
836 assert_eq!(
837 scoped.all_uuid_values_for(pep_properties::OWNER_ID),
838 &[existing_owner]
839 );
840 }
841
842 #[test]
843 fn ensure_owner_on_unconstrained_creates_owner_scope() {
844 let scope = AccessScope::allow_all();
845 let owner_id = uid(T1);
846
847 let scoped = scope.ensure_owner(owner_id);
848 assert!(!scoped.is_unconstrained());
849 assert!(scoped.contains_uuid(pep_properties::OWNER_ID, owner_id));
850 }
851
852 #[test]
853 fn ensure_owner_on_deny_all_stays_deny_all() {
854 let scope = AccessScope::deny_all();
855 let scoped = scope.ensure_owner(uid(T1));
856 assert!(scoped.is_deny_all());
857 }
858
859 #[test]
860 fn ensure_owner_narrows_existing_owner_to_subject() {
861 let user_a = uid(T1);
862 let user_b = uid(T2);
863 let scope = AccessScope::single(ScopeConstraint::new(vec![
864 ScopeFilter::eq(pep_properties::OWNER_TENANT_ID, uid(T1)),
865 ScopeFilter::in_uuids(pep_properties::OWNER_ID, vec![user_a, user_b]),
866 ]));
867
868 let scoped = scope.ensure_owner(user_a);
869 assert_eq!(
870 scoped.all_uuid_values_for(pep_properties::OWNER_ID),
871 &[user_a],
872 "Must narrow to exactly the subject's owner_id"
873 );
874 assert!(scoped.contains_uuid(pep_properties::OWNER_TENANT_ID, uid(T1)));
875 }
876
877 #[test]
878 fn ensure_owner_drops_constraint_when_subject_not_in_pdp() {
879 let user_x = uid(T1);
880 let user_y = uid(T2);
881 let scope = AccessScope::single(ScopeConstraint::new(vec![
882 ScopeFilter::eq(pep_properties::OWNER_TENANT_ID, uid(T1)),
883 ScopeFilter::eq(pep_properties::OWNER_ID, user_x),
884 ]));
885
886 let scoped = scope.ensure_owner(user_y);
887 assert!(
888 scoped.is_deny_all(),
889 "Must be deny-all when subject not in PDP's owner set"
890 );
891 }
892
893 #[test]
894 fn ensure_owner_checks_all_owner_filters_in_constraint() {
895 let alice = uid(T1);
896 let bob = uid(T2);
897 let scope = AccessScope::single(ScopeConstraint::new(vec![
900 ScopeFilter::in_uuids(pep_properties::OWNER_ID, vec![alice, bob]),
901 ScopeFilter::in_uuids(pep_properties::OWNER_ID, vec![bob]),
902 ]));
903
904 let scoped = scope.ensure_owner(alice);
905 assert!(
906 scoped.is_deny_all(),
907 "Must deny when subject is missing from any owner_id filter"
908 );
909
910 let scoped = scope.ensure_owner(bob);
912 assert!(!scoped.is_deny_all());
913 assert_eq!(
914 scoped.all_uuid_values_for(pep_properties::OWNER_ID),
915 &[bob],
916 "Must narrow to single Eq for the matching owner"
917 );
918 }
919
920 #[test]
921 fn ensure_owner_multi_constraint_keeps_only_matching() {
922 let alice = uid(T1);
923 let bob = uid(T2);
924 let tenant = uid(T1);
925
926 let c1 = ScopeConstraint::new(vec![
928 ScopeFilter::eq(pep_properties::OWNER_TENANT_ID, tenant),
929 ScopeFilter::eq(pep_properties::OWNER_ID, alice),
930 ]);
931 let c2 = ScopeConstraint::new(vec![
933 ScopeFilter::eq(pep_properties::OWNER_TENANT_ID, tenant),
934 ScopeFilter::eq(pep_properties::OWNER_ID, bob),
935 ]);
936
937 let scope = AccessScope::from_constraints(vec![c1, c2]);
938 let scoped = scope.ensure_owner(alice);
939
940 assert!(
941 !scoped.is_deny_all(),
942 "Must not be deny-all - one constraint matches"
943 );
944 assert_eq!(
945 scoped.all_uuid_values_for(pep_properties::OWNER_ID),
946 &[alice],
947 "Must keep only the constraint matching alice"
948 );
949 assert!(
950 scoped.contains_uuid(pep_properties::OWNER_TENANT_ID, tenant),
951 "Tenant filter must be preserved"
952 );
953 }
954
955 #[test]
956 fn contains_uuid_matches_string_variant() {
957 let scope = AccessScope::single(ScopeConstraint::new(vec![ScopeFilter::eq(
958 pep_properties::OWNER_TENANT_ID,
959 ScopeValue::String(T1.to_owned()),
960 )]));
961 assert!(scope.contains_uuid(pep_properties::OWNER_TENANT_ID, uid(T1)));
962 assert!(!scope.contains_uuid(pep_properties::OWNER_TENANT_ID, uid(T2)));
963 }
964
965 #[test]
966 fn contains_uuid_does_not_match_invalid_string() {
967 let scope = AccessScope::single(ScopeConstraint::new(vec![ScopeFilter::eq(
968 pep_properties::OWNER_TENANT_ID,
969 ScopeValue::String("not-a-uuid".to_owned()),
970 )]));
971 assert!(!scope.contains_uuid(pep_properties::OWNER_TENANT_ID, uid(T1)));
972 }
973}