diff options
| author | Dan Engelbrecht <[email protected]> | 2024-10-23 10:31:43 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2024-10-23 10:31:43 +0200 |
| commit | 530ab3394938331f224058c381a1db5d4a68e6a9 (patch) | |
| tree | 5060eb394d67b7454855aed0fa8d7d3acf5f5c98 /src/zenserver-test/zenserver-test.cpp | |
| parent | fix gc date (#204) (diff) | |
| download | zen-530ab3394938331f224058c381a1db5d4a68e6a9.tar.xz zen-530ab3394938331f224058c381a1db5d4a68e6a9.zip | |
workspace share security (#192)
- Improvement: Reworked workspace shares to be more secure. Workspaces and workspace shares can only be created using the `zen workspace` command, the http endpoint is disabled unless zenserver is started with the `--workspaces-allow-changes` option enabled.
- Each workspace are now configured via a `zenworkspaceconfig.json` file in the root of each workspace
- A workspace can allow shares to be created via the http interface if the workspace is created with the `--allow-share-create-from-http` option enabled
- A new http endpoint at `/ws` - issuing a `Get` operation will get you a list of workspaces
- A new http endpoint at `/ws/refresh` - issuing a `Get` will make zenserver scan for edits in workspaces and workspace shares
Diffstat (limited to 'src/zenserver-test/zenserver-test.cpp')
| -rw-r--r-- | src/zenserver-test/zenserver-test.cpp | 165 |
1 files changed, 147 insertions, 18 deletions
diff --git a/src/zenserver-test/zenserver-test.cpp b/src/zenserver-test/zenserver-test.cpp index 15993f5c9..ca2257361 100644 --- a/src/zenserver-test/zenserver-test.cpp +++ b/src/zenserver-test/zenserver-test.cpp @@ -3259,16 +3259,22 @@ TEST_CASE("workspaces.create") std::filesystem::path TestDir = TestEnv.CreateNewTestDir(); ZenServerInstance Instance(TestEnv); Instance.SetTestDir(TestDir); - const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(fmt::format("--workspaces-enabled --system-dir {}", SystemRootPath)); + const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady( + fmt::format("--workspaces-enabled --workspaces-allow-changes --system-dir {}", SystemRootPath)); CHECK(PortNumber != 0); ScopedTemporaryDirectory TempDir; - std::filesystem::path Root1Path = TempDir.Path() / "root1"; - std::filesystem::path Root2Path = TempDir.Path() / "root2"; - std::filesystem::path Share1Path = "shared_1"; - std::filesystem::path Share2Path = "shared_2"; - CreateDirectories(Share1Path); - CreateDirectories(Share2Path); + std::filesystem::path Root1Path = TempDir.Path() / "root1"; + std::filesystem::path Root2Path = TempDir.Path() / "root2"; + DeleteDirectories(Root1Path); + DeleteDirectories(Root2Path); + + std::filesystem::path Share1Path = "shared_1"; + std::filesystem::path Share2Path = "shared_2"; + CreateDirectories(Root1Path / Share1Path); + CreateDirectories(Root1Path / Share2Path); + CreateDirectories(Root2Path / Share1Path); + CreateDirectories(Root2Path / Share2Path); Oid Root1Id = Oid::Zero; Oid Root2Id = Oid::NewOid(); @@ -3319,8 +3325,11 @@ TEST_CASE("workspaces.create") Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode == HttpResponseCode::NotFound); + CHECK(Client.Put(fmt::format("/ws/{}", Root2Id), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}}).StatusCode == + HttpResponseCode::Conflict); + if (HttpClient::Response Root2Response = - Client.Put(fmt::format("/ws/{}", Root2Id), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}}); + Client.Put(fmt::format("/ws/{}", Root2Id), HttpClient::KeyValueMap{{"root_path", Root2Path.string()}}); Root2Response.StatusCode == HttpResponseCode::Created) { CHECK(Root2Id == Oid::TryFromHexString(Root2Response.AsText())); @@ -3340,6 +3349,10 @@ TEST_CASE("workspaces.create") Share2Id = Oid::TryFromHexString(Share2Response.AsText()); CHECK(Share2Id != Oid::Zero); } + else + { + CHECK(false); + } CHECK( Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode == @@ -3352,6 +3365,117 @@ TEST_CASE("workspaces.create") CHECK( Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share1Path.string()}}).StatusCode == HttpResponseCode::Conflict); + + CHECK(Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::NewOid()), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}) + .StatusCode == HttpResponseCode::Conflict); + + CHECK(Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", "idonotexist"}}).StatusCode != + HttpResponseCode::OK); + + while (true) + { + std::error_code Ec; + std::filesystem::remove_all(Root2Path / Share2Path, Ec); + if (!Ec) + break; + } + + CHECK(Client.Get(fmt::format("/ws/{}/{}/files", Root2Id, Share2Id)).StatusCode == HttpResponseCode::NotFound); +} + +TEST_CASE("workspaces.restricted") +{ + using namespace std::literals; + + std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir(); + + std::filesystem::path TestDir = TestEnv.CreateNewTestDir(); + ZenServerInstance Instance(TestEnv); + Instance.SetTestDir(TestDir); + const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(fmt::format("--workspaces-enabled --system-dir {}", SystemRootPath)); + CHECK(PortNumber != 0); + + ScopedTemporaryDirectory TempDir; + std::filesystem::path Root1Path = TempDir.Path() / "root1"; + std::filesystem::path Root2Path = TempDir.Path() / "root2"; + DeleteDirectories(Root1Path); + DeleteDirectories(Root2Path); + + std::filesystem::path Share1Path = "shared_1"; + std::filesystem::path Share2Path = "shared_2"; + CreateDirectories(Root1Path / Share1Path); + CreateDirectories(Root1Path / Share2Path); + CreateDirectories(Root2Path / Share1Path); + CreateDirectories(Root2Path / Share2Path); + + Oid Root1Id = Oid::NewOid(); + Oid Root2Id = Oid::NewOid(); + Oid Share1Id = Oid::NewOid(); + Oid Share2Id = Oid::NewOid(); + + HttpClient Client(Instance.GetBaseUri()); + CHECK(Client.Put(fmt::format("/ws/{}", Oid::Zero), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}}).StatusCode == + HttpResponseCode::Unauthorized); + + CHECK_EQ(Client.Get(fmt::format("/ws/{}", Root1Id)).StatusCode, HttpResponseCode::NotFound); + + std::string Config1; + { + CbObjectWriter Config; + Config.BeginArray("workspaces"); + Config.BeginObject(); + Config << "id"sv << Root1Id.ToString(); + Config << "root_path"sv << Root1Path.string(); + Config << "allow_share_creation_from_http"sv << false; + Config.EndObject(); + Config.EndArray(); + ExtendableStringBuilder<256> SB; + CompactBinaryToJson(Config.Save(), SB); + Config1 = SB.ToString(); + } + WriteFile(SystemRootPath / "workspaces" / "config.json", IoBuffer(IoBuffer::Wrap, Config1.data(), Config1.size())); + + CHECK(IsHttpSuccessCode(Client.Get("/ws/refresh").StatusCode)); + + CHECK_EQ(Client.Get(fmt::format("/ws/{}", Root1Id)).StatusCode, HttpResponseCode::OK); + + CHECK(Client.Get(fmt::format("/ws/{}/{}", Root1Id, Share1Id)).StatusCode == HttpResponseCode::NotFound); + CHECK( + Client.Put(fmt::format("/ws/{}/{}", Root1Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share1Path.string()}}).StatusCode == + HttpResponseCode::Unauthorized); + + std::string Config2; + { + CbObjectWriter Config; + Config.BeginArray("workspaces"); + Config.BeginObject(); + Config << "id"sv << Root1Id.ToString(); + Config << "root_path"sv << Root1Path.string(); + Config << "allow_share_creation_from_http"sv << false; + Config.EndObject(); + Config.BeginObject(); + Config << "id"sv << Root2Id.ToString(); + Config << "root_path"sv << Root2Path.string(); + Config << "allow_share_creation_from_http"sv << true; + Config.EndObject(); + Config.EndArray(); + ExtendableStringBuilder<256> SB; + CompactBinaryToJson(Config.Save(), SB); + Config2 = SB.ToString(); + } + WriteFile(SystemRootPath / "workspaces" / "config.json", IoBuffer(IoBuffer::Wrap, Config2.data(), Config2.size())); + + CHECK(IsHttpSuccessCode(Client.Get("/ws/refresh").StatusCode)); + + CHECK_EQ(Client.Get(fmt::format("/ws/{}", Root2Id)).StatusCode, HttpResponseCode::OK); + + CHECK(Client.Get(fmt::format("/ws/{}/{}", Root2Id, Share2Id)).StatusCode == HttpResponseCode::NotFound); + CHECK( + Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode == + HttpResponseCode::Created); + CHECK(Client.Get(fmt::format("/ws/{}/{}", Root2Id, Share2Id)).StatusCode == HttpResponseCode::OK); + + CHECK(IsHttpSuccessCode(Client.Delete(fmt::format("/ws/{}/{}", Root2Id, Share2Id)).StatusCode)); } TEST_CASE("workspaces.lifetimes") @@ -3363,19 +3487,20 @@ TEST_CASE("workspaces.lifetimes") Oid WorkspaceId = Oid::NewOid(); Oid ShareId = Oid::NewOid(); + ScopedTemporaryDirectory TempDir; + std::filesystem::path RootPath = TempDir.Path(); + DeleteDirectories(RootPath); + std::filesystem::path SharePath = RootPath / "shared_folder"; + CreateDirectories(SharePath); + { std::filesystem::path TestDir = TestEnv.CreateNewTestDir(); ZenServerInstance Instance(TestEnv); Instance.SetTestDir(TestDir); - const uint16_t PortNumber = - Instance.SpawnServerAndWaitUntilReady(fmt::format("--workspaces-enabled --system-dir {}", SystemRootPath)); + const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady( + fmt::format("--workspaces-enabled --workspaces-allow-changes --system-dir {}", SystemRootPath)); CHECK(PortNumber != 0); - ScopedTemporaryDirectory TempDir; - std::filesystem::path RootPath = TempDir.Path(); - std::filesystem::path SharePath = RootPath / "shared_folder"; - CreateDirectories(SharePath); - HttpClient Client(Instance.GetBaseUri()); CHECK(Client.Put(fmt::format("/ws/{}", WorkspaceId), HttpClient::KeyValueMap{{"root_path", RootPath.string()}}).StatusCode == HttpResponseCode::Created); @@ -3427,14 +3552,18 @@ TEST_CASE("workspaces.lifetimes") TEST_CASE("workspaces.share") { + std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir(); + ZenServerInstance Instance(TestEnv); - const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady("--workspaces-enabled"); + const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady( + fmt::format("--workspaces-enabled --workspaces-allow-changes --system-dir {}", SystemRootPath)); CHECK(PortNumber != 0); ScopedTemporaryDirectory TempDir; - std::filesystem::path RootPath = TempDir.Path(); - std::filesystem::path SharePath = RootPath / "shared_folder"; + std::filesystem::path RootPath = TempDir.Path(); + DeleteDirectories(RootPath); + std::filesystem::path SharePath = RootPath / "shared_folder"; GenerateFolderContent(SharePath); HttpClient Client(Instance.GetBaseUri()); |