Skip to content

Commit

Permalink
subscription, model: implement wildcard PrincsFilter support
Browse files Browse the repository at this point in the history
  • Loading branch information
MrAnno committed Nov 1, 2024
1 parent 59630e8 commit b9a7817
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 24 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions cli/src/skell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,12 @@ fn get_filter() -> String {
# - "Except": everyone but the listed principals will be able to read the subscription
#
# By default, everyone can read the subscription.
# Wildcard (*, ?) patterns are allowed.
#
# Example to only authorize "courgette@REALM" and "radis@REALM" to read the subscription.
# Example to only authorize "courgette@REALM" and "radis*@REALM" to read the subscription.
# [filter]
# operation = "Only"
# princs = ["courgette@REALM", "radis@REALM"]
# princs = ["courgette@REALM", "radis*@REALM"]
"#
.to_string()
Expand Down
1 change: 1 addition & 0 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ deadpool-sqlite = "0.5.0"
openssl = "0.10.66"
postgres-openssl = "0.5.0"
strum = { version = "0.26.1", features = ["derive"] }
glob = "0.3.1"

[dev-dependencies]
tempfile = "3.13.0"
Expand Down
9 changes: 7 additions & 2 deletions common/src/models/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ mod v1 {

impl From<PrincsFilter> for crate::subscription::PrincsFilter {
fn from(value: PrincsFilter) -> Self {
crate::subscription::PrincsFilter::new(value.operation.map(|x| x.into()), value.princs)
crate::subscription::PrincsFilter::new(value.operation.map(|x| x.into()), value.princs, vec![])
}
}

Expand Down Expand Up @@ -283,6 +283,7 @@ pub mod v2 {
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use uuid::Uuid;
use glob::Pattern;

#[derive(Debug, Clone, Deserialize, Eq, PartialEq, Serialize)]
pub(super) struct KafkaConfiguration {
Expand Down Expand Up @@ -537,11 +538,13 @@ pub mod v2 {
pub(super) struct PrincsFilter {
pub operation: Option<PrincsFilterOperation>,
pub princs: HashSet<String>,
pub princ_globs: Vec<String>
}

impl From<PrincsFilter> for crate::subscription::PrincsFilter {
fn from(value: PrincsFilter) -> Self {
crate::subscription::PrincsFilter::new(value.operation.map(|x| x.into()), value.princs)
let princ_globs = value.princ_globs.iter().map(|p| Pattern::new(p).unwrap()).collect();
crate::subscription::PrincsFilter::new(value.operation.map(|x| x.into()), value.princs, princ_globs)
}
}

Expand All @@ -550,6 +553,7 @@ pub mod v2 {
Self {
operation: value.operation().map(|x| x.clone().into()),
princs: value.princ_literals().clone(),
princ_globs: value.princ_globs().iter().map(|p| p.as_str().to_owned()).collect()
}
}
}
Expand Down Expand Up @@ -707,6 +711,7 @@ mod tests {
.set_princs_filter(crate::subscription::PrincsFilter::new(
Some(crate::subscription::PrincsFilterOperation::Except),
princs,
vec![],
))
.set_outputs(vec![crate::subscription::SubscriptionOutput::new(
crate::subscription::SubscriptionOutputFormat::Json,
Expand Down
95 changes: 77 additions & 18 deletions common/src/subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use log::{info, warn};
use serde::{Deserialize, Serialize};
use strum::{AsRefStr, EnumString, VariantNames};
use uuid::Uuid;
use glob::Pattern;

use crate::utils::VersionHasher;

Expand Down Expand Up @@ -238,55 +239,91 @@ impl PrincsFilterOperation {
pub struct PrincsFilter {
operation: Option<PrincsFilterOperation>,
princs: HashSet<String>,
princ_globs: Vec<Pattern>
}

fn has_wildcard(s: &str) -> bool {
s.chars().any(|c| c == '*' || c == '?')
}

impl PrincsFilter {
pub fn empty() -> Self {
PrincsFilter {
operation: None,
princs: HashSet::new(),
princ_globs: Vec::new(),
}
}

pub fn new(operation: Option<PrincsFilterOperation>, princs: HashSet<String>) -> Self {
Self { operation, princs }
pub fn new(operation: Option<PrincsFilterOperation>, princs: HashSet<String>, princ_globs: Vec<Pattern>) -> Self {
Self { operation, princs, princ_globs }
}

pub fn from(operation: Option<String>, princs: Option<String>) -> Result<Self> {
pub fn from(operation: Option<String>, princ_patterns: Option<String>) -> Result<Self> {
let operation = match operation {
Some(op) => PrincsFilterOperation::opt_from_str(&op)?,
None => None,
};

let Some(princ_patterns) = princ_patterns else {
return Ok(PrincsFilter {
operation,
princs: HashSet::new(),
princ_globs: Vec::new(),
});
};

let (princ_globs, princs): (Vec<_>, Vec<_>) = princ_patterns.split(',').partition(|&p| has_wildcard(p));

let princs = princs.iter().map(|p| p.to_string());
let princ_globs = princ_globs.iter().map(|p| Pattern::new(p)).collect::<Result<Vec<Pattern>, _>>()?;

Ok(PrincsFilter {
operation: match operation {
Some(op) => PrincsFilterOperation::opt_from_str(&op)?,
None => None,
},
princs: match princs {
Some(p) => HashSet::from_iter(p.split(',').map(|s| s.to_string())),
None => HashSet::new(),
},
operation,
princs: HashSet::from_iter(princs),
princ_globs,
})
}

fn matches(&self, principal: &str) -> bool {
if self.princs.contains(principal) {
return true;
}

for p in &self.princ_globs {
if p.matches(principal) {
return true;
}
}

false
}

pub fn eval(&self, principal: &str) -> bool {
match self.operation {
None => true,
Some(PrincsFilterOperation::Only) => self.princs.contains(principal),
Some(PrincsFilterOperation::Except) => !self.princs.contains(principal),
Some(PrincsFilterOperation::Only) => self.matches(principal),
Some(PrincsFilterOperation::Except) => !self.matches(principal),
}
}

pub fn princ_literals(&self) -> &HashSet<String> {
&self.princs
}

pub fn princ_globs(&self) -> &Vec<Pattern> {
&self.princ_globs
}

pub fn princs_to_string(&self) -> String {
self.princs
.iter()
.cloned()
self.princs.iter().cloned()
.chain(self.princ_globs.iter().map(|p| p.as_str().to_owned()))
.collect::<Vec<String>>()
.join(",")
}

pub fn princs_to_opt_string(&self) -> Option<String> {
if self.princs.is_empty() {
if self.princs.is_empty() && self.princ_globs.is_empty() {
None
} else {
Some(self.princs_to_string())
Expand All @@ -297,6 +334,12 @@ impl PrincsFilter {
if self.operation.is_none() {
bail!("Could not add a principal to an unset filter")
}

if has_wildcard(princ) {
self.princ_globs.push(Pattern::new(princ)?);
return Ok(())
}

self.princs.insert(princ.to_owned());
Ok(())
}
Expand All @@ -305,17 +348,33 @@ impl PrincsFilter {
if self.operation.is_none() {
bail!("Could not delete a principal of an unset filter")
}

if has_wildcard(princ) {
let Some(i) = self.princ_globs.iter().position(|p| p.as_str() == princ) else {
warn!("{} was not present in the principals set", princ);
return Ok(())
};

self.princ_globs.remove(i);
return Ok(())
}

if !self.princs.remove(princ) {
warn!("{} was not present in the principals set", princ)
}
Ok(())
}

pub fn set_princs(&mut self, princs: HashSet<String>) -> Result<()> {
pub fn set_princs(&mut self, princ_patterns: HashSet<String>) -> Result<()> {
if self.operation.is_none() {
bail!("Could not set principals of an unset filter")
}

let (princ_globs, princs): (HashSet<_>, HashSet<_>) = princ_patterns.iter().cloned().partition(|p| has_wildcard(p));
let princ_globs = princ_globs.iter().map(|p| Pattern::new(p)).collect::<Result<Vec<Pattern>, _>>()?;

self.princs = princs;
self.princ_globs = princ_globs;
Ok(())
}

Expand Down
5 changes: 3 additions & 2 deletions subscription.sample.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@ query = """
# - "Except": everyone but the listed principals will be able to read the subscription
#
# By default, everyone can read the subscription.
# Wildcard (*, ?) patterns are allowed.
#
# Example to only authorize "courgette@REALM" and "radis@REALM" to read the subscription.
# Example to only authorize "courgette@REALM" and "radis*@REALM" to read the subscription.
# [filter]
# operation = "Only"
# princs = ["courgette@REALM", "radis@REALM"]
# princs = ["courgette@REALM", "radis*@REALM"]


#
Expand Down

0 comments on commit b9a7817

Please sign in to comment.