Audit shared-library source writes
A shared source goes live in every flow using it the moment it is saved, so the write is recorded in the audit trail like a publish. Private source saves stay drafts and are not audited.
This commit is contained in:
@@ -625,6 +625,7 @@ async def save_node_source(
|
||||
node_id: str,
|
||||
source: NodeSource,
|
||||
controller: FlowControllerDep,
|
||||
user: CurrentUser,
|
||||
) -> Any:
|
||||
"""Save a node's source as an unpublished edit and report whether it loads.
|
||||
|
||||
@@ -634,7 +635,8 @@ async def save_node_source(
|
||||
|
||||
A shared node writes to the library, so the fix reaches every flow using
|
||||
it — and that one is live immediately rather than waiting for a publish,
|
||||
because the copy is not any single flow's to hold back.
|
||||
because the copy is not any single flow's to hold back. Being live, it is
|
||||
audited like a publish.
|
||||
"""
|
||||
definition = _read_flow(controller, name)
|
||||
ref = _source_ref(definition, node_id)
|
||||
@@ -643,6 +645,7 @@ async def save_node_source(
|
||||
controller.store.write_lib_source, ref, source.code
|
||||
)
|
||||
if changed:
|
||||
_audit(f"edited shared node '{ref}'", name, user)
|
||||
await controller.reload_lib_users(ref)
|
||||
else:
|
||||
await run_in_threadpool(
|
||||
|
||||
@@ -581,3 +581,37 @@ def test_a_shared_node_in_use_is_retired_only_with_detach(
|
||||
assert source.json()["code"] == WORKING_NODE
|
||||
|
||||
client.delete(f"{PREFIX}/shared", headers=superuser_token_headers)
|
||||
|
||||
|
||||
def test_a_shared_source_write_is_audited(
|
||||
client: TestClient,
|
||||
superuser_token_headers: dict[str, str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""It goes live at once, so it is recorded like a publish."""
|
||||
client.put(
|
||||
f"{PREFIX}/audited", headers=superuser_token_headers, json=a_flow("audited")
|
||||
)
|
||||
client.post(
|
||||
f"{PREFIX}/audited/nodes/sensor/share",
|
||||
headers=superuser_token_headers,
|
||||
json={"lib_name": "audited_reading"},
|
||||
)
|
||||
|
||||
published: list[dict] = []
|
||||
monkeypatch.setattr(event_bus, "publish", published.append)
|
||||
client.put(
|
||||
f"{PREFIX}/audited/nodes/sensor/source",
|
||||
headers=superuser_token_headers,
|
||||
json={"code": WORKING_NODE},
|
||||
)
|
||||
|
||||
(audit,) = [e for e in published if e["type"] == "audit"]
|
||||
assert audit["action"] == "edited shared node 'audited_reading'"
|
||||
assert audit["flow"] == "audited"
|
||||
|
||||
client.delete(
|
||||
f"{PREFIX}/library/audited_reading?detach=true",
|
||||
headers=superuser_token_headers,
|
||||
)
|
||||
client.delete(f"{PREFIX}/audited", headers=superuser_token_headers)
|
||||
|
||||
+2
-1
@@ -80,7 +80,8 @@ Function and Command nodes, changing Command settings, or changing
|
||||
`resources.env`. Other users can edit wiring, titles, and ordinary settings.
|
||||
|
||||
Ordinary source edits stay in the draft until publishing. Shared library
|
||||
source changes affect its users immediately.
|
||||
source changes affect its users immediately, and the audit trail records them
|
||||
like a publish.
|
||||
`POST /flows/{name}/nodes/{id}/share` promotes a source to the library;
|
||||
`/unshare` makes it private again. `GET /flows/library` lists shared sources.
|
||||
`DELETE /flows/library/{name}` retires one, and answers 409 while nodes still
|
||||
|
||||
@@ -1017,7 +1017,8 @@ export class FlowsService {
|
||||
*
|
||||
* A shared node writes to the library, so the fix reaches every flow using
|
||||
* it — and that one is live immediately rather than waiting for a publish,
|
||||
* because the copy is not any single flow's to hold back.
|
||||
* because the copy is not any single flow's to hold back. Being live, it is
|
||||
* audited like a publish.
|
||||
* @param data The data for the request.
|
||||
* @param data.name
|
||||
* @param data.nodeId
|
||||
|
||||
Reference in New Issue
Block a user