diff options
Diffstat (limited to 'src/access')
| -rw-r--r-- | src/access/mod.rs | 71 |
1 files changed, 71 insertions, 0 deletions
diff --git a/src/access/mod.rs b/src/access/mod.rs index e223756..e04be02 100644 --- a/src/access/mod.rs +++ b/src/access/mod.rs @@ -97,6 +97,41 @@ impl Capability { && (meta::is_protocol(&self.path) || !meta::is_protocol(path)) } + /// What both `self` and `other` give, as one capability, if anything: at the path + /// both cover (the longer, a `*` of one taking the other's key there), with the + /// rights both give. Nothing where the paths part, no right is shared, or the path + /// is protocol data that one of them doesn't name (`covers`'s carve-out). + pub(crate) fn meet(&self, other: &Capability) -> Option<Capability> { + if !agree(&self.path, &other.path) { + return None; + } + let (longer, shorter) = if self.path.len() >= other.path.len() { + (&self.path, &other.path) + } else { + (&other.path, &self.path) + }; + let path: Vec<String> = longer + .iter() + .enumerate() + .map(|(at, segment)| match shorter.get(at) { + Some(theirs) if segment == "*" => theirs.clone(), + _ => segment.clone(), + }) + .collect(); + if meta::is_protocol(&path) + && !(meta::is_protocol(&self.path) && meta::is_protocol(&other.path)) + { + return None; + } + let rights: BTreeSet<String> = self.rights.intersection(&other.rights).cloned().collect(); + (!rights.is_empty()).then(|| Capability { + path, + rights, + by: self.by.clone(), + since: self.since, + }) + } + /// Whether `inner` gives nothing it doesn't: at or below its path (a `*` of `inner` /// only under one of its own), with some of its rights. Of limits narrowing again. pub(crate) fn holds(&self, inner: &Capability) -> bool { @@ -261,6 +296,42 @@ mod tests { assert!(!members.reaches(Right::Read, &path("_continuum/users"))); } + /// What two capabilities both give: the deeper path, shared rights, protocol data + /// only where both name it. + #[test] + fn what_both_give() { + let both = cap("data/*/x", &["read", "write"]) + .meet(&cap("data/eu", &["read"])) + .unwrap(); + assert_eq!( + (both.path, both.rights), + (path("data/eu/x"), ["read".to_owned()].into()) + ); + assert!( + cap("data/us", &["read"]) + .meet(&cap("data/eu", &["read"])) + .is_none() + ); + assert!( + cap("data", &["write"]) + .meet(&cap("data/eu", &["read"])) + .is_none() + ); + // `[]` never covers protocol data; `_continuum` meets `_continuum/members`. + assert!( + cap("", &["read"]) + .meet(&cap("_continuum/members", &["read"])) + .is_none() + ); + assert_eq!( + cap("_continuum", &["read"]) + .meet(&cap("_continuum/members", &["read"])) + .unwrap() + .path, + path("_continuum/members") + ); + } + /// `*` stands for any one key: in covering, in listings, and in narrowing. #[test] fn a_star_is_any_key() { |
