PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
xds_audit_events.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
37
39
40#include <chrono>
41#include <memory>
42#include <string>
43#include <utility>
44#include <vector>
45
46namespace kcenon::pacs::security {
47
48namespace {
49
50// -----------------------------------------------------------------------------
51// RFC 3881 coded-value constants
52// -----------------------------------------------------------------------------
53
54// EventID values the reviewer's Task-2 checklist pins per transaction.
55// DCM 110107 Import is not in atna_event_ids (only Export 110106 is), so
56// both are declared here to keep the vocabulary in one place.
57const atna_coded_value kEventIdExport{
58 "110106", "DCM", "Export"};
59const atna_coded_value kEventIdImport{
60 "110107", "DCM", "Import"};
61const atna_coded_value kEventIdQuery{
62 "110112", "DCM", "Query"};
63
64// EventTypeCodes - code system "IHETransactions" per IHE ITI TF-2a
65// Audit Trail Profile. The ITI-18 code uses the full URN form the
66// reviewer specified; ITI-41/43 use the short transaction label that
67// matches the existing atna_service_auditor convention.
68const atna_coded_value kEventTypeIti18{
69 "urn:ihe:iti:2007:RegistryStoredQuery",
70 "IHETransactions",
71 "Registry Stored Query"};
72const atna_coded_value kEventTypeIti41{
73 "ITI-41", "IHETransactions",
74 "Provide and Register Document Set-b"};
75const atna_coded_value kEventTypeIti43{
76 "ITI-43", "IHETransactions", "Retrieve Document Set"};
77
78// Object ID type codes for participant objects.
79const atna_coded_value kXdsDocumentUniqueIdType{
80 "urn:ihe:iti:xds:2013:uniqueId", "IHE XDS Metadata",
81 "XDSDocumentEntry.uniqueId"};
82const atna_coded_value kXdsEntryUuidType{
83 "urn:ihe:iti:xds:2013:entryUUID", "IHE XDS Metadata",
84 "XDSDocumentEntry.entryUUID"};
85const atna_coded_value kXdsSubmissionSetUidType{
86 "urn:ihe:iti:xds:2013:submissionSet.uniqueId", "IHE XDS Metadata",
87 "XDSSubmissionSet.uniqueId"};
88
89// -----------------------------------------------------------------------------
90// Helpers
91// -----------------------------------------------------------------------------
92
93atna_active_participant make_participant(const std::string& user_id,
94 bool is_requestor,
95 const atna_coded_value& role) {
96 atna_active_participant p;
97 p.user_id = user_id;
98 p.user_is_requestor = is_requestor;
99 p.network_access_point_type = atna_network_access_type::machine_name;
100 p.role_id_codes.push_back(role);
101 return p;
102}
103
104void attach_failure_detail(atna_audit_message& msg,
105 const std::string& description) {
106 if (description.empty()) return;
107 atna_object_detail d;
108 d.type = "FailureDescription";
109 d.value = description;
110 if (msg.participant_objects.empty()) {
111 atna_participant_object synth;
112 synth.object_type = atna_object_type::system_object;
113 synth.object_role = atna_object_role::report;
114 synth.object_id_type_code = kXdsDocumentUniqueIdType;
115 synth.object_id = "(no object context)";
116 synth.object_details.push_back(std::move(d));
117 msg.participant_objects.push_back(std::move(synth));
118 } else {
119 msg.participant_objects.back().object_details.push_back(std::move(d));
120 }
121}
122
123} // namespace
124
125// -----------------------------------------------------------------------------
126// Message builders (public, testing surface)
127// -----------------------------------------------------------------------------
128
130 const std::string& audit_source_id, const xds_iti41_event& e,
131 const std::string& local_participant_id) {
133 msg.event_id = kEventIdExport;
135 msg.event_date_time = std::chrono::system_clock::now();
136 msg.event_outcome = e.outcome;
137 msg.event_type_codes.push_back(kEventTypeIti41);
138 msg.audit_source.audit_source_id = audit_source_id;
139
140 const std::string source =
141 local_participant_id.empty() ? e.source_id : local_participant_id;
142 msg.active_participants.push_back(
143 make_participant(source, true, atna_role_ids::source));
144 msg.active_participants.push_back(make_participant(
145 e.destination_endpoint, false, atna_role_ids::destination));
146
147 if (!e.submission_set_unique_id.empty()) {
151 ss.object_id_type_code = kXdsSubmissionSetUidType;
152 ss.object_id = e.submission_set_unique_id;
153 msg.participant_objects.push_back(std::move(ss));
154 }
155 for (const auto& uid : e.document_unique_ids) {
156 if (uid.empty()) continue;
160 d.object_id_type_code = kXdsDocumentUniqueIdType;
161 d.object_id = uid;
162 msg.participant_objects.push_back(std::move(d));
163 }
164 if (!e.patient_id.empty()) {
169 p.object_id = e.patient_id;
170 msg.participant_objects.push_back(std::move(p));
171 }
172 attach_failure_detail(msg, e.failure_description);
173 return msg;
174}
175
177 const std::string& audit_source_id, const xds_iti43_event& e,
178 const std::string& local_participant_id) {
180 msg.event_id = kEventIdImport;
182 msg.event_date_time = std::chrono::system_clock::now();
183 msg.event_outcome = e.outcome;
184 msg.event_type_codes.push_back(kEventTypeIti43);
185 msg.audit_source.audit_source_id = audit_source_id;
186
187 const std::string consumer =
188 local_participant_id.empty() ? e.consumer_id : local_participant_id;
189 // Consumer side: the local actor is the requestor pulling from a
190 // remote repository. Source role = remote repository (not
191 // requestor), destination role = local consumer (requestor).
192 msg.active_participants.push_back(
193 make_participant(e.source_endpoint, false, atna_role_ids::source));
194 msg.active_participants.push_back(
195 make_participant(consumer, true, atna_role_ids::destination));
196
197 if (!e.document_unique_id.empty()) {
201 d.object_id_type_code = kXdsDocumentUniqueIdType;
202 d.object_id = e.document_unique_id;
203 msg.participant_objects.push_back(std::move(d));
204 }
205 if (!e.repository_unique_id.empty()) {
210 r.object_id = e.repository_unique_id;
211 msg.participant_objects.push_back(std::move(r));
212 }
213 attach_failure_detail(msg, e.failure_description);
214 return msg;
215}
216
218 const std::string& audit_source_id, const xds_iti18_event& e,
219 const std::string& local_participant_id) {
221 msg.event_id = kEventIdQuery;
223 msg.event_date_time = std::chrono::system_clock::now();
224 msg.event_outcome = e.outcome;
225 msg.event_type_codes.push_back(kEventTypeIti18);
226 msg.audit_source.audit_source_id = audit_source_id;
227
228 const std::string caller =
229 local_participant_id.empty() ? e.querier_id : local_participant_id;
230 msg.active_participants.push_back(
231 make_participant(caller, true, atna_role_ids::source));
232 msg.active_participants.push_back(make_participant(
233 e.registry_endpoint, false, atna_role_ids::destination));
234
235 if (e.query_kind == xds_iti18_event::kind::find_documents &&
236 !e.patient_id.empty()) {
241 p.object_id = e.patient_id;
242 msg.participant_objects.push_back(std::move(p));
243 }
244 for (const auto& u : e.document_uuids) {
245 if (u.empty()) continue;
249 obj.object_id_type_code = kXdsEntryUuidType;
250 obj.object_id = u;
251 msg.participant_objects.push_back(std::move(obj));
252 }
253 attach_failure_detail(msg, e.failure_description);
254 return msg;
255}
256
257// -----------------------------------------------------------------------------
258// Sinks
259// -----------------------------------------------------------------------------
260
261namespace {
262
263class null_sink : public xds_audit_sink {
264public:
265 void on_iti41_submit(const xds_iti41_event&) override {}
266 void on_iti43_retrieve(const xds_iti43_event&) override {}
267 void on_iti18_query(const xds_iti18_event&) override {}
268};
269
270class atna_sink : public xds_audit_sink {
271public:
272 atna_sink(atna_service_auditor& auditor, std::string local_participant_id)
273 : auditor_(&auditor),
274 local_participant_id_(std::move(local_participant_id)) {}
275
276 void on_iti41_submit(const xds_iti41_event& e) override {
277 auditor_->submit_audit_message(build_iti41_audit_message(
278 auditor_->audit_source_id(), e, local_participant_id_));
279 }
280 void on_iti43_retrieve(const xds_iti43_event& e) override {
281 auditor_->submit_audit_message(build_iti43_audit_message(
282 auditor_->audit_source_id(), e, local_participant_id_));
283 }
284 void on_iti18_query(const xds_iti18_event& e) override {
285 auditor_->submit_audit_message(build_iti18_audit_message(
286 auditor_->audit_source_id(), e, local_participant_id_));
287 }
288
289private:
290 atna_service_auditor* auditor_;
292};
293
294} // namespace
295
296// -----------------------------------------------------------------------------
297// Factories
298// -----------------------------------------------------------------------------
299
300std::shared_ptr<xds_audit_sink> make_null_xds_audit_sink() {
301 return std::make_shared<null_sink>();
302}
303
304std::shared_ptr<xds_audit_sink> make_atna_xds_audit_sink(
305 atna_service_auditor& auditor, std::string local_participant_id) {
306 return std::make_shared<atna_sink>(auditor,
307 std::move(local_participant_id));
308}
309
310// -----------------------------------------------------------------------------
311// recording_xds_audit_sink
312// -----------------------------------------------------------------------------
313
314void recording_xds_audit_sink::on_iti41_submit(const xds_iti41_event& e) {
315 captured_event c;
316 c.k = captured_event::kind::iti41;
317 c.iti41 = e;
318 events_.push_back(std::move(c));
319}
320
321void recording_xds_audit_sink::on_iti43_retrieve(const xds_iti43_event& e) {
322 captured_event c;
323 c.k = captured_event::kind::iti43;
324 c.iti43 = e;
325 events_.push_back(std::move(c));
326}
327
328void recording_xds_audit_sink::on_iti18_query(const xds_iti18_event& e) {
329 captured_event c;
330 c.k = captured_event::kind::iti18;
331 c.iti18 = e;
332 events_.push_back(std::move(c));
333}
334
335const std::vector<recording_xds_audit_sink::captured_event>&
336recording_xds_audit_sink::events() const noexcept {
337 return events_;
338}
339
340void recording_xds_audit_sink::clear() noexcept {
341 events_.clear();
342}
343
344} // namespace kcenon::pacs::security
High-level facade for ATNA audit logging in DICOM services.
High-level facade for emitting ATNA audit events from DICOM services.
@ move
C-MOVE move request/response.
const atna_coded_value patient_number
Patient Number (RFC 3881 defined)
const atna_coded_value source
Source Role ID (110153)
const atna_coded_value destination
Destination Role ID (110152)
atna_audit_message build_iti18_audit_message(const std::string &audit_source_id, const xds_iti18_event &e, const std::string &local_participant_id)
std::shared_ptr< xds_audit_sink > make_null_xds_audit_sink()
atna_audit_message build_iti41_audit_message(const std::string &audit_source_id, const xds_iti41_event &e, const std::string &local_participant_id)
std::shared_ptr< xds_audit_sink > make_atna_xds_audit_sink(atna_service_auditor &auditor, std::string local_participant_id)
atna_audit_message build_iti43_audit_message(const std::string &audit_source_id, const xds_iti43_event &e, const std::string &local_participant_id)
atna_event_action event_action
Action that triggered the event.
std::vector< atna_participant_object > participant_objects
Participant objects (patients/studies/queries)
std::vector< atna_coded_value > event_type_codes
Event type codes for sub-classification.
atna_event_outcome event_outcome
Outcome of the event.
atna_coded_value event_id
Event ID coded value (e.g., DCM 110114 = UserAuthentication)
atna_audit_source audit_source
Audit source identification.
std::vector< atna_active_participant > active_participants
Active participants (users/processes)
std::chrono::system_clock::time_point event_date_time
When the event occurred.
std::string audit_source_id
Unique identifier for the audit source.
An object (patient, study, query) involved in the event.
std::string object_id
Object identifier (e.g., Patient ID, Study UID)
atna_object_role object_role
Role of the object in the event.
atna_coded_value object_id_type_code
Object ID type code.
std::string_view uid
std::string local_participant_id_
atna_service_auditor * auditor_
ATNA audit event helpers for the IHE XDS.b actors (ITI-18/41/43).