Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve libzauth ACL syntax #2714

Merged
merged 6 commits into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/5-internal/improve-acl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add regular expression support to libzauth ACL language
42 changes: 41 additions & 1 deletion libs/libzauth/libzauth-c/Cargo.lock

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

4 changes: 3 additions & 1 deletion libs/libzauth/libzauth/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "zauth"
version = "3.0.0"
version = "3.1.0"
authors = ["Wire Swiss GmbH <[email protected]>"]
license = "AGPL-3.0"

Expand All @@ -11,6 +11,8 @@ name = "zauth"
asexp = ">= 0.3"
rustc-serialize = ">= 0.3"
sodiumoxide = "^0.2.7"
regex = "1.6"
lazy_static = "1.4"

[dev-dependencies]
clap = ">= 2.0"
99 changes: 47 additions & 52 deletions libs/libzauth/libzauth/src/acl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,34 @@
// You should have received a copy of the GNU Affero General Public License along
// with this program. If not, see <https://www.gnu.org/licenses/>.

use std::collections::HashMap;
use asexp::Sexp;
use tree::Tree;
use matcher::{Item, Matcher};
use std::collections::HashMap;

#[derive(Debug, Clone)]
pub enum Error {
Parse(&'static str)
Parse(&'static str),
}

pub type AclResult<A> = Result<A, Error>;

#[derive(Debug, Clone)]
pub struct Acl {
acl: HashMap<String, List>
acl: HashMap<String, List>,
}

impl Acl {
pub fn new() -> Acl {
Acl { acl: HashMap::new() }
Acl {
acl: HashMap::new(),
}
}

pub fn from_str(s: &str) -> AclResult<Acl> {
match Sexp::parse_toplevel(s) {
Err(()) => Err(Error::Parse("invalid s-expressions")),
Ok(sexp) => Acl::from_sexp(&sexp)
let sexp = Sexp::parse_toplevel(s);
match sexp {
Err(()) => Err(Error::Parse("invalid s-expressions")),
Ok(sexp) => Acl::from_sexp(&sexp),
}
}

Expand All @@ -51,82 +54,69 @@ impl Acl {
if let Some(k) = key.get_str().map(String::from) {
acl.insert(k, List::from_sexp(&list)?);
} else {
return Err(Error::Parse("not a string"))
return Err(Error::Parse("not a string"));
}
}
Ok(Acl { acl })
}
_ => Err(Error::Parse("expected key and values"))
_ => Err(Error::Parse("expected key and values")),
}
}

pub fn allowed(&self, key: &str, path: &str) -> bool {
self.acl.get(key).map(|list| {
match *list {
List::Black(Some(ref t)) => !t.contains(path),
List::Black(None) => true,
List::White(Some(ref t)) => t.contains(path),
List::White(None) => false
}
}).unwrap_or(false)
self.acl
.get(key)
.map(|list| match *list {
List::Black(ref t) => !t.contains(path),
List::White(ref t) => t.contains(path),
})
.unwrap_or(false)
}
}

#[derive(Debug, Clone)]
enum List {
Black(Option<Tree>),
White(Option<Tree>)
Black(Matcher),
White(Matcher),
}

impl List {
fn from_sexp(s: &Sexp) -> AclResult<List> {
let items = match *s {
Sexp::Tuple(ref a) => a.as_slice(),
Sexp::Array(ref a) => a.as_slice(),
_ => return Err(Error::Parse("s-expr not a list"))
_ => return Err(Error::Parse("s-expr not a list")),
};

if items.is_empty() {
return Err(Error::Parse("list is empty"))
return Err(Error::Parse("list is empty"));
}

match items[0].get_str() {
Some("blacklist") => List::items(&items[1 ..]).map(List::Black),
Some("whitelist") => List::items(&items[1 ..]).map(List::White),
_ => Err(Error::Parse("'blacklist' or 'whitelist' expected"))
Some("blacklist") => List::items(&items[1..]).map(List::Black),
Some("whitelist") => List::items(&items[1..]).map(List::White),
_ => Err(Error::Parse("'blacklist' or 'whitelist' expected")),
}
}

fn items(xs: &[Sexp]) -> AclResult<Option<Tree>> {
match xs.len() {
0 => Ok(None),
1 if List::is_unit(&xs[0]) => Ok(None),
_ => {
let mut t = Tree::new();
for x in xs {
t.add(&List::read_path(x)?)
}
Ok(Some(t))
}
}
fn items(xs: &[Sexp]) -> AclResult<Matcher> {
let items: AclResult<Vec<_>> = xs.iter().map(List::read_path).collect();
let m = Matcher::new(&items?);
Ok(m)
}

fn is_unit(s: &Sexp) -> bool {
match *s {
Sexp::Tuple(ref a) if a.is_empty() => true,
_ => false
}
}

fn read_path(s: &Sexp) -> AclResult<String> {
fn read_path(s: &Sexp) -> AclResult<Item> {
match *s {
Sexp::Tuple(ref a) | Sexp::Array(ref a) if a.len() == 2 => {
match (a[0].get_str(), a[1].get_str()) {
(Some("path"), Some(x)) => Ok(String::from(x)),
_ => Err(Error::Parse("'path' not found"))
(Some("path"), Some(x)) => Ok(Item::Str(String::from(x))),
(Some("regex"), Some(x)) => {
Ok(Item::Regex(String::from(x)))
}
_ => Err(Error::Parse("'path' not found")),
}
}
_ => return Err(Error::Parse("s-expr not a list"))
_ => return Err(Error::Parse("s-expr not a list")),
}
}
}
Expand All @@ -144,14 +134,15 @@ mod tests {
(path "/a/**"))

b (whitelist (path "/conversation/message")
(path "/foo/bar/*"))
(path "/foo/bar/*")
(regex "(/v[0-9]+)?/foo/baz/[^/]+"))

# this is a comment that should not lead to a parse failure.
la (whitelist (path "/legalhold/**"))

x (blacklist ())
x (blacklist)

y (whitelist ())
y (whitelist)
"#;

#[test]
Expand All @@ -165,8 +156,12 @@ mod tests {
assert!(!acl.allowed("u", "/x/here/z"));
assert!(acl.allowed("u", "/x/here/z/x"));
assert!(acl.allowed("b", "/conversation/message"));
assert!(acl.allowed("b", "/foo/bar/baz"));
assert!(acl.allowed("b", "/foo/bar/quux"));
assert!(!acl.allowed("b", "/foo/bar/"));
assert!(acl.allowed("b", "/foo/baz/quux"));
assert!(!acl.allowed("b", "/foo/bar/"));
assert!(acl.allowed("b", "/v97/foo/baz/quux"));
assert!(!acl.allowed("b", "/voo/foo/baz/quux"));
assert!(!acl.allowed("b", "/anywhere/else/"));
assert!(acl.allowed("x", "/everywhere"));
assert!(acl.allowed("x", "/"));
Expand Down
4 changes: 3 additions & 1 deletion libs/libzauth/libzauth/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@
// with this program. If not, see <https://www.gnu.org/licenses/>.

extern crate asexp;
extern crate lazy_static;
extern crate regex;
extern crate rustc_serialize;
extern crate sodiumoxide;

pub mod acl;
pub mod error;
pub mod zauth;

mod tree;
mod matcher;

pub use acl::Acl;
pub use error::Error;
Expand Down
Loading