fix: auth precedence (#325)

pull/328/head
sigoden 2023-12-21 17:28:13 +08:00 committed by GitHub
parent a66f95b39f
commit 77f86a4c60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 31 deletions

View File

@ -47,10 +47,7 @@ impl AccessControl {
if raw_rules.is_empty() { if raw_rules.is_empty() {
return Ok(Default::default()); return Ok(Default::default());
} }
let new_raw_rules = compact_split_rules(raw_rules); let new_raw_rules = split_rules(raw_rules);
if new_raw_rules.len() != raw_rules.len() {
eprintln!("Warning: deprecate the use of `|` to separate auth rules.")
}
let mut use_hashed_password = false; let mut use_hashed_password = false;
let create_err = |v: &str| anyhow!("Invalid auth `{v}`"); let create_err = |v: &str| anyhow!("Invalid auth `{v}`");
let mut anony = None; let mut anony = None;
@ -194,7 +191,11 @@ impl AccessPaths {
} }
fn find_impl(&self, parts: &[&str], perm: AccessPerm) -> Option<AccessPaths> { fn find_impl(&self, parts: &[&str], perm: AccessPerm) -> Option<AccessPaths> {
let perm = self.perm.max(perm); let perm = if !self.perm.indexonly() {
self.perm
} else {
perm
};
if parts.is_empty() { if parts.is_empty() {
if perm.indexonly() { if perm.indexonly() {
return Some(self.clone()); return Some(self.clone());
@ -215,24 +216,24 @@ impl AccessPaths {
child.find_impl(&parts[1..], perm) child.find_impl(&parts[1..], perm)
} }
pub fn child_paths(&self) -> Vec<&String> { pub fn child_names(&self) -> Vec<&String> {
self.children.keys().collect() self.children.keys().collect()
} }
pub fn leaf_paths(&self, base: &Path) -> Vec<PathBuf> { pub fn child_paths(&self, base: &Path) -> Vec<PathBuf> {
if !self.perm().indexonly() { if !self.perm().indexonly() {
return vec![base.to_path_buf()]; return vec![base.to_path_buf()];
} }
let mut output = vec![]; let mut output = vec![];
self.leaf_paths_impl(&mut output, base); self.child_paths_impl(&mut output, base);
output output
} }
fn leaf_paths_impl(&self, output: &mut Vec<PathBuf>, base: &Path) { fn child_paths_impl(&self, output: &mut Vec<PathBuf>, base: &Path) {
for (name, child) in self.children.iter() { for (name, child) in self.children.iter() {
let base = base.join(name); let base = base.join(name);
if child.perm().indexonly() { if child.perm().indexonly() {
child.leaf_paths_impl(output, &base); child.child_paths_impl(output, &base);
} else { } else {
output.push(base) output.push(base)
} }
@ -489,8 +490,7 @@ fn split_account_paths(s: &str) -> Option<(&str, &str)> {
Some((&s[0..i], &s[i + 1..])) Some((&s[0..i], &s[i + 1..]))
} }
/// Compatible with deprecated usage of `|` for role separation fn split_rules(rules: &[&str]) -> Vec<String> {
fn compact_split_rules(rules: &[&str]) -> Vec<String> {
let mut output = vec![]; let mut output = vec![];
for rule in rules { for rule in rules {
let parts: Vec<&str> = rule.split('|').collect(); let parts: Vec<&str> = rule.split('|').collect();
@ -540,15 +540,15 @@ mod tests {
#[test] #[test]
fn test_compact_split_rules() { fn test_compact_split_rules() {
assert_eq!( assert_eq!(
compact_split_rules(&["user1:pass1@/:rw|user2:pass2@/:rw"]), split_rules(&["user1:pass1@/:rw|user2:pass2@/:rw"]),
["user1:pass1@/:rw", "user2:pass2@/:rw"] ["user1:pass1@/:rw", "user2:pass2@/:rw"]
); );
assert_eq!( assert_eq!(
compact_split_rules(&["user1:pa|ss1@/:rw|user2:pa|ss2@/:rw"]), split_rules(&["user1:pa|ss1@/:rw|user2:pa|ss2@/:rw"]),
["user1:pa|ss1@/:rw", "user2:pa|ss2@/:rw"] ["user1:pa|ss1@/:rw", "user2:pa|ss2@/:rw"]
); );
assert_eq!( assert_eq!(
compact_split_rules(&["user1:pa|ss1@/:rw|@/"]), split_rules(&["user1:pa|ss1@/:rw|@/"]),
["user1:pa|ss1@/:rw", "@/"] ["user1:pa|ss1@/:rw", "@/"]
); );
} }
@ -557,16 +557,18 @@ mod tests {
fn test_access_paths() { fn test_access_paths() {
let mut paths = AccessPaths::default(); let mut paths = AccessPaths::default();
paths.add("/dir1", AccessPerm::ReadWrite); paths.add("/dir1", AccessPerm::ReadWrite);
paths.add("/dir2/dir1", AccessPerm::ReadWrite); paths.add("/dir2/dir21", AccessPerm::ReadWrite);
paths.add("/dir2/dir2", AccessPerm::ReadOnly); paths.add("/dir2/dir21/dir211", AccessPerm::ReadOnly);
paths.add("/dir2/dir3/dir1", AccessPerm::ReadWrite); paths.add("/dir2/dir22", AccessPerm::ReadOnly);
paths.add("/dir2/dir22/dir221", AccessPerm::ReadWrite);
paths.add("/dir2/dir23/dir231", AccessPerm::ReadWrite);
assert_eq!( assert_eq!(
paths.leaf_paths(Path::new("/tmp")), paths.child_paths(Path::new("/tmp")),
[ [
"/tmp/dir1", "/tmp/dir1",
"/tmp/dir2/dir1", "/tmp/dir2/dir21",
"/tmp/dir2/dir2", "/tmp/dir2/dir22",
"/tmp/dir2/dir3/dir1" "/tmp/dir2/dir23/dir231",
] ]
.iter() .iter()
.map(PathBuf::from) .map(PathBuf::from)
@ -575,16 +577,32 @@ mod tests {
assert_eq!( assert_eq!(
paths paths
.find("dir2", false) .find("dir2", false)
.map(|v| v.leaf_paths(Path::new("/tmp/dir2"))), .map(|v| v.child_paths(Path::new("/tmp/dir2"))),
Some( Some(
["/tmp/dir2/dir1", "/tmp/dir2/dir2", "/tmp/dir2/dir3/dir1"] [
.iter() "/tmp/dir2/dir21",
.map(PathBuf::from) "/tmp/dir2/dir22",
.collect::<Vec<_>>() "/tmp/dir2/dir23/dir231"
]
.iter()
.map(PathBuf::from)
.collect::<Vec<_>>()
) )
); );
assert_eq!(paths.find("dir2", true), None); assert_eq!(paths.find("dir2", true), None);
assert!(paths.find("dir1/file", true).is_some()); assert_eq!(
paths.find("dir1/file", true),
Some(AccessPaths::new(AccessPerm::ReadWrite))
);
assert_eq!(
paths.find("dir2/dir21/file", true),
Some(AccessPaths::new(AccessPerm::ReadWrite))
);
assert_eq!(
paths.find("dir2/dir21/dir211/file", false),
Some(AccessPaths::new(AccessPerm::ReadOnly))
);
assert_eq!(paths.find("dir2/dir21/dir211/file", true), None);
} }
#[test] #[test]

View File

@ -514,7 +514,7 @@ impl Server {
let access_paths = access_paths.clone(); let access_paths = access_paths.clone();
let search_paths = tokio::task::spawn_blocking(move || { let search_paths = tokio::task::spawn_blocking(move || {
let mut paths: Vec<PathBuf> = vec![]; let mut paths: Vec<PathBuf> = vec![];
for dir in access_paths.leaf_paths(&path_buf) { for dir in access_paths.child_paths(&path_buf) {
let mut it = WalkDir::new(&dir).into_iter(); let mut it = WalkDir::new(&dir).into_iter();
it.next(); it.next();
while let Some(Ok(entry)) = it.next() { while let Some(Ok(entry)) = it.next() {
@ -1184,7 +1184,7 @@ impl Server {
) -> Result<Vec<PathItem>> { ) -> Result<Vec<PathItem>> {
let mut paths: Vec<PathItem> = vec![]; let mut paths: Vec<PathItem> = vec![];
if access_paths.perm().indexonly() { if access_paths.perm().indexonly() {
for name in access_paths.child_paths() { for name in access_paths.child_names() {
let entry_path = entry_path.join(name); let entry_path = entry_path.join(name);
self.add_pathitem(&mut paths, base_path, &entry_path).await; self.add_pathitem(&mut paths, base_path, &entry_path).await;
} }
@ -1465,7 +1465,7 @@ async fn zip_dir<W: AsyncWrite + Unpin>(
let dir_clone = dir.to_path_buf(); let dir_clone = dir.to_path_buf();
let zip_paths = tokio::task::spawn_blocking(move || { let zip_paths = tokio::task::spawn_blocking(move || {
let mut paths: Vec<PathBuf> = vec![]; let mut paths: Vec<PathBuf> = vec![];
for dir in access_paths.leaf_paths(&dir_clone) { for dir in access_paths.child_paths(&dir_clone) {
let mut it = WalkDir::new(&dir).into_iter(); let mut it = WalkDir::new(&dir).into_iter();
it.next(); it.next();
while let Some(Ok(entry)) = it.next() { while let Some(Ok(entry)) = it.next() {

View File

@ -282,3 +282,22 @@ fn auth_data(
assert_eq!(json["allow_upload"], serde_json::Value::Bool(true)); assert_eq!(json["allow_upload"], serde_json::Value::Bool(true));
Ok(()) Ok(())
} }
#[rstest]
fn auth_precedence(
#[with(&["--auth", "user:pass@/dir1:rw,/dir1/test.txt", "-A"])] server: TestServer,
) -> Result<(), Error> {
let url = format!("{}dir1/test.txt", server.url());
let resp = fetch!(b"PUT", &url)
.body(b"abc".to_vec())
.send_with_digest_auth("user", "pass")?;
assert_eq!(resp.status(), 403);
let url = format!("{}dir1/file1", server.url());
let resp = fetch!(b"PUT", &url)
.body(b"abc".to_vec())
.send_with_digest_auth("user", "pass")?;
assert_eq!(resp.status(), 201);
Ok(())
}