PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
atna_service_auditor.cpp
Go to the documentation of this file.
1
7
9
10// =============================================================================
11// Construction
12// =============================================================================
13
15 const syslog_transport_config& config,
16 std::string audit_source_id)
17 : audit_source_id_(std::move(audit_source_id)),
18 transport_(config) {}
19
21 atna_service_auditor&& other) noexcept
22 : audit_source_id_(std::move(other.audit_source_id_)),
23 transport_(std::move(other.transport_)),
24 enabled_(other.enabled_.load(std::memory_order_relaxed)),
25 events_sent_(other.events_sent_.load(std::memory_order_relaxed)),
26 events_failed_(other.events_failed_.load(std::memory_order_relaxed)) {}
27
29 atna_service_auditor&& other) noexcept {
30 if (this != &other) {
31 audit_source_id_ = std::move(other.audit_source_id_);
32 transport_ = std::move(other.transport_);
33 enabled_.store(other.enabled_.load(std::memory_order_relaxed),
34 std::memory_order_relaxed);
35 events_sent_.store(other.events_sent_.load(std::memory_order_relaxed),
36 std::memory_order_relaxed);
37 events_failed_.store(
38 other.events_failed_.load(std::memory_order_relaxed),
39 std::memory_order_relaxed);
40 }
41 return *this;
42}
43
44// =============================================================================
45// DICOM Service Audit Methods
46// =============================================================================
47
49 const std::string& source_ae,
50 const std::string& dest_ae,
51 const std::string& study_uid,
52 const std::string& patient_id,
53 bool success) {
54
55 if (!enabled_.load(std::memory_order_relaxed)) {
56 return;
57 }
58
61
64 source_ae, // source AE
65 "", // source IP (not available at service level)
66 dest_ae, // destination AE
67 "", // destination IP (not available at service level)
68 study_uid,
69 patient_id,
70 true, // is_import (SCP receiving)
71 outcome);
72
73 send_audit(msg);
74}
75
77 const std::string& calling_ae,
78 const std::string& called_ae,
79 const std::string& query_level,
80 bool success) {
81
82 if (!enabled_.load(std::memory_order_relaxed)) {
83 return;
84 }
85
88
89 // Use query level as query_data for the audit trail
92 calling_ae,
93 "", // user IP (not available at service level)
94 "QueryLevel=" + query_level + " CalledAE=" + called_ae,
95 "", // patient_id (not available from query keys here)
96 outcome);
97
98 send_audit(msg);
99}
100
102 const std::string& user_id,
103 bool is_login,
104 bool success) {
105
106 if (!enabled_.load(std::memory_order_relaxed)) {
107 return;
108 }
109
110 auto outcome = success ? atna_event_outcome::success
112
115 user_id,
116 "", // user IP (not available at this level)
117 is_login,
118 outcome);
119
120 send_audit(msg);
121}
122
124 const std::string& user_id,
125 const std::string& alert_description) {
126
127 if (!enabled_.load(std::memory_order_relaxed)) {
128 return;
129 }
130
133 user_id,
134 "", // user IP (not available at this level)
135 alert_description);
136
137 send_audit(msg);
138}
139
140// =============================================================================
141// IHE XDS-I.b Transaction Audit Methods
142// =============================================================================
143
144namespace {
145
148const atna_coded_value xds_event_type_iti_41{
149 "ITI-41", "IHE Transactions", "Provide and Register Document Set-b"};
150const atna_coded_value xds_event_type_rad_68{
151 "RAD-68", "IHE Transactions", "Provide and Register Imaging Document Set"};
152const atna_coded_value xds_event_type_iti_43{
153 "ITI-43", "IHE Transactions", "Retrieve Document Set"};
154const atna_coded_value xds_event_type_rad_69{
155 "RAD-69", "IHE Transactions", "Retrieve Imaging Document Set"};
156
158const atna_coded_value sop_instance_uid_type{
159 "110181", "DCM", "SOP Instance UID"};
160
162void append_sop_instance_objects(
163 atna_audit_message& msg,
164 const std::vector<std::string>& sop_instance_uids) {
165 for (const auto& uid : sop_instance_uids) {
166 if (uid.empty()) continue;
167 atna_participant_object obj;
168 obj.object_type = atna_object_type::system_object;
169 obj.object_role = atna_object_role::report;
170 obj.object_id_type_code = sop_instance_uid_type;
171 obj.object_id = uid;
172 msg.participant_objects.push_back(std::move(obj));
173 }
174}
175
176} // namespace
177
179 xds_source_transaction transaction,
180 const std::string& source_ae,
181 const std::string& dest_ae,
182 const std::string& study_uid,
183 const std::string& patient_id,
184 const std::vector<std::string>& sop_instance_uids,
185 bool success) {
186
187 if (!enabled_.load(std::memory_order_relaxed)) {
188 return;
189 }
190
191 auto outcome = success ? atna_event_outcome::success
193
194 // XDS-I.b Provide and Register is an Export event (DCM 110106) —
195 // the local actor is sending a document set to a remote registry.
198 source_ae, // user (the local source actor)
199 "", // user IP (not available at service level)
200 dest_ae, // destination identifier (remote registry)
201 study_uid,
202 patient_id,
203 outcome);
204
205 // Tag the event with the specific IHE transaction code so consumers
206 // of the audit log can differentiate ITI-41 from RAD-68.
207 msg.event_type_codes.push_back(
208 transaction == xds_source_transaction::iti_41
209 ? xds_event_type_iti_41
210 : xds_event_type_rad_68);
211
212 // Add SOP Instance UID references if provided (required for RAD-68,
213 // optional but recommended for ITI-41).
214 append_sop_instance_objects(msg, sop_instance_uids);
215
216 send_audit(msg);
217}
218
220 xds_consumer_transaction transaction,
221 const std::string& source_ae,
222 const std::string& dest_ae,
223 const std::string& study_uid,
224 const std::string& patient_id,
225 const std::vector<std::string>& sop_instance_uids,
226 bool success) {
227
228 if (!enabled_.load(std::memory_order_relaxed)) {
229 return;
230 }
231
232 auto outcome = success ? atna_event_outcome::success
234
235 // XDS-I.b Retrieve Document Set is an Import event
236 // (DICOM Instances Transferred with is_import=true) — the local
237 // actor is receiving a document set from a remote repository.
240 source_ae,
241 "", // source IP (not available at service level)
242 dest_ae,
243 "", // destination IP (not available at service level)
244 study_uid,
245 patient_id,
246 true, // is_import (consumer is receiving)
247 outcome);
248
249 // Tag the event with the specific IHE transaction code so consumers
250 // of the audit log can differentiate ITI-43 from RAD-69.
251 msg.event_type_codes.push_back(
253 ? xds_event_type_iti_43
254 : xds_event_type_rad_69);
255
256 // Add SOP Instance UID references if provided.
257 append_sop_instance_objects(msg, sop_instance_uids);
258
259 send_audit(msg);
260}
261
262// =============================================================================
263// Enable / Disable
264// =============================================================================
265
266void atna_service_auditor::set_enabled(bool enabled) noexcept {
267 enabled_.store(enabled, std::memory_order_relaxed);
268}
269
270bool atna_service_auditor::is_enabled() const noexcept {
271 return enabled_.load(std::memory_order_relaxed);
272}
273
274// =============================================================================
275// Statistics
276// =============================================================================
277
278size_t atna_service_auditor::events_sent() const noexcept {
279 return events_sent_.load(std::memory_order_relaxed);
280}
281
282size_t atna_service_auditor::events_failed() const noexcept {
283 return events_failed_.load(std::memory_order_relaxed);
284}
285
287 events_sent_.store(0, std::memory_order_relaxed);
288 events_failed_.store(0, std::memory_order_relaxed);
289}
290
291// =============================================================================
292// Configuration Access
293// =============================================================================
294
295const std::string& atna_service_auditor::audit_source_id() const noexcept {
296 return audit_source_id_;
297}
298
300 return transport_;
301}
302
304 const atna_audit_message& message) {
305 if (!enabled_.load(std::memory_order_relaxed)) {
306 return;
307 }
308 send_audit(message);
309}
310
311// =============================================================================
312// Private Helpers
313// =============================================================================
314
316 auto xml = atna_audit_logger::to_xml(message);
317 auto result = transport_.send(xml);
318
319 if (result.is_ok()) {
320 events_sent_.fetch_add(1, std::memory_order_relaxed);
321 } else {
322 events_failed_.fetch_add(1, std::memory_order_relaxed);
323 }
324}
325
326} // namespace kcenon::pacs::security
High-level facade for ATNA audit logging in DICOM services.
static atna_audit_message build_dicom_instances_transferred(const std::string &source_id, const std::string &source_ae, const std::string &source_ip, const std::string &dest_ae, const std::string &dest_ip, const std::string &study_uid, const std::string &patient_id="", bool is_import=true, atna_event_outcome outcome=atna_event_outcome::success)
Build DICOM Instances Transferred audit message (C-STORE, C-MOVE)
static std::string to_xml(const atna_audit_message &message)
Serialize an audit message to RFC 3881 XML.
static atna_audit_message build_export(const std::string &source_id, const std::string &user_id, const std::string &user_ip, const std::string &dest_id, const std::string &study_uid, const std::string &patient_id="", atna_event_outcome outcome=atna_event_outcome::success)
Build Export audit message (media/network export)
static atna_audit_message build_security_alert(const std::string &source_id, const std::string &user_id, const std::string &user_ip, const std::string &alert_description, atna_event_outcome outcome=atna_event_outcome::serious_failure)
Build Security Alert audit message.
static atna_audit_message build_user_authentication(const std::string &source_id, const std::string &user_id, const std::string &user_ip, bool is_login, atna_event_outcome outcome=atna_event_outcome::success)
Build User Authentication audit message (login/logout)
static atna_audit_message build_query(const std::string &source_id, const std::string &user_id, const std::string &user_ip, const std::string &query_data, const std::string &patient_id="", atna_event_outcome outcome=atna_event_outcome::success)
Build Query audit message (C-FIND, QIDO-RS)
High-level facade for emitting ATNA audit events from DICOM services.
xds_consumer_transaction
XDS-I.b consumer-side transaction identifier.
size_t events_failed() const noexcept
Get the number of audit event send failures.
void audit_authentication(const std::string &user_id, bool is_login, bool success)
Audit a User Authentication event.
void set_enabled(bool enabled) noexcept
Enable or disable audit event emission.
virtual void audit_xds_retrieve(xds_consumer_transaction transaction, const std::string &source_ae, const std::string &dest_ae, const std::string &study_uid, const std::string &patient_id, const std::vector< std::string > &sop_instance_uids, bool success)
Audit an XDS-I.b Retrieve Document Set transaction.
size_t events_sent() const noexcept
Get the number of audit events successfully sent.
xds_source_transaction
XDS-I.b source-side transaction identifier.
@ iti_41
ITI-41 Provide and Register Document Set-b.
std::atomic< bool > enabled_
Whether audit is enabled.
bool is_enabled() const noexcept
Check if audit event emission is enabled.
std::string audit_source_id_
Audit source identifier (e.g., "PACS_SYSTEM_01")
void audit_query(const std::string &calling_ae, const std::string &called_ae, const std::string &query_level, bool success)
Audit a C-FIND (Query) event.
void send_audit(const atna_audit_message &message)
Send an audit message via syslog transport.
const atna_syslog_transport & transport() const noexcept
Get the underlying transport (for advanced use)
atna_service_auditor(const syslog_transport_config &config, std::string audit_source_id)
Construct an auditor with syslog transport configuration.
virtual void audit_xds_provide_and_register(xds_source_transaction transaction, const std::string &source_ae, const std::string &dest_ae, const std::string &study_uid, const std::string &patient_id, const std::vector< std::string > &sop_instance_uids, bool success)
Audit an XDS-I.b Provide and Register Document Set transaction.
void submit_audit_message(const atna_audit_message &message)
Emit a pre-built RFC 3881 audit message.
void audit_security_alert(const std::string &user_id, const std::string &alert_description)
Audit a Security Alert event (e.g., access denied)
const std::string & audit_source_id() const noexcept
Get the audit source identifier.
std::atomic< size_t > events_sent_
Statistics.
atna_service_auditor & operator=(const atna_service_auditor &)=delete
void reset_statistics() noexcept
Reset statistics counters.
atna_syslog_transport transport_
Syslog transport for sending audit messages.
void audit_instance_stored(const std::string &source_ae, const std::string &dest_ae, const std::string &study_uid, const std::string &patient_id, bool success)
Audit a C-STORE (DICOM Instances Transferred) event.
Sends ATNA audit messages via Syslog protocol.
kcenon::pacs::VoidResult send(const std::string &xml_message)
Send an RFC 3881 XML audit message via Syslog.
A coded value with code, code system name, and display name.
Configuration for the Syslog transport.
std::string_view uid