fix: quick fix to enforce support of x509 database connection on install (#6157)
[openemr.git] / interface / reports / audit_log_tamper_report.php
blob9b1d35c3c596e3f08f69913fb5361390237d5a25
1 <?php
3 /**
4 * Audit Log Tamper Report.
6 * @package OpenEMR
7 * @link http://www.open-emr.org
8 * @author Anil N <aniln@ensoftek.com>
9 * @author Brady Miller <brady.g.miller@gmail.com>
10 * @copyright Copyright (c) 2014 Ensoftek
11 * @copyright Copyright (c) 2017-2018 Brady Miller <brady.g.miller@gmail.com>
12 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
15 require_once("../globals.php");
17 use OpenEMR\Common\Acl\AclMain;
18 use OpenEMR\Common\Crypto\CryptoGen;
19 use OpenEMR\Common\Csrf\CsrfUtils;
20 use OpenEMR\Common\Logging\EventAuditLogger;
21 use OpenEMR\Common\Twig\TwigContainer;
22 use OpenEMR\Core\Header;
24 // Control access
25 if (!AclMain::aclCheckCore('admin', 'super')) {
26 echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Audit Log Tamper Report")]);
27 exit;
30 if (!empty($_GET)) {
31 if (!CsrfUtils::verifyCsrfToken($_GET["csrf_token_form"])) {
32 CsrfUtils::csrfNotVerified();
37 <html>
38 <head>
40 <title><?php echo xlt("Audit Log Tamper Report"); ?></title>
42 <?php Header::setupHeader('datetime-picker'); ?>
44 <style>
45 #logview {
46 width: 100%;
48 #logview table {
49 width:100%;
50 border-collapse: collapse;
52 #logview th {
53 background-color: #cccccc;
54 cursor: pointer; cursor: hand;
55 padding: 5px 5px;
56 align: left;
57 text-align: left;
60 #logview td {
61 background-color: var(--white);
62 border-bottom: 1px solid #808080;
63 cursor: default;
64 padding: 5px 5px;
65 vertical-align: top;
67 .highlight {
68 background-color: #336699;
69 color: #336699;
71 .tamperColor{
72 color:red;
74 </style>
75 <script>
76 //function to disable the event type field if the event name is disclosure
77 function eventTypeChange(eventname)
79 if (eventname == "disclosure") {
80 document.theform.type_event.disabled = true;
82 else {
83 document.theform.type_event.disabled = false;
87 // VicarePlus :: This invokes the find-patient popup.
88 function sel_patient() {
89 dlgopen('../main/calendar/find_patient_popup.php?pflag=0', '_blank', 500, 400);
92 // VicarePlus :: This is for callback by the find-patient popup.
93 function setpatient(pid, lname, fname, dob) {
94 var f = document.theform;
95 f.form_patient.value = lname + ', ' + fname;
96 f.form_pid.value = pid;
99 </script>
100 </head>
101 <body class="body_top">
102 <font class="title"><?php echo xlt('Audit Log Tamper Report'); ?></font>
103 <br />
104 <?php
105 $err_message = 0;
107 $start_date = (!empty($_GET["start_date"])) ? DateTimeToYYYYMMDDHHMMSS($_GET["start_date"]) : date("Y-m-d") . " 00:00:00";
108 $end_date = (!empty($_GET["end_date"])) ? DateTimeToYYYYMMDDHHMMSS($_GET["end_date"]) : date("Y-m-d") . " 23:59:59";
110 * Start date should not be greater than end date - Date Validation
112 if ($start_date > $end_date) {
113 echo "<table><tr class='alert'><td colspan=7>";
114 echo xlt('Start Date should not be greater than End Date');
115 echo "</td></tr></table>";
116 $err_message = 1;
119 if (!empty($_GET["form_patient"])) {
120 $form_patient = $_GET['form_patient'];
124 <?php
125 $form_user = $_GET['form_user'] ?? null;
126 $form_pid = $_GET['form_pid'] ?? null;
127 if (empty($form_patient)) {
128 $form_pid = null;
132 <br />
133 <FORM METHOD="GET" name="theform" id="theform" onSubmit='top.restoreSession()'>
134 <input type="hidden" name="csrf_token_form" value="<?php echo attr(CsrfUtils::collectCsrfToken()); ?>" />
135 <?php
137 $sortby = $_GET['sortby'] ?? null;
139 <input type="hidden" name="sortby" id="sortby" value="<?php echo attr($sortby); ?>">
140 <input type=hidden name=csum value="">
141 <table>
142 <tr><td>
143 <span class="text"><?php echo xlt('Start Date'); ?>: </span>
144 </td><td>
145 <input type="text" size="18" class="datetimepicker" name="start_date" id="start_date" value="<?php echo attr(oeFormatDateTime($start_date, 0, true)); ?>" title="<?php echo xla('Start date'); ?>" />
146 </td>
147 <td>
148 <span class="text"><?php echo xlt('End Date'); ?>: </span>
149 </td><td>
150 <input type="text" size="18" class="datetimepicker" name="end_date" id="end_date" value="<?php echo attr(oeFormatDateTime($end_date, 0, true)); ?>" title="<?php echo xla('End date'); ?>" />
151 </td>
153 <td>
154 &nbsp;&nbsp;<span class='text'><?php echo xlt('Patient'); ?>: </span>
155 </td>
156 <td>
157 <input type='text' size='20' name='form_patient' style='width:100%;cursor:pointer;cursor:hand' value='<?php echo (!empty($form_patient)) ? attr($form_patient) : '' ?>' placeholder= '<?php echo xla('Click To Select'); ?>' onclick='sel_patient()' title='<?php echo xla('Click to select patient'); ?>' />
158 <input type='hidden' name='form_pid' value='<?php echo attr($form_pid); ?>' />
159 </td>
160 </tr>
162 <tr><td>
163 <span class='text'><?php echo xlt('Include Checksum'); ?>: </span>
164 </td><td>
165 <?php
167 $check_sum = isset($_GET['check_sum']);
169 <input type="checkbox" name="check_sum" <?php echo ($check_sum) ? "checked" : ""; ?>>
170 </td>
171 <td>
172 <input type=hidden name="event" value="<?php echo attr($event ?? '') ; ?>">
173 <a href="javascript:document.theform.submit();" class='link_submit'>[<?php echo xlt('Refresh'); ?>]</a>
174 </td>
175 </tr>
176 </table>
177 </FORM>
180 <?php if ($start_date && $end_date && $err_message != 1) { ?>
181 <div id="logview">
182 <span class="text" id="display_tamper" style="display:none;"><?php echo xlt('Following rows in the audit log have been tampered'); ?></span>
183 <table>
184 <tr>
185 <th id="sortby_date" class="text" title="<?php echo xla('Sort by date/time'); ?>"><?php echo xlt('Log Types'); ?></th>
186 <th id="sortby_date" class="text" title="<?php echo xla('Sort by date/time'); ?>"><?php echo xlt('ID'); ?></th>
187 <th id="sortby_date" class="text" title="<?php echo xla('Sort by date/time'); ?>"><?php echo xlt('Date'); ?></th>
188 <th id="sortby_user" class="text" title="<?php echo xla('Sort by User'); ?>"><?php echo xlt('User'); ?></th>
189 <th id="sortby_pid" class="text" title="<?php echo xla('Sort by PatientID'); ?>"><?php echo xlt('PatientID'); ?></th>
190 <th id="sortby_comments" class="text" title="<?php echo xla('Sort by Comments'); ?>"><?php echo xlt('Comments'); ?></th>
191 <?php if ($check_sum) {?>
192 <th id="sortby_newchecksum" class="text" title="<?php xla('Sort by New Checksum'); ?>"><?php echo xlt('Tampered Checksum'); ?></th>
193 <th id="sortby_oldchecksum" class="text" title="<?php xla('Sort by Old Checksum'); ?>"><?php echo xlt('Original Checksum'); ?></th>
194 <?php } ?>
195 </tr>
196 <?php
198 $eventname = $_GET['eventname'] ?? null;
199 $type_event = $_GET['type_event'] ?? null;
201 <input type="hidden" name="event" value="<?php echo attr($eventname) . "-" . attr($type_event) ?>">
202 <?php
203 $type_event = "";
204 $tevent = "";
205 $gev = "";
206 if ($eventname != "" && $type_event != "") {
207 $getevent = $eventname . "-" . $type_event;
210 if (($eventname == "") && ($type_event != "")) {
211 $tevent = $type_event;
212 } elseif ($type_event == "" && $eventname != "") {
213 $gev = $eventname;
214 } elseif ($eventname == "") {
215 $gev = "";
216 } else {
217 $gev = $getevent;
220 $dispArr = array();
221 $icnt = 1;
222 if ($ret = EventAuditLogger::instance()->getEvents(array('sdate' => $start_date,'edate' => $end_date, 'user' => $form_user, 'patient' => $form_pid, 'sortby' => ($_GET['sortby'] ?? null), 'levent' => $gev, 'tevent' => $tevent))) {
223 // Set up crypto object (object will increase performance since caches used keys)
224 $cryptoGen = new CryptoGen();
226 while ($iter = sqlFetchArray($ret)) {
227 if (empty($iter["id"])) {
228 // Log item is missing; it has been deleted.
229 echo "<tr><td colspan='6' class='text tamperColor''>" . xlt("The log entry with following id has been deleted") . ": " . $iter['log_id_hash'] . "</td></tr>";
230 continue;
233 //translate comments
234 $patterns = array ('/^success/','/^failure/','/ encounter/');
235 $replace = array ( xl('success'), xl('failure'), xl('encounter', '', ' '));
237 $checkSumOld = $iter['checksum'];
238 if (empty($checkSumOld)) {
239 // no checksum, so skip
240 continue;
241 } elseif (strlen($checkSumOld) < 50) {
242 // for backward compatibility (for log checksums created in the sha1 days)
243 $checkSumNew = sha1($iter['date'] . $iter['event'] . $iter['user'] . $iter['groupname'] . $iter['comments'] . $iter['patient_id'] . $iter['success'] . $iter['checksum'] . $iter['crt_user']);
244 } else {
245 $checkSumNew = hash('sha3-512', $iter['date'] . $iter['event'] . $iter['category'] . $iter['user'] . $iter['groupname'] . $iter['comments'] . $iter['user_notes'] . $iter['patient_id'] . $iter['success'] . $iter['crt_user'] . $iter['log_from'] . $iter['menu_item_id'] . $iter['ccda_doc_id']);
248 $checkSumOldApi = $iter['checksum_api'];
249 if (!empty($checkSumOldApi)) {
250 $checkSumNewApi = hash('sha3-512', $iter['log_id_api'] . $iter['user_id'] . $iter['patient_id_api'] . $iter['ip_address'] . $iter['method'] . $iter['request'] . $iter['request_url'] . $iter['request_body'] . $iter['response'] . $iter['created_time']);
253 $dispCheck = false;
254 $mainFail = false;
255 $apiFail = false;
256 if ($checkSumOld != $checkSumNew) {
257 $dispCheck = true;
258 $mainFail = true;
260 if (!empty($checkSumOldApi) && ($checkSumOldApi != $checkSumNewApi)) {
261 $dispCheck = true;
262 $apiFail = true;
264 if (!$dispCheck) {
265 continue;
268 $logType = '';
269 if (!empty($mainFail) && !empty($apiFail)) {
270 $logType = xl('Main and API');
271 } elseif (!empty($mainFail)) {
272 $logType = xl('Main');
273 } else { // !empty($apiFail)
274 $logType = xl('API');
277 if (!empty($iter['encrypt'])) {
278 $commentEncrStatus = $iter['encrypt'];
279 } else {
280 $commentEncrStatus = "No";
282 if (!empty($iter['version'])) {
283 $encryptVersion = $iter['version'];
284 } else {
285 $encryptVersion = 0;
288 if ($commentEncrStatus == "Yes") {
289 if ($encryptVersion >= 3) {
290 // Use new openssl method
291 if (extension_loaded('openssl')) {
292 $trans_comments = $cryptoGen->decryptStandard($iter["comments"]);
293 if ($trans_comments !== false) {
294 $trans_comments = preg_replace($patterns, $replace, trim($trans_comments));
295 } else {
296 $trans_comments = xl("Unable to decrypt these comments since decryption failed.");
298 } else {
299 $trans_comments = xl("Unable to decrypt these comments since the PHP openssl module is not installed.");
301 } elseif ($encryptVersion == 2) {
302 // Use new openssl method
303 if (extension_loaded('openssl')) {
304 $trans_comments = $cryptoGen->aes256DecryptTwo($iter["comments"]);
305 if ($trans_comments !== false) {
306 $trans_comments = preg_replace($patterns, $replace, trim($trans_comments));
307 } else {
308 $trans_comments = xl("Unable to decrypt these comments since decryption failed.");
310 } else {
311 $trans_comments = xl("Unable to decrypt these comments since the PHP openssl module is not installed.");
313 } elseif ($encryptVersion == 1) {
314 // Use new openssl method
315 if (extension_loaded('openssl')) {
316 $trans_comments = preg_replace($patterns, $replace, trim($cryptoGen->aes256DecryptOne($iter["comments"])));
317 } else {
318 $trans_comments = xl("Unable to decrypt these comments since the PHP openssl module is not installed.");
320 } else { //$encryptVersion == 0
321 // Use old mcrypt method
322 if (extension_loaded('mcrypt')) {
323 $trans_comments = preg_replace($patterns, $replace, trim($cryptoGen->aes256Decrypt_mycrypt($iter["comments"])));
324 } else {
325 $trans_comments = xl("Unable to decrypt these comments since the PHP mycrypt module is not installed.");
328 } else {
329 // base64 decode if applicable (note the $encryptVersion is a misnomer here, we have added in base64 encoding
330 // of comments in OpenEMR 6.0.0 and greater when the comments are not encrypted since they hold binary (uuid) elements)
331 if ($encryptVersion >= 4) {
332 $iter["comments"] = base64_decode($iter["comments"]);
334 $trans_comments = preg_replace($patterns, $replace, trim($iter["comments"]));
337 //Alter Checksum value records only display here
338 if ($dispCheck) {
339 $dispArr[] = $icnt++;
341 <TR class="oneresult">
342 <TD class="text tamperColor"><?php echo text($logType); ?></TD>
343 <TD class="text tamperColor"><?php echo text($iter["id"]); ?></TD>
344 <TD class="text tamperColor"><?php echo text(oeFormatDateTime($iter["date"], "global", true)); ?></TD>
345 <TD class="text tamperColor"><?php echo text($iter["user"]); ?></TD>
346 <TD class="text tamperColor"><?php echo text($iter["patient_id"]);?></TD>
347 <?php // Using mb_convert_encoding to change binary stuff (uuid) to just be '?' characters ?>
348 <TD class="text tamperColor"><?php echo text(mb_convert_encoding($trans_comments, 'UTF-8', 'UTF-8'));?></TD>
349 <?php
350 if ($check_sum) {
351 if (!empty($mainFail) && !empty($apiFail)) {
352 echo '<TD class="text tamperColor">' . text($checkSumNew) . '<br>' . text($checkSumNewApi) . '</TD>';
353 echo '<TD class="text tamperColor">' . text($checkSumOld) . '<br>' . text($checkSumOldApi) . '</TD>';
354 } elseif (!empty($mainFail)) {
355 echo '<TD class="text tamperColor">' . text($checkSumNew) . '</TD>';
356 echo '<TD class="text tamperColor">' . text($checkSumOld) . '</TD>';
357 } else { // !empty($apiFail)
358 echo '<TD class="text tamperColor">' . text($checkSumNewApi) . '</TD>';
359 echo '<TD class="text tamperColor">' . text($checkSumOldApi) . '</TD>';
363 </TR>
364 <?php
369 if (count($dispArr) == 0) {?>
370 <TR class="oneresult">
371 <?php
372 $colspan = 4;
373 if ($check_sum) {
374 $colspan = 6;
377 <TD class="text" colspan="<?php echo attr($colspan);?>" align="center"><?php echo xlt('No audit log tampering detected in the selected date range.'); ?></TD>
378 </TR>
379 <?php
380 } else {?>
381 <script>$('#display_tamper').css('display', 'block');</script>
382 <?php
386 </table>
387 </div>
388 <?php } ?>
389 </body>
390 <script>
392 // jQuery stuff to make the page a little easier to use
393 $(function () {
394 // funny thing here... good learning experience
395 // the TR has TD children which have their own background and text color
396 // toggling the TR color doesn't change the TD color
397 // so we need to change all the TR's children (the TD's) just as we did the TR
398 // thus we have two calls to toggleClass:
399 // 1 - for the parent (the TR)
400 // 2 - for each of the children (the TDs)
401 $(".oneresult").mouseover(function() { $(this).toggleClass("highlight"); $(this).children().toggleClass("highlight"); });
402 $(".oneresult").mouseout(function() { $(this).toggleClass("highlight"); $(this).children().toggleClass("highlight"); });
404 // click-able column headers to sort the list
405 $("#sortby_date").click(function() { $("#sortby").val("date"); $("#theform").submit(); });
406 $("#sortby_event").click(function() { $("#sortby").val("event"); $("#theform").submit(); });
407 $("#sortby_user").click(function() { $("#sortby").val("user"); $("#theform").submit(); });
408 $("#sortby_cuser").click(function() { $("#sortby").val("user"); $("#theform").submit(); });
409 $("#sortby_group").click(function() { $("#sortby").val("groupname"); $("#theform").submit(); });
410 $("#sortby_pid").click(function() { $("#sortby").val("patient_id"); $("#theform").submit(); });
411 $("#sortby_success").click(function() { $("#sortby").val("success"); $("#theform").submit(); });
412 $("#sortby_comments").click(function() { $("#sortby").val("comments"); $("#theform").submit(); });
413 $("#sortby_oldchecksum").click(function() { $("#sortby").val("checksum"); $("#theform").submit(); });
414 $("#sortby_newchecksum").click(function() { $("#sortby").val("checksum"); $("#theform").submit(); });
416 $('.datetimepicker').datetimepicker({
417 <?php $datetimepicker_timepicker = true; ?>
418 <?php $datetimepicker_showseconds = true; ?>
419 <?php $datetimepicker_formatInput = true; ?>
420 <?php require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?>
421 <?php // can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>
425 </script>
427 </html>