broker: initialize drops the dataset's bearer tokens and everything they protected
Build Packages / Create release (push) Successful in 8s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 4m52s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 9m14s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 15m2s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 20m44s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 21m49s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 22m3s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 26m5s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 26m45s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 27m15s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 28m0s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 22m22s
Build Packages / Generate python client (push) Successful in 24s
Build Packages / Build documentation (push) Successful in 1m16s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 23m36s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 27m10s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 26m35s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 22m7s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 25m14s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 26m56s
Build Packages / Unit tests (push) Successful in 1h33m16s
Build Packages / Validation Battery (push) Successful in 32m33s

Initialize is the fresh start between users. It already cleared the
images, plots, scan result and live rugnux status; it now also drops the
tokens, resets the dataset settings (name, file prefix, unit cell, ...)
to their defaults and forgets the last collection followed through
/live/events, so after it nothing of the previous dataset is readable,
with or without its token - the broker is as open and as empty as a
freshly started one.

The auth test checks that after /initialize the data-collection
statistics and the /statistics aggregate are open and no longer carry
the previous dataset's name. SECURITY.md also lists /rugnux/status
among the protected endpoints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-10-20 12:16:32 +02:00
1 parent 6f5c235fd7
commit 9e09cd4b3d
6 files changed
+39 -4

No files matched your search

+6 -1
View File
@@ -330,8 +330,13 @@ void JFJochStateMachine::Initialize() {
start_exception = nullptr; // Re-initialising discards a pending start failure
// A fresh start: no images, plots or live rugnux status from before, and no live rugnux analysis
// or model left over from the previous user.
// or model left over from the previous user. The previous dataset's tokens go too, and with them
// everything they protected: its settings (name, cell, ...) and the record of its collection.
services.ClearResults();
tokens.Replace({});
experiment.ImportDatasetSettings(DatasetSettings());
if (auto *live = services.GetLiveCollection())
live->Forget();
{
std::unique_lock ul2(rugnux_mutex);
RugnuxLiveSettings s = services.GetRugnuxLive();
+1
View File
@@ -2918,6 +2918,7 @@ paths:
During operation of the detector it is recommended to use the `POST /pedestal` operation instead.
If storage cells are used, the execution time might be few minutes.
It also clears the image buffer, the previous collection's plots and live rugnux status, disables live rugnux analysis and removes the rugnux model.
It drops the previous dataset's bearer tokens and resets its settings, so nothing of that dataset stays readable.
This is async function - one needs to use `POST /wait_till_done` to ensure operation is done.
responses:
+4 -1
View File
@@ -101,7 +101,9 @@ users cannot read this one. No endpoint returns the tokens; the broker only comp
An accepted `/start` also clears what the previous run left readable - its statistics, plots and
buffered images - in the same step that installs the new tokens, so the previous run is never
served under the new tokens, and the new run's name never under the old ones. A refused `/start`
(wrong state, invalid settings) changes nothing.
(wrong state, invalid settings) changes nothing. `/initialize` drops the tokens together with
everything they protected - the dataset's settings, results, images, plots, live rugnux status and
the record of its collection - so the broker is left as open and as empty as a freshly started one.
| Endpoint | With tokens set |
|----------|-----------------|
@@ -110,6 +112,7 @@ served under the new tokens, and the new run's name never under the old ones. A
| `/image_buffer/start.cbor`, `/image_buffer/image.cbor`, `/image_buffer/image.jpeg`, `/image_buffer/image.tiff` (the images and the start message) | 401 without a token |
| `/preview/plot`, `/preview/plot.bin` (per-image plots, unit cell) | 401 without a token |
| `/live/events` (the start message and the data files of the collection being written) | 401 without a token |
| `/rugnux/status` (live rugnux run of the collection: cell, space group, results) | 401 without a token |
| `/statistics` (the aggregate the web UI polls) | 200, but the `measurement` block is omitted |
| everything else (`/status`, `/config/*`, `/start`, `/cancel`, masks, pedestal, ...) | open |
+10
View File
@@ -68,6 +68,16 @@ void LiveCollection::EndIfOpen(const std::string &error) {
End(0, error);
}
void LiveCollection::Forget() {
{
std::unique_lock ul(m);
state = Snapshot{};
start_message = StartMessage{};
start_cbor.clear();
}
cv.notify_all();
}
std::optional<std::vector<uint8_t>> LiveCollection::StartCBOR() const {
std::unique_lock ul(m);
if (!state.started)
+2
View File
@@ -51,6 +51,8 @@ public:
void End(uint64_t images, const std::string &error = "");
// The measurement is over: ends a collection the image pusher did not end, with this error.
void EndIfOpen(const std::string &error);
// Back to "no collection since the broker started" (/initialize): nothing of the last one is kept.
void Forget();
// The start message of the current collection; nothing before it is known.
std::optional<std::vector<uint8_t>> StartCBOR() const;
+16 -2
View File
@@ -110,6 +110,20 @@ TEST_CASE("JFJochBrokerHttp_BearerTokens", "[broker]") {
REQUIRE(client.Get("/live/events", good)->status == 409);
}
SECTION("initialize drops the tokens and everything they protected") {
REQUIRE(client.Post("/initialize")->status == 200);
for (int i = 0; i < 120 && nlohmann::json::parse(client.Get("/status")->body).at("state") != "Idle"; i++)
std::this_thread::sleep_for(std::chrono::milliseconds(500));
auto res = client.Get("/statistics/data_collection");
REQUIRE(res->status == 200);
REQUIRE(res->body.find("protected_run") == std::string::npos);
auto stats = client.Get("/statistics");
REQUIRE(stats->status == 200);
REQUIRE(stats->body.find("protected_run") == std::string::npos);
REQUIRE(client.Get("/rugnux/status")->status == 200);
REQUIRE(client.Get("/result/scan")->status != 401);
}
SECTION("the control plane and the broker status stay open") {
REQUIRE(client.Get("/status")->status == 200);
REQUIRE(client.Get("/image_buffer/status")->status == 200);
@@ -122,11 +136,11 @@ TEST_CASE("JFJochBrokerHttp_BearerTokens", "[broker]") {
nlohmann::json body = {{"beam_x_pxl", 1000}, {"beam_y_pxl", 1000},
{"detector_distance_mm", 100}, {"incident_energy_keV", 12.4}};
REQUIRE(client.Post("/start", body.dump(), "application/json")->status == 500);
REQUIRE(client.Post("/initialize")->status == 200);
REQUIRE(client.Get("/statistics/data_collection")->status == 401);
REQUIRE(client.Get("/statistics/data_collection", good)->status == 200);
// The next start without tokens lifts the protection.
// Back from Inactive; the next start without tokens leaves the dataset open.
REQUIRE(client.Post("/initialize")->status == 200);
start(nlohmann::json::object());
REQUIRE(client.Get("/statistics/data_collection")->status == 200);
REQUIRE(client.Get("/statistics/data_collection", wrong)->status == 200);