4 * Audit Log Tamper Report.
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
;
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")]);
31 if (!CsrfUtils
::verifyCsrfToken($_GET["csrf_token_form"])) {
32 CsrfUtils
::csrfNotVerified();
40 <title
><?php
echo xlt("Audit Log Tamper Report"); ?
></title
>
42 <?php Header
::setupHeader('datetime-picker'); ?
>
50 border
-collapse
: collapse
;
53 background
-color
: #cccccc;
54 cursor
: pointer
; cursor
: hand
;
61 background
-color
: var(--white
);
62 border
-bottom
: 1px solid
#808080;
68 background
-color
: #336699;
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;
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
;
101 <body
class="body_top">
102 <font
class="title"><?php
echo xlt('Audit Log Tamper Report'); ?
></font
>
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>";
119 if (!empty($_GET["form_patient"])) {
120 $form_patient = $_GET['form_patient'];
125 $form_user = $_GET['form_user'] ??
null;
126 $form_pid = $_GET['form_pid'] ??
null;
127 if (empty($form_patient)) {
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()); ?>" />
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
="">
143 <span
class="text"><?php
echo xlt('Start Date'); ?
>: </span
>
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'); ?>" />
148 <span
class="text"><?php
echo xlt('End Date'); ?
>: </span
>
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'); ?>" />
154  
; 
;<span
class='text'><?php
echo xlt('Patient'); ?
>: </span
>
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); ?>' />
163 <span
class='text'><?php
echo xlt('Include Checksum'); ?
>: </span
>
167 $check_sum = isset($_GET['check_sum']);
169 <input type
="checkbox" name
="check_sum" <?php
echo ($check_sum) ?
"checked" : ""; ?
>>
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
>
180 <?php
if ($start_date && $end_date && $err_message != 1) { ?
>
182 <span
class="text" id
="display_tamper" style
="display:none;"><?php
echo xlt('Following rows in the audit log have been tampered'); ?
></span
>
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
>
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) ?>">
206 if ($eventname != "" && $type_event != "") {
207 $getevent = $eventname . "-" . $type_event;
210 if (($eventname == "") && ($type_event != "")) {
211 $tevent = $type_event;
212 } elseif ($type_event == "" && $eventname != "") {
214 } elseif ($eventname == "") {
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>";
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
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']);
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']);
256 if ($checkSumOld != $checkSumNew) {
260 if (!empty($checkSumOldApi) && ($checkSumOldApi != $checkSumNewApi)) {
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'];
280 $commentEncrStatus = "No";
282 if (!empty($iter['version'])) {
283 $encryptVersion = $iter['version'];
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));
296 $trans_comments = xl("Unable to decrypt these comments since decryption failed.");
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));
308 $trans_comments = xl("Unable to decrypt these comments since decryption failed.");
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"])));
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"])));
325 $trans_comments = xl("Unable to decrypt these comments since the PHP mycrypt module is not installed.");
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
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
>
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>';
369 if (count($dispArr) == 0) {?
>
370 <TR
class="oneresult">
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
>
381 <script
>$
('#display_tamper').css('display', 'block');</script
>
392 // jQuery stuff to make the page a little easier to use
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 ?>