P1: WebDAV ACL enforcement (RFC 3744)
Some checks failed
Test / build (push) Has been cancelled
Test / test (push) Has been cancelled

- Add enable_acl field to VfsDavFs
- Add check_acl() helper method
- ACL checks in open(), read_dir(), create_dir(), remove_dir(), remove_file(), rename()
- Uses VfsAceMask for permission checks (ReadData, WriteData, etc.)
- Returns FsError::Forbidden if ACL denies access

Tests: 289 passed, 0 failed
This commit is contained in:
Warren
2026-06-21 18:37:48 +08:00
parent 2a0376cc58
commit 9c82830959

View File

@@ -81,6 +81,7 @@ pub struct VfsDavFs {
versioning: Option<Arc<WebDavVersioning>>, versioning: Option<Arc<WebDavVersioning>>,
props_data: Arc<RwLock<HashMap<String, Vec<DavProp>>>>, props_data: Arc<RwLock<HashMap<String, Vec<DavProp>>>>,
props_path: PathBuf, props_path: PathBuf,
enable_acl: bool,
} }
impl Clone for VfsDavFs { impl Clone for VfsDavFs {
@@ -93,6 +94,7 @@ impl Clone for VfsDavFs {
versioning: self.versioning.clone(), versioning: self.versioning.clone(),
props_data: self.props_data.clone(), props_data: self.props_data.clone(),
props_path: self.props_path.clone(), props_path: self.props_path.clone(),
enable_acl: self.enable_acl,
} }
} }
} }
@@ -178,6 +180,7 @@ impl VfsDavFs {
versioning: None, versioning: None,
props_data, props_data,
props_path, props_path,
enable_acl: true,
}) })
} }
@@ -198,9 +201,14 @@ impl VfsDavFs {
versioning: Some(versioning), versioning: Some(versioning),
props_data, props_data,
props_path, props_path,
enable_acl: true,
}) })
} }
pub fn set_enable_acl(&mut self, enable: bool) {
self.enable_acl = enable;
}
fn rel_key(&self, path: &DavPath) -> String { fn rel_key(&self, path: &DavPath) -> String {
let rel = path.as_pathbuf(); let rel = path.as_pathbuf();
rel.to_string_lossy().to_string() rel.to_string_lossy().to_string()
@@ -617,6 +625,18 @@ impl VfsDavFs {
xml: Some(Self::empty_acl_xml()), xml: Some(Self::empty_acl_xml()),
} }
} }
fn check_acl(&self, path: &Path, mask: crate::vfs::VfsAceMask) -> Result<(), FsError> {
if !self.enable_acl {
return Ok(());
}
match self.vfs.check_acl(path, &self.user_uuid, mask) {
Ok(true) => Ok(()),
Ok(false) => Err(FsError::Forbidden),
Err(crate::vfs::VfsError::Unsupported(_)) => Ok(()),
Err(_) => Err(FsError::Forbidden),
}
}
} }
impl DavFileSystem for VfsDavFs { impl DavFileSystem for VfsDavFs {
@@ -631,6 +651,9 @@ impl DavFileSystem for VfsDavFs {
}; };
if options.write { if options.write {
if let Err(e) = self.check_acl(&full_path, crate::vfs::VfsAceMask::WriteData) {
return Box::pin(std::future::ready(Err(e)));
}
let file = VfsDavFile::new_write( let file = VfsDavFile::new_write(
full_path, full_path,
self.vfs.clone_boxed(), self.vfs.clone_boxed(),
@@ -640,6 +663,9 @@ impl DavFileSystem for VfsDavFs {
); );
Box::pin(std::future::ready(Ok(Box::new(file) as Box<dyn DavFile>))) Box::pin(std::future::ready(Ok(Box::new(file) as Box<dyn DavFile>)))
} else { } else {
if let Err(e) = self.check_acl(&full_path, crate::vfs::VfsAceMask::ReadData) {
return Box::pin(std::future::ready(Err(e)));
}
let flags = OpenFlags::new().read(); let flags = OpenFlags::new().read();
match self.vfs.open_file(&full_path, &flags) { match self.vfs.open_file(&full_path, &flags) {
Ok(vfs_file) => { Ok(vfs_file) => {
@@ -661,6 +687,10 @@ impl DavFileSystem for VfsDavFs {
Err(e) => return Box::pin(std::future::ready(Err(e))), Err(e) => return Box::pin(std::future::ready(Err(e))),
}; };
if let Err(e) = self.check_acl(&full_path, crate::vfs::VfsAceMask::ListDirectory) {
return Box::pin(std::future::ready(Err(e)));
}
match self.vfs.read_dir(&full_path) { match self.vfs.read_dir(&full_path) {
Ok(entries) => { Ok(entries) => {
let results: Vec<Box<dyn DavDirEntry>> = entries let results: Vec<Box<dyn DavDirEntry>> = entries
@@ -698,6 +728,9 @@ impl DavFileSystem for VfsDavFs {
Ok(p) => p, Ok(p) => p,
Err(e) => return Box::pin(std::future::ready(Err(e))), Err(e) => return Box::pin(std::future::ready(Err(e))),
}; };
if let Err(e) = self.check_acl(&full_path, crate::vfs::VfsAceMask::AddSubdirectory) {
return Box::pin(std::future::ready(Err(e)));
}
if self.vfs.exists(&full_path) { if self.vfs.exists(&full_path) {
return Box::pin(std::future::ready(Err(FsError::Exists))); return Box::pin(std::future::ready(Err(FsError::Exists)));
} }
@@ -712,6 +745,9 @@ impl DavFileSystem for VfsDavFs {
Ok(p) => p, Ok(p) => p,
Err(e) => return Box::pin(std::future::ready(Err(e))), Err(e) => return Box::pin(std::future::ready(Err(e))),
}; };
if let Err(e) = self.check_acl(&full_path, crate::vfs::VfsAceMask::DeleteChild) {
return Box::pin(std::future::ready(Err(e)));
}
match self.vfs.remove_dir_all(&full_path) { match self.vfs.remove_dir_all(&full_path) {
Ok(_) => Box::pin(std::future::ready(Ok(()))), Ok(_) => Box::pin(std::future::ready(Ok(()))),
Err(e) => Box::pin(std::future::ready(Err(map_vfs_error(e)))), Err(e) => Box::pin(std::future::ready(Err(map_vfs_error(e)))),
@@ -723,6 +759,9 @@ impl DavFileSystem for VfsDavFs {
Ok(p) => p, Ok(p) => p,
Err(e) => return Box::pin(std::future::ready(Err(e))), Err(e) => return Box::pin(std::future::ready(Err(e))),
}; };
if let Err(e) = self.check_acl(&full_path, crate::vfs::VfsAceMask::Delete) {
return Box::pin(std::future::ready(Err(e)));
}
match self.vfs.remove_file(&full_path) { match self.vfs.remove_file(&full_path) {
Ok(_) => Box::pin(std::future::ready(Ok(()))), Ok(_) => Box::pin(std::future::ready(Ok(()))),
Err(e) => Box::pin(std::future::ready(Err(map_vfs_error(e)))), Err(e) => Box::pin(std::future::ready(Err(map_vfs_error(e)))),
@@ -738,6 +777,12 @@ impl DavFileSystem for VfsDavFs {
Ok(p) => p, Ok(p) => p,
Err(e) => return Box::pin(std::future::ready(Err(e))), Err(e) => return Box::pin(std::future::ready(Err(e))),
}; };
if let Err(e) = self.check_acl(&from_path, crate::vfs::VfsAceMask::Delete) {
return Box::pin(std::future::ready(Err(e)));
}
if let Err(e) = self.check_acl(&to_path, crate::vfs::VfsAceMask::AddFile) {
return Box::pin(std::future::ready(Err(e)));
}
let from_key = self.rel_key(from); let from_key = self.rel_key(from);
let to_key = self.rel_key(to); let to_key = self.rel_key(to);
let props_data = self.props_data.clone(); let props_data = self.props_data.clone();