diff --git a/virtweb_backend/src/api_tokens.rs b/virtweb_backend/src/api_tokens.rs index de2591c..0e30eee 100644 --- a/virtweb_backend/src/api_tokens.rs +++ b/virtweb_backend/src/api_tokens.rs @@ -39,10 +39,33 @@ impl TokenRights { } pub fn contains(&self, verb: TokenVerb, path: &str) -> bool { - self.0.contains(&TokenRight { - verb, - path: path.to_string(), - }) + let path_split = path.split('/').collect::>(); + + 'root: for r in &self.0 { + if r.verb != verb { + continue 'root; + } + + let mut last_idx = 0; + for (idx, part) in r.path.split('/').enumerate() { + if idx >= path_split.len() { + continue 'root; + } + + if part != "*" && part != path_split[idx] { + continue 'root; + } + + last_idx = idx; + } + + // Check we visited the whole path + if last_idx + 1 == path_split.len() { + return true; + } + } + + false } } @@ -233,3 +256,39 @@ pub async fn delete(id: TokenID) -> anyhow::Result<()> { } Ok(()) } + +#[cfg(test)] +mod test { + use crate::api_tokens::{TokenRight, TokenRights, TokenVerb}; + + #[test] + fn test_rights_patch() { + let rights = TokenRights(vec![ + TokenRight { + path: "/api/vm/*".to_string(), + verb: TokenVerb::GET, + }, + TokenRight { + path: "/api/vm/a".to_string(), + verb: TokenVerb::PUT, + }, + TokenRight { + path: "/api/vm/a/other".to_string(), + verb: TokenVerb::DELETE, + }, + TokenRight { + path: "/api/net/create".to_string(), + verb: TokenVerb::POST, + }, + ]); + + assert!(rights.contains(TokenVerb::GET, "/api/vm/ab")); + assert!(!rights.contains(TokenVerb::GET, "/api/vm/ab/c")); + assert!(rights.contains(TokenVerb::PUT, "/api/vm/a")); + assert!(!rights.contains(TokenVerb::PUT, "/api/vm/other")); + assert!(rights.contains(TokenVerb::POST, "/api/net/create")); + assert!(!rights.contains(TokenVerb::GET, "/api/net/create")); + assert!(!rights.contains(TokenVerb::POST, "/api/net/b")); + assert!(!rights.contains(TokenVerb::POST, "/api/net/create/b")); + } +}