From 0ae39ea8e43e629d67e9373d189f3730019b4bfc Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 10 Dec 2023 22:49:35 +0100 Subject: [PATCH] feat: support overlapping permissions (#6561) * feat: support overlapping permissions * cleanup --- crates/config/src/fs_permissions.rs | 43 +++++++++++++++++++++++++++-- crates/forge/tests/it/repros.rs | 18 ++++++++++++ testdata/repros/Issue6554.t.sol | 16 +++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 testdata/repros/Issue6554.t.sol diff --git a/crates/config/src/fs_permissions.rs b/crates/config/src/fs_permissions.rs index 3128cd6014c2..5a8328ba4b1c 100644 --- a/crates/config/src/fs_permissions.rs +++ b/crates/config/src/fs_permissions.rs @@ -25,6 +25,11 @@ impl FsPermissions { Self { permissions: permissions.into_iter().collect() } } + /// Adds a new permission + pub fn add(&mut self, permission: PathPermission) { + self.permissions.push(permission) + } + /// Returns true if access to the specified path is allowed with the specified. /// /// This first checks permission, and only if it is granted, whether the path is allowed. @@ -37,9 +42,28 @@ impl FsPermissions { self.find_permission(path).map(|perm| perm.is_granted(kind)).unwrap_or_default() } - /// Returns the permission for the matching path + /// Returns the permission for the matching path. + /// + /// This finds the longest matching path, e.g. if we have the following permissions: + /// + /// `./out` = `read` + /// `./out/contracts` = `read-write` + /// + /// And we check for `./out/contracts/MyContract.sol` we will get `read-write` as permission. pub fn find_permission(&self, path: &Path) -> Option { - self.permissions.iter().find(|perm| path.starts_with(&perm.path)).map(|perm| perm.access) + let mut permission: Option<&PathPermission> = None; + for perm in &self.permissions { + if path.starts_with(&perm.path) { + if let Some(active_perm) = permission.as_ref() { + // the longest path takes precedence + if perm.path < active_perm.path { + continue; + } + } + permission = Some(perm); + } + } + permission.map(|perm| perm.access) } /// Updates all `allowed_paths` and joins ([`Path::join`]) the `root` with all entries @@ -238,4 +262,19 @@ mod tests { assert_eq!(FsAccessPermission::Read, "read".parse().unwrap()); assert_eq!(FsAccessPermission::Write, "write".parse().unwrap()); } + + #[test] + fn nested_permissions() { + let permissions = FsPermissions::new(vec![ + PathPermission::read("./"), + PathPermission::write("./out"), + PathPermission::read_write("./out/contracts"), + ]); + + let permission = + permissions.find_permission(Path::new("./out/contracts/MyContract.sol")).unwrap(); + assert_eq!(FsAccessPermission::ReadWrite, permission); + let permission = permissions.find_permission(Path::new("./out/MyContract.sol")).unwrap(); + assert_eq!(FsAccessPermission::Write, permission); + } } diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index 490954eb83e2..5ea2bd37845c 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -37,6 +37,16 @@ macro_rules! test_repro { } } }; + ($issue_number:literal; |$config:ident| $e:expr $(,)?) => { + paste::paste! { + #[tokio::test(flavor = "multi_thread")] + async fn [< issue_ $issue_number >]() { + let mut $config = repro_config($issue_number, false, None).await; + $e + $config.run().await; + } + } + }; } async fn repro_config(issue: usize, should_fail: bool, sender: Option
) -> TestConfig { @@ -267,3 +277,11 @@ test_repro!(6501, false, None, |res| { ); } }); + +// https://github.com/foundry-rs/foundry/issues/6554 +test_repro!(6554; |config| { + let mut cheats_config = config.runner.cheats_config.as_ref().clone(); + let path = cheats_config.root.join("out/Issue6554.t.sol"); + cheats_config.fs_permissions.add(PathPermission::read_write(path)); + config.runner.cheats_config = std::sync::Arc::new(cheats_config); +}); diff --git a/testdata/repros/Issue6554.t.sol b/testdata/repros/Issue6554.t.sol new file mode 100644 index 000000000000..be7af3d9d9ca --- /dev/null +++ b/testdata/repros/Issue6554.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "../cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/6554 +contract Issue6554Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testPermissions() public { + vm.writeFile("./out/Issue6554.t.sol/cachedFile.txt", "cached data"); + string memory content = vm.readFile("./out/Issue6554.t.sol/cachedFile.txt"); + assertEq(content, "cached data"); + } +}