mirror of
https://github.com/sigoden/dufs.git
synced 2026-06-07 23:16:54 +03:00
fix: tweak auth logic (#689)
This commit is contained in:
+46
-22
@@ -85,8 +85,14 @@ impl AccessControl {
|
|||||||
access_paths
|
access_paths
|
||||||
.merge(paths)
|
.merge(paths)
|
||||||
.ok_or_else(|| anyhow!("Invalid auth value `{user}:{pass}@{paths}"))?;
|
.ok_or_else(|| anyhow!("Invalid auth value `{user}:{pass}@{paths}"))?;
|
||||||
if let Some(paths) = annoy_paths {
|
if let Some(anon_ap) = &anonymous {
|
||||||
access_paths.merge(paths);
|
let orig_user = access_paths.clone();
|
||||||
|
access_paths.absorb_anon(
|
||||||
|
anon_ap,
|
||||||
|
&orig_user,
|
||||||
|
AccessPerm::IndexOnly,
|
||||||
|
AccessPerm::IndexOnly,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if pass.starts_with("$6$") {
|
if pass.starts_with("$6$") {
|
||||||
use_hashed_password = true;
|
use_hashed_password = true;
|
||||||
@@ -219,9 +225,8 @@ impl AccessPaths {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_perm(&mut self, perm: AccessPerm) {
|
pub fn set_perm(&mut self, perm: AccessPerm) {
|
||||||
if self.perm < perm {
|
if !perm.indexonly() {
|
||||||
self.perm = perm;
|
self.perm = perm;
|
||||||
self.recursively_purge_children(perm);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,17 +251,6 @@ impl AccessPaths {
|
|||||||
Some(target)
|
Some(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recursively_purge_children(&mut self, perm: AccessPerm) {
|
|
||||||
self.children.retain(|_, child| {
|
|
||||||
if child.perm <= perm {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
child.recursively_purge_children(perm);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add(&mut self, path: &str, perm: AccessPerm) {
|
fn add(&mut self, path: &str, perm: AccessPerm) {
|
||||||
let path = path.trim_matches('/');
|
let path = path.trim_matches('/');
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
@@ -268,18 +262,48 @@ impl AccessPaths {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_impl(&mut self, parts: &[&str], perm: AccessPerm) {
|
fn add_impl(&mut self, parts: &[&str], perm: AccessPerm) {
|
||||||
let parts_len = parts.len();
|
if parts.is_empty() {
|
||||||
if parts_len == 0 {
|
self.perm = perm;
|
||||||
self.set_perm(perm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if self.perm >= perm {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let child = self.children.entry(parts[0].to_string()).or_default();
|
let child = self.children.entry(parts[0].to_string()).or_default();
|
||||||
child.add_impl(&parts[1..], perm)
|
child.add_impl(&parts[1..], perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Merge anonymous `AccessPaths` into `self` (a user's paths) with "higher perm wins" semantics.
|
||||||
|
/// `orig_user` is a snapshot of `self` before any anonymous merging begins, used so that
|
||||||
|
/// the user's own effective perm is measured against the pre-merge state.
|
||||||
|
fn absorb_anon(
|
||||||
|
&mut self,
|
||||||
|
anon: &AccessPaths,
|
||||||
|
orig_user: &AccessPaths,
|
||||||
|
user_inherited: AccessPerm,
|
||||||
|
anon_inherited: AccessPerm,
|
||||||
|
) {
|
||||||
|
let anon_eff = if !anon.perm.indexonly() {
|
||||||
|
anon.perm
|
||||||
|
} else {
|
||||||
|
anon_inherited
|
||||||
|
};
|
||||||
|
let orig_user_eff = if !orig_user.perm.indexonly() {
|
||||||
|
orig_user.perm
|
||||||
|
} else {
|
||||||
|
user_inherited
|
||||||
|
};
|
||||||
|
|
||||||
|
let combined = std::cmp::max(anon_eff, orig_user_eff);
|
||||||
|
if !combined.indexonly() && combined > self.perm {
|
||||||
|
self.perm = combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_ap = AccessPaths::default();
|
||||||
|
for (name, anon_child) in &anon.children {
|
||||||
|
let orig_user_child = orig_user.children.get(name).unwrap_or(&default_ap);
|
||||||
|
let user_child = self.children.entry(name.clone()).or_default();
|
||||||
|
user_child.absorb_anon(anon_child, orig_user_child, orig_user_eff, anon_eff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn find(&self, path: &str) -> Option<AccessPaths> {
|
pub fn find(&self, path: &str) -> Option<AccessPaths> {
|
||||||
let parts: Vec<&str> = path
|
let parts: Vec<&str> = path
|
||||||
.trim_matches('/')
|
.trim_matches('/')
|
||||||
@@ -706,7 +730,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
paths.find("dir2/dir21/dir211/file"),
|
paths.find("dir2/dir21/dir211/file"),
|
||||||
Some(AccessPaths::new(AccessPerm::ReadWrite))
|
Some(AccessPaths::new(AccessPerm::ReadOnly))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
paths.find("dir2/dir22/file"),
|
paths.find("dir2/dir22/file"),
|
||||||
|
|||||||
+12
-1
@@ -366,7 +366,18 @@ fn auth_data(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn auth_shadow(
|
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 = send_with_digest_auth(fetch!(b"PUT", &url).body(b"abc".to_vec()), "user", "pass")?;
|
||||||
|
assert_eq!(resp.status(), 403);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn auth_anonymous_no_precedence(
|
||||||
#[with(&["--auth", "user:pass@/:rw", "-a", "@/dir1", "-A"])] server: TestServer,
|
#[with(&["--auth", "user:pass@/:rw", "-a", "@/dir1", "-A"])] server: TestServer,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let url = format!("{}dir1/test.txt", server.url());
|
let url = format!("{}dir1/test.txt", server.url());
|
||||||
|
|||||||
Reference in New Issue
Block a user