3 function newEvent($event, $user, $groupname, $success, $comments = "", $patient_id = null, $log_from = 'open-emr', $menu_item = 'dashboard', $ccda_doc_id = 0)
5 $adodb = $GLOBALS['adodb']['db'];
6 $crt_user=isset($_SERVER['SSL_CLIENT_S_DN_CN']) ? $_SERVER['SSL_CLIENT_S_DN_CN'] : null;
9 // Special case delete for lists table
10 if ($event == 'delete') {
11 $category = eventCategoryFinder($comments, $event, '');
14 // deal with comments encryption, if turned on
15 $encrypt_comment = 'No';
16 if (!empty($comments)) {
17 if ($GLOBALS["enable_auditlog_encryption"]) {
18 $comments = aes256Encrypt($comments);
19 $encrypt_comment = 'Yes';
23 if ($log_from == 'patient-portal') {
24 $sqlMenuItems = "SELECT * FROM patient_portal_menu";
26 $resMenuItems = sqlStatement($sqlMenuItems);
27 for ($iter=0; $rowMenuItem=sqlFetchArray($resMenuItems); $iter++) {
28 $menuItems[$rowMenuItem['patient_portal_menu_id']] = $rowMenuItem['menu_name'];
31 $menuItemId = array_search($menu_item, $menuItems);
32 $sql = "insert into log ( date, event,category, user, patient_id, groupname, success, comments,
33 log_from, menu_item_id, crt_user, ccda_doc_id) values ( NOW(), ?,'Patient Portal', ?, ?, ?, ?, ?, ?, ?,?, ?)";
34 $ret = sqlStatementNoLog($sql, array($event, $user, $patient_id, $groupname, $success, $comments,$log_from, $menuItemId,$crt_user, $ccda_doc_id));
36 /* More details added to the log */
37 $sql = "insert into log ( date, event,category, user, groupname, success, comments, crt_user, patient_id) " .
38 "values ( NOW(), " . $adodb->qstr($event) . ",". $adodb->qstr($category) . "," . $adodb->qstr($user) .
39 "," . $adodb->qstr($groupname) . "," . $adodb->qstr($success) . "," .
40 $adodb->qstr($comments) ."," .
41 $adodb->qstr($crt_user) ."," . $adodb->qstr($patient_id). ")";
43 $ret = sqlInsertClean_audit($sql);
46 // Send item to log_comment_encrypt for comment encyption tracking
47 $last_log_id = $GLOBALS['adodb']['db']->Insert_ID();
48 $encryptLogQry = "INSERT INTO log_comment_encrypt (log_id, encrypt, checksum) ".
50 $adodb->qstr($last_log_id) . "," .
51 $adodb->qstr($encrypt_comment) . "," .
53 sqlInsertClean_audit($encryptLogQry);
55 if (($patient_id=="NULL") || ($patient_id==null)) {
59 send_atna_audit_msg($user, $groupname, $event, $patient_id, $success, $comments);
62 function getEventByDate($date, $user = "", $cols = "DISTINCT date, event, user, groupname, patient_id, success, comments, checksum")
64 $sql = "SELECT $cols FROM log WHERE date >= '$date 00:00:00' AND date <= '$date 23:59:59'";
66 $sql .= " AND user LIKE '$user'";
69 $sql .= " ORDER BY date DESC LIMIT 5000";
70 $res = sqlStatement($sql);
71 for ($iter=0; $row=sqlFetchArray($res); $iter++) {
79 * Get records from the LOG and Extended_Log table
80 * using the optional parameters:
81 * date : a specific date (defaults to today)
82 * user : a specific user (defaults to none)
83 * cols : gather specific columns (defaults to date,event,user,groupname,comments)
84 * sortby : sort the results by (defaults to none)
88 function getEvents($params)
90 // parse the parameters
91 $cols = "DISTINCT date, event, category, user, groupname, patient_id, success, comments,checksum,crt_user, id ";
92 if (isset($params['cols']) && $params['cols'] != "") {
93 $cols = $params['cols'];
96 $date1 = date("Y-m-d H:i:s", time());
97 if (isset($params['sdate']) && $params['sdate'] != "") {
98 $date1= $params['sdate'];
101 $date2 = date("Y-m-d H:i:s", time());
102 if (isset($params['edate']) && $params['edate'] != "") {
103 $date2= $params['edate'];
107 if (isset($params['user']) && $params['user'] != "") {
108 $user= $params['user'];
111 //VicarePlus :: For Generating log with patient id.
113 if (isset($params['patient']) && $params['patient'] != "") {
114 $patient= $params['patient'];
118 if (isset($params['sortby']) && $params['sortby'] != "") {
119 $sortby = $params['sortby'];
123 if (isset($params['levent']) && $params['levent'] != "") {
124 $levent = $params['levent'];
128 if (isset($params['tevent']) && $params['tevent'] != "") {
129 $tevent = $params['tevent'];
133 if (isset($params['direction']) && $params['direction'] != "") {
134 $direction = $params['direction'];
138 if (isset($params['event']) && $params['event'] != "") {
139 $event = $params['event'];
143 if ($sortby == "comments") {
144 $sortby = "description";
147 if ($sortby == "groupname") {
148 $sortby = ""; //VicarePlus :: since there is no groupname in extended_log
151 if ($sortby == "success") {
152 $sortby = ""; //VicarePlus :: since there is no success field in extended_log
155 if ($sortby == "checksum") {
156 $sortby = ""; //VicarePlus :: since there is no checksum field in extended_log
159 if ($sortby == "category") {
160 $sortby = ""; //VicarePlus :: since there is no category field in extended_log
163 $columns = "DISTINCT date, event, user, recipient,patient_id,description";
164 $sql = "SELECT $columns FROM extended_log WHERE date >= '$date1' AND date <= '$date2'";
166 $sql .= " AND user LIKE '$user'";
169 if ($patient != "") {
170 $sql .= " AND patient_id LIKE '$patient'";
174 $sql .= " AND event LIKE '$levent%'";
178 $sql .= " ORDER BY ".$sortby." DESC"; // descending order
181 $sql .= " LIMIT 5000";
184 $sql = "SELECT $cols FROM log WHERE date >= '$date1' AND date <= '$date2'";
186 $sql .= " AND user LIKE '$user'";
189 if ($patient != "") {
190 $sql .= " AND patient_id LIKE '$patient'";
194 $sql .= " AND event LIKE '$levent%'";
198 $sql .= " AND event LIKE '%$tevent'";
202 $sql .= " ORDER BY ".$sortby." ".escape_sort_order($direction); // descending order
205 $sql .= " LIMIT 5000";
208 $res = sqlStatement($sql);
209 for ($iter=0; $row=sqlFetchArray($res); $iter++) {
216 /* Given an SQL insert/update that was just performeds:
217 * - Find the table and primary id of the row that was created/modified
218 * - Calculate the SHA1 checksum of that row (with all the
219 * column values concatenated together).
220 * - Return the SHA1 checksum as a 40 char hex string.
221 * If this is not an insert/update query, return "".
222 * If multiple rows were modified, return "".
223 * If we're unable to determine the row modified, return "".
225 * TODO: May need to incorporate the binded stuff (still analyzing)
228 function sql_checksum_of_modified_row($statement)
233 $tokens = preg_split("/[\s,(\'\"]+/", $statement);
234 /* Identifying the id for insert/replace statements for calculating the checksum */
235 if ((strcasecmp($tokens[0], "INSERT")==0) || (strcasecmp($tokens[0], "REPLACE")==0)) {
237 $rid = generic_sql_insert_id();
238 /* For handling the table that doesn't have auto-increment column */
239 if ($rid === 0 || $rid === false) {
240 if ($table == "gacl_aco_map" || $table == "gacl_aro_groups_map" || $table == "gacl_aro_map" || $table == "gacl_axo_groups_map" || $table == "gacl_axo_map") {
242 } else if ($table == "gacl_groups_aro_map" || $table == "gacl_groups_axo_map") {
248 /* To handle insert statements */
249 if ($tokens[3] == $id) {
250 for ($i=4; $i<count($tokens); $i++) {
251 if (strcasecmp($tokens[$i], "VALUES")==0) {
257 /* To handle replace statements */
258 else if (strcasecmp($tokens[3], "SET")==0) {
259 if ((strcasecmp($tokens[4], "ID")==0) || (strcasecmp($tokens[4], "`ID`")==0)) {
266 } /* Identifying the id for update statements for calculating the checksum */
267 else if (strcasecmp($tokens[0], "UPDATE")==0) {
271 $total = count($tokens);
273 /* Identifying the primary key column for the updated record */
274 if ($table == "form_physical_exam") {
276 } else if ($table == "claims") {
278 } else if ($table == "openemr_postcalendar_events") {
280 } else if ($table == "lang_languages") {
282 } else if ($table == "openemr_postcalendar_categories" || $table == "openemr_postcalendar_topics") {
284 } else if ($table == "openemr_postcalendar_limits") {
286 } else if ($table == "gacl_aco_map" || $table == "gacl_aro_groups_map" || $table == "gacl_aro_map" || $table == "gacl_axo_groups_map" || $table == "gacl_axo_map") {
288 } else if ($table == "gacl_groups_aro_map" || $table == "gacl_groups_axo_map") {
294 /* Identifying the primary key value for the updated record */
295 while ($offset < $total) {
296 /* There are 4 possible ways that the id=123 can be parsed:
304 if (($tokens[$offset] == "$id=") && ($offset + 1 < $total)) {
305 $rid = $tokens[$offset+1];
307 } /* 'id', '=', '123' */
308 else if ($tokens[$offset] == "$id" && $tokens[$offset+1] == "=" && ($offset+2 < $total)) {
309 $rid = $tokens[$offset+2];
312 else if (strpos($tokens[$offset], "$id=") === 0) {
313 $tid = substr($tokens[$offset], strlen($id)+1);
314 if (is_numeric($tid)) {
320 else if ($tokens[$offset] == "$id") {
321 $tid = substr($tokens[$offset+1], 1);
322 if (is_numeric($tid)) {
330 }//while ($offset < $total)
331 }// else if ($tokens[0] == 'update' || $tokens[0] == 'UPDATE' )
333 if ($table == "" || $rid == "") {
337 /* Framing sql statements for calculating checksum */
338 if ($table == "form_physical_exam") {
339 $sql = "select * from $table where forms_id = $rid";
340 } else if ($table == "claims") {
341 $sql = "select * from $table where patient_id = $rid";
342 } else if ($table == "openemr_postcalendar_events") {
343 $sql = "select * from $table where pc_eid = $rid";
344 } else if ($table == "lang_languages") {
345 $sql = "select * from $table where lang_id = $rid";
346 } else if ($table == "openemr_postcalendar_categories" || $table == "openemr_postcalendar_topics") {
347 $sql = "select * from $table where pc_catid = $rid";
348 } else if ($table == "openemr_postcalendar_limits") {
349 $sql = "select * from $table where pc_limitid = $rid";
350 } else if ($table == "gacl_aco_map" || $table == "gacl_aro_groups_map" || $table == "gacl_aro_map" || $table == "gacl_axo_groups_map" || $table == "gacl_axo_map") {
351 $sql = "select * from $table where acl_id = $rid";
352 } else if ($table == "gacl_groups_aro_map" || $table == "gacl_groups_axo_map") {
353 $sql = "select * from $table where group_id = $rid";
355 $sql = "select * from $table where id = $rid";
358 // When this function is working perfectly, can then shift to the
359 // sqlQueryNoLog() function.
360 $results = sqlQueryNoLogIgnoreError($sql);
362 /* Concatenating the column values for the row inserted/updated */
363 if (is_array($results)) {
364 foreach ($results as $field_name => $field) {
365 $column_values .= $field;
369 // ViCarePlus: As per NIST standard, the encryption algorithm SHA1 is used
371 //error_log("COLUMN_VALUES: ".$column_values,0);
372 return sha1($column_values);
375 /* Create an XML audit record corresponding to RFC 3881.
376 * The parameters passed are the column values (from table 'log')
377 * for a single audit record.
379 function create_rfc3881_msg($user, $group, $event, $patient_id, $outcome, $comments)
382 /* Event action codes indicate whether the event is read/write.
383 * C = create, R = read, U = update, D = delete, E = execute
385 $eventActionCode = 'E';
386 if (substr($event, -7) == "-create") {
387 $eventActionCode = 'C';
388 } else if (substr($event, -7) == "-insert") {
389 $eventActionCode = 'C';
390 } else if (substr($event, -7) == "-select") {
391 $eventActionCode = 'R';
392 } else if (substr($event, -7) == "-update") {
393 $eventActionCode = 'U';
394 } else if (substr($event, -7) == "-delete") {
395 $eventActionCode = 'D';
398 $date_obj = new DateTime();
399 $eventDateTime = $date_obj->format(DATE_ATOM);
401 /* For EventOutcomeIndicator, 0 = success and 4 = minor error */
402 $eventOutcome = ($outcome === 1) ? 0 : 4;
404 /* The choice of event codes is up to OpenEMR.
405 * We're using the same event codes as
406 * https://iheprofiles.projects.openhealthtools.org/
408 $eventIDcodeSystemName = "DCM";
410 $eventIDdisplayName = $event;
412 if (strpos($event, 'patient-record') !== false) {
413 $eventIDcode = 110110;
414 $eventIDdisplayName = 'Patient Record';
415 } else if (strpos($event, 'view') !== false) {
416 $eventIDCode = 110110;
417 $eventIDdisplayName = 'Patient Record';
418 } else if (strpos($event, 'login') !== false) {
419 $eventIDcode = 110122;
420 $eventIDdisplayName = 'Login';
421 } else if (strpos($event, 'logout') !== false) {
422 $eventIDcode = 110123;
423 $eventIDdisplayName = 'Logout';
424 } else if (strpos($event, 'scheduling') !== false) {
425 $eventIDcode = 110111;
426 $eventIDdisplayName = 'Patient Care Assignment';
427 } else if (strpos($event, 'security-administration') !== false) {
428 $eventIDcode = 110129;
429 $eventIDdisplayName = 'Security Administration';
435 /* Variables used in ActiveParticipant section, which identifies
436 * the IP address and application of the source and destination.
438 $srcUserID = $_SERVER['SERVER_NAME'] . '|OpenEMR';
439 $srcNetwork = $_SERVER['SERVER_ADDR'];
440 $destUserID = $GLOBALS['atna_audit_host'];
441 $destNetwork = $GLOBALS['atna_audit_host'];
447 $userDisplayName = 'User Identifier';
450 $patientTypeCode = "";
453 $patientDisplayName = "";
455 if ($eventIDdisplayName == 'Patient Record') {
456 $patientID = $patient_id;
457 $pattientTypeCode = 1;
460 $patientDisplayName = 'Patient Number';
463 /* Construct the XML audit message, and save to $msg */
464 $msg = '<?xml version="1.0" encoding="ASCII"?>';
465 $msg .= '<AuditMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ';
466 $msg .= 'xsi:noNamespaceSchemaLocation="healthcare-security-audit.xsd">';
468 /* Indicate the event code, text name, read/write type, and date/time */
469 $msg .= "<EventIdentification EventActionCode=\"$eventActionCode\" ";
470 $msg .= "EventDateTime=\"$eventDateTime\" ";
471 $msg .= "EventOutcomeIndicator=\"$eventOutcome\">";
472 $msg .= "<EventID code=\"eventIDcode\" displayName=\"$eventIDdisplayName\" ";
473 $msg .= "codeSystemName=\"DCM\" />";
474 $msg .= "</EventIdentification>";
476 /* Indicate the IP address and application of the source and destination */
477 $msg .= "<ActiveParticipant UserID=\"$srcUserID\" UserIsRequestor=\"true\" ";
478 $msg .= "NetworkAccessPointID=\"$srcNetwork\" NetworkAccessPointTypeCode=\"2\" >";
479 $msg .= "<RoleIDCode code=\"110153\" displayName=\"Source\" codeSystemName=\"DCM\" />";
480 $msg .= "</ActiveParticipant>";
481 $msg .= "<ActiveParticipant UserID=\"$destUserID\" UserIsRequestor=\"false\" ";
482 $msg .= "NetworkAccessPointID=\"$destNetwork\" NetworkAccessPointTypeCode=\"2\" >";
483 $msg .= "<RoleIDCode code=\"110152\" displayName=\"Destination\" codeSystemName=\"DCM\" />";
484 $msg .= "</ActiveParticipant>";
486 $msg .= "<AuditSourceIdentification AuditSourceID=\"$srcUserID\" />";
488 /* Indicate the username who generated this audit record */
489 $msg .= "<ParticipantObjectIdentification ParticipantObjectID=\"$user\" ";
490 $msg .= "ParticipantObjectTypeCode=\"1\" ";
491 $msg .= "ParticipantObjectTypeCodeRole=\"6\" >";
492 $msg .= "<ParticipantObjectIDTypeCode code=\"11\" ";
493 $msg .= "displayName=\"User Identifier\" ";
494 $msg .= "codeSystemName=\"RFC-3881\" /></ParticipantObjectIdentification>";
496 if ($eventIDdisplayName == 'Patient Record' && $patient_id != 0) {
497 $msg .= "<ParticipantObjectIdentification ParticipantObjectID=\"$patient_id\" ";
498 $msg .= "ParticipantObjectTypeCode=\"1\" ";
499 $msg .= "ParticipantObjectTypeCodeRole=\"1\" >";
500 $msg .= "<ParticipantObjectIDTypeCode code=\"2\" ";
501 $msg .= "displayName=\"Patient Number\" ";
502 $msg .= "codeSystemName=\"RFC-3881\" /></ParticipantObjectIdentification>";
505 $msg .= "</AuditMessage>";
507 /* Add the syslog header */
508 $date_obj = new DateTime($date);
509 $datestr= $date_obj->format(DATE_ATOM);
510 $msg = "<13> " . $datestr . " " . $_SERVER['SERVER_NAME'] . " " . $msg;
515 /* Create a TLS (SSLv3) connection to the given host/port.
516 * $localcert is the path to a PEM file with a client certificate and private key.
517 * $cafile is the path to the CA certificate file, for
518 * authenticating the remote machine's certificate.
519 * If $cafile is "", the remote machine's certificate is not verified.
520 * If $localcert is "", we don't pass a client certificate in the connection.
522 * Return a stream resource that can be used with fwrite(), fread(), etc.
523 * Returns FALSE on error.
525 function create_tls_conn($host, $port, $localcert, $cafile)
528 if ($cafile !== null && $cafile != "") {
529 $sslopts['cafile'] = $cafile;
530 $sslopts['verify_peer'] = true;
531 $sslopts['verify_depth'] = 10;
534 if ($localcert !== null && $localcert != "") {
535 $sslopts['local_cert'] = $localcert;
538 $opts = array('tls' => $sslopts, 'ssl' => $sslopts);
539 $ctx = stream_context_create($opts);
541 $flags = STREAM_CLIENT_CONNECT;
543 $olderr = error_reporting(0);
544 $conn = stream_socket_client(
545 'tls://' . $host . ":" . $port,
552 error_reporting($olderr);
557 /* This function is used to send audit records to an Audit Repository Server,
558 * as described in the Audit Trail and Node Authentication (ATNA) standard.
559 * Given the fields in a single audit record:
560 * - Create an XML audit message according to RFC 3881, including the RFC5425 syslog header.
561 * - Create a TLS connection that performs bi-directions certificate authentication,
562 * according to RFC 5425.
563 * - Send the XML message on the TLS connection.
565 function send_atna_audit_msg($user, $group, $event, $patient_id, $outcome, $comments)
567 /* If no ATNA repository server is configured, return */
568 if (empty($GLOBALS['atna_audit_host']) || empty($GLOBALS['enable_atna_audit'])) {
572 $host = $GLOBALS['atna_audit_host'];
573 $port = $GLOBALS['atna_audit_port'];
574 $localcert = $GLOBALS['atna_audit_localcert'];
575 $cacert = $GLOBALS['atna_audit_cacert'];
576 $conn = create_tls_conn($host, $port, $localcert, $cacert);
577 if ($conn !== false) {
578 $msg = create_rfc3881_msg($user, $group, $event, $patient_id, $outcome, $comments);
586 /* Add an entry into the audit log table, indicating that an
587 * SQL query was performed. $outcome is true if the statement
588 * successfully completed. Determine the event type based on
589 * the tables present in the SQL query.
591 function auditSQLEvent($statement, $outcome, $binds = null)
593 $user = isset($_SESSION['authUser']) ? $_SESSION['authUser'] : "";
594 /* Don't log anything if the audit logging is not enabled. Exception for "emergency" users */
595 if (!isset($GLOBALS['enable_auditlog']) || !($GLOBALS['enable_auditlog'])) {
596 if ((soundex($user) != soundex("emergency")) && (soundex($user) != soundex("breakglass"))) {
602 $statement = trim($statement);
604 /* Don't audit SQL statements done to the audit log,
605 * or we'll have an infinite loop.
607 if ((stripos($statement, "insert into log") !== false) ||
608 (stripos($statement, "FROM log ") !== false) ) {
612 $group = isset($_SESSION['authGroup']) ? $_SESSION['authGroup'] : "";
613 $comments = $statement;
615 $processed_binds = "";
616 if (is_array($binds)) {
617 // Need to include the binded variable elements in the logging
619 foreach ($binds as $value_bind) {
622 $processed_binds .= "'" . add_escape_custom($value_bind) . "'";
626 $processed_binds .= ",'" . add_escape_custom($value_bind) . "'";
630 if (!empty($processed_binds)) {
631 $processed_binds = "(" . $processed_binds . ")";
632 $comments .= " " . $processed_binds;
638 if ($outcome === false) {
642 if ($outcome !== false) {
643 // Should use the $statement rather than the processed
644 // variables, which includes the binded stuff. If do
645 // indeed need the binded values, then will need
646 // to include this as a separate array.
648 //error_log("STATEMENT: ".$statement,0);
649 //error_log("BINDS: ".$processed_binds,0);
650 $checksum = sql_checksum_of_modified_row($statement);
651 //error_log("CHECKSUM: ".$checksum,0);
654 /* Determine the query type (select, update, insert, delete) */
655 $querytype = "select";
656 $querytypes = array("select", "update", "insert", "delete","replace");
657 foreach ($querytypes as $qtype) {
658 if (stripos($statement, $qtype) === 0) {
663 /* Determine the audit event based on the database tables */
666 $tables = array("billing" => "patient-record",
667 "claims" => "patient-record",
668 "employer_data" => "patient-record",
669 "forms" => "patient-record",
670 "form_encounter" => "patient-record",
671 "form_dictation" => "patient-record",
672 "form_misc_billing_options" => "patient-record",
673 "form_reviewofs" => "patient-record",
674 "form_ros" => "patient-record",
675 "form_soap" => "patient-record",
676 "form_vitals" => "patient-record",
677 "history_data" => "patient-record",
678 "immunizations" => "patient-record",
679 "insurance_data" => "patient-record",
680 "issue_encounter" => "patient-record",
681 "lists" => "patient-record",
682 "patient_data" => "patient-record",
683 "payments" => "patient-record",
684 "pnotes" => "patient-record",
685 "onotes" => "patient-record",
686 "prescriptions" => "order",
687 "transactions" => "patient-record",
688 "amendments" => "patient-record",
689 "amendments_history" => "patient-record",
690 "facility" => "security-administration",
691 "pharmacies" => "security-administration",
692 "addresses" => "security-administration",
693 "phone_numbers" => "security-administration",
694 "x12_partners" => "security-administration",
695 "insurance_companies" => "security-administration",
696 "codes" => "security-administration",
697 "registry" => "security-administration",
698 "users" => "security-administration",
699 "groups" => "security-administration",
700 "openemr_postcalendar_events" => "scheduling",
701 "openemr_postcalendar_categories" => "security-administration",
702 "openemr_postcalendar_limits" => "security-administration",
703 "openemr_postcalendar_topics" => "security-administration",
704 "gacl_acl" => "security-administration",
705 "gacl_acl_sections" => "security-administration",
706 "gacl_acl_seq" => "security-administration",
707 "gacl_aco" => "security-administration",
708 "gacl_aco_map" => "security-administration",
709 "gacl_aco_sections" => "security-administration",
710 "gacl_aco_sections_seq" => "security-administration",
711 "gacl_aco_seq" => "security-administration",
712 "gacl_aro" => "security-administration",
713 "gacl_aro_groups" => "security-administration",
714 "gacl_aro_groups_id_seq" => "security-administration",
715 "gacl_aro_groups_map" => "security-administration",
716 "gacl_aro_map" => "security-administration",
717 "gacl_aro_sections" => "security-administration",
718 "gacl_aro_sections_seq" => "security-administration",
719 "gacl_aro_seq" => "security-administration",
720 "gacl_axo" => "security-administration",
721 "gacl_axo_groups" => "security-administration",
722 "gacl_axo_groups_map" => "security-administration",
723 "gacl_axo_map" => "security-administration",
724 "gacl_axo_sections" => "security-administration",
725 "gacl_groups_aro_map" => "security-administration",
726 "gacl_groups_axo_map" => "security-administration",
727 "gacl_phpgacl" => "security-administration",
728 "procedure_order" => "lab-order",
729 "procedure_order_code" => "lab-order",
730 "procedure_report" => "lab-results",
731 "procedure_result" => "lab-results");
733 /* When searching for table names, truncate the SQL statement,
734 * removing any WHERE, SET, or VALUE clauses.
736 $truncated_sql = $statement;
737 $truncated_sql = str_replace("\n", " ", $truncated_sql);
738 if ($querytype == "select") {
739 $startwhere = stripos($truncated_sql, " where ");
740 if ($startwhere > 0) {
741 $truncated_sql = substr($truncated_sql, 0, $startwhere);
744 $startparen = stripos($truncated_sql, "(");
745 $startset = stripos($truncated_sql, " set ");
746 $startvalues = stripos($truncated_sql, " values ");
748 if ($startparen > 0) {
749 $truncated_sql = substr($truncated_sql, 0, $startparen);
752 if ($startvalues > 0) {
753 $truncated_sql = substr($truncated_sql, 0, $startvalues);
757 $truncated_sql = substr($truncated_sql, 0, $startset);
761 foreach ($tables as $table => $value) {
762 if (strpos($truncated_sql, $table) !== false) {
764 $category = eventCategoryFinder($comments, $event, $table);
766 } else if (strpos($truncated_sql, "form_") !== false) {
767 $event = "patient-record";
768 $category = eventCategoryFinder($comments, $event, $table);
773 /* Avoid filling the audit log with trivial SELECT statements.
774 * Skip SELECTs from unknown tables.
775 * Skip SELECT count() statements.
776 * Skip the SELECT made by the authCheckSession() function.
778 if ($querytype == "select") {
779 if ($event == "other") {
783 if (stripos($statement, "SELECT count(") === 0) {
787 if (stripos($statement, "select username, password from users") === 0) {
793 /* If the event is a patient-record, then note the patient id */
795 if ($event == "patient-record") {
796 if (array_key_exists('pid', $_SESSION) && $_SESSION['pid'] != '') {
797 $pid = $_SESSION['pid'];
801 /* If query events are not enabled, don't log them */
802 if (($querytype == "select") && !(array_key_exists('audit_events_query', $GLOBALS) && $GLOBALS['audit_events_query'])) {
803 if ((soundex($user) != soundex("emergency")) && (soundex($user) != soundex("breakglass"))) {
808 if (!($GLOBALS["audit_events_${event}"])) {
809 if ((soundex($user) != soundex("emergency")) && (soundex($user) != soundex("breakglass"))) {
815 $event = $event . "-" . $querytype;
817 $adodb = $GLOBALS['adodb']['db'];
819 // ViSolve : Don't log sequences - to avoid the affect due to GenID calls
820 if (strpos($comments, "sequences") !== false) {
824 $encrypt_comment = 'No';
825 //July 1, 2014: Ensoftek: Check and encrypt audit logging
826 if (array_key_exists('enable_auditlog_encryption', $GLOBALS) && $GLOBALS["enable_auditlog_encryption"]) {
827 $comments = aes256Encrypt($comments);
828 $encrypt_comment = 'Yes';
831 $current_datetime = date("Y-m-d H:i:s");
832 $SSL_CLIENT_S_DN_CN=isset($_SERVER['SSL_CLIENT_S_DN_CN']) ? $_SERVER['SSL_CLIENT_S_DN_CN'] : '';
833 $sql = "insert into log (date, event,category, user, groupname, comments, patient_id, success, checksum,crt_user) " .
835 $adodb->qstr($current_datetime). ", ".
836 $adodb->qstr($event) . ", " .
837 $adodb->qstr($category) . ", " .
838 $adodb->qstr($user) . "," .
839 $adodb->qstr($group) . "," .
840 $adodb->qstr($comments) . "," .
841 $adodb->qstr($pid) . "," .
842 $adodb->qstr($success) . "," .
843 $adodb->qstr($checksum) . "," .
844 $adodb->qstr($SSL_CLIENT_S_DN_CN) .")";
845 sqlInsertClean_audit($sql);
847 $last_log_id = $GLOBALS['adodb']['db']->Insert_ID();
848 $checksumGenerate = '';
849 //July 1, 2014: Ensoftek: Record the encryption checksum in a secondary table(log_comment_encrypt)
850 if ($querytype == 'update') {
851 $concatLogColumns = $current_datetime.$event.$user.$group.$comments.$pid.$success.$checksum.$SSL_CLIENT_S_DN_CN;
852 $checksumGenerate = sha1($concatLogColumns);
855 $encryptLogQry = "INSERT INTO log_comment_encrypt (log_id, encrypt, checksum) ".
857 $adodb->qstr($last_log_id) . "," .
858 $adodb->qstr($encrypt_comment) . "," .
859 $adodb->qstr($checksumGenerate) .")";
860 sqlInsertClean_audit($encryptLogQry);
862 send_atna_audit_msg($user, $group, $event, $pid, $success, $comments);
866 // May-29-2014: Ensoftek: For Auditable events and tamper-resistance (MU2)
867 // Insert Audit Logging Status into the LOG table.
868 function auditSQLAuditTamper($enable)
870 $user = isset($_SESSION['authUser']) ? $_SESSION['authUser'] : "";
871 $group = isset($_SESSION['authGroup']) ? $_SESSION['authGroup'] : "";
875 $event = "security-administration" . "-" . "insert";
878 $adodb = $GLOBALS['adodb']['db'];
880 if ($enable == "1") {
881 $comments = "Audit Logging Enabled.";
883 $comments = "Audit Logging Disabled.";
886 $SSL_CLIENT_S_DN_CN=isset($_SERVER['SSL_CLIENT_S_DN_CN']) ? $_SERVER['SSL_CLIENT_S_DN_CN'] : '';
887 $sql = "insert into log (date, event, user, groupname, comments, patient_id, success, checksum,crt_user) " .
889 $adodb->qstr($event) . ", " .
890 $adodb->qstr($user) . "," .
891 $adodb->qstr($group) . "," .
892 $adodb->qstr($comments) . "," .
893 $adodb->qstr($pid) . "," .
894 $adodb->qstr($success) . "," .
895 $adodb->qstr($checksum) . "," .
896 $adodb->qstr($SSL_CLIENT_S_DN_CN) .")";
898 sqlInsertClean_audit($sql);
899 send_atna_audit_msg($user, $group, $event, $pid, $success, $comments);
903 * Record the patient disclosures.
904 * @param $dates - The date when the disclosures are sent to the thrid party.
905 * @param $event - The type of the disclosure.
906 * @param $pid - The id of the patient for whom the disclosures are recorded.
907 * @param $comment - The recipient name and description of the disclosure.
908 * @uname - The username who is recording the disclosure.
910 function recordDisclosure($dates, $event, $pid, $recipient, $description, $user)
912 $adodb = $GLOBALS['adodb']['db'];
913 $crt_user= $_SERVER['SSL_CLIENT_S_DN_CN'];
914 $groupname=$_SESSION['authProvider'];
916 $sql = "insert into extended_log ( date, event, user, recipient, patient_id, description) " .
917 "values (" . $adodb->qstr($dates) . "," . $adodb->qstr($event) . "," . $adodb->qstr($user) .
918 "," . $adodb->qstr($recipient) . ",".
919 $adodb->qstr($pid) ."," .
920 $adodb->qstr($description) .")";
921 $ret = sqlInsertClean_audit($sql);
924 * Edit the disclosures that is recorded.
925 * @param $dates - The date when the disclosures are sent to the thrid party.
926 * @param $event - The type of the disclosure.
927 * param $comment - The recipient and the description of the disclosure are appended.
928 * $logeventid - The id of the record which is to be edited.
930 function updateRecordedDisclosure($dates, $event, $recipient, $description, $disclosure_id)
932 $adodb = $GLOBALS['adodb']['db'];
933 $sql="update extended_log set
934 event=" . $adodb->qstr($event) . ",
935 date=" . $adodb->qstr($dates) . ",
936 recipient=" . $adodb->qstr($recipient) . ",
937 description=" . $adodb->qstr($description) . "
938 where id=" . $adodb->qstr($disclosure_id) . "";
939 $ret = sqlInsertClean_audit($sql);
942 * Delete the disclosures that is recorded.
943 * $deleteid - The id of the record which is to be deleted.
945 function deleteDisclosure($deletelid)
947 $sql="delete from extended_log where id='" . add_escape_custom($deletelid) . "'";
948 $ret = sqlInsertClean_audit($sql);
951 //July 1, 2014: Ensoftek: Function to AES256 encrypt a given string
952 function aes256Encrypt($sValue)
954 $sSecretKey = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
975 //July 1, 2014: Ensoftek: Function to AES256 decrypt a given string
976 function aes256Decrypt($sValue)
978 $sSecretKey = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
983 base64_decode($sValue),
997 //July 1, 2014: Ensoftek: Utility function to get data from table(log_comment_encrypt)
998 function logCommentEncryptData($log_id)
1000 $encryptRow = array();
1001 $logRes = sqlStatement("SELECT * FROM log_comment_encrypt WHERE log_id=?", array($log_id));
1002 while ($logRow = sqlFetchArray($logRes)) {
1003 $encryptRow['encrypt'] = $logRow['encrypt'];
1004 $encryptRow['checksum'] = $logRow['checksum'];
1011 * Function used to determine category of the event
1014 function eventCategoryFinder($sql, $event, $table)
1016 if ($event == 'delete') {
1017 if (strpos($sql, "lists:") === 0) {
1018 $fieldValues = explode("'", $sql);
1019 if (in_array('medical_problem', $fieldValues) === true) {
1020 return 'Problem List';
1021 } else if (in_array('medication', $fieldValues) === true) {
1022 return 'Medication';
1023 } else if (in_array('allergy', $fieldValues) === true) {
1029 if ($table == 'lists' || $table == 'lists_touch') {
1030 $trimSQL = stristr($sql, $table);
1031 $fieldValues = explode("'", $trimSQL);
1032 if (in_array('medical_problem', $fieldValues) === true) {
1033 return 'Problem List';
1034 } else if (in_array('medication', $fieldValues) === true) {
1035 return 'Medication';
1036 } else if (in_array('allergy', $fieldValues) === true) {
1039 } else if ($table == 'immunizations') {
1040 return "Immunization";
1041 } else if ($table == 'form_vitals') {
1043 } else if ($table == 'history_data') {
1044 return "Social and Family History";
1045 } else if ($table == 'forms' || $table == 'form_encounter' || strpos($table, 'form_') === 0) {
1046 return "Encounter Form";
1047 } else if ($table == 'insurance_data') {
1048 return "Patient Insurance";
1049 } else if ($table == 'patient_data' || $table == 'employer_data') {
1050 return "Patient Demographics";
1051 } else if ($table == 'payments' || $table == "billing" || $table == "claims") {
1053 } else if ($table == 'pnotes') {
1054 return "Clinical Mail";
1055 } else if ($table == 'prescriptions') {
1056 return "Medication";
1057 } else if ($table == 'transactions') {
1058 $trimSQL = stristr($sql, "transactions");
1059 $fieldValues = explode("'", $trimSQL);
1060 if (in_array("LBTref", $fieldValues)) {
1065 } else if ($table == 'amendments' || $table == 'amendments_history') {
1066 return "Amendments";
1067 } else if ($table == 'openemr_postcalendar_events') {
1068 return "Scheduling";
1069 } else if ($table == 'procedure_order' || $table == 'procedure_order_code') {
1071 } else if ($table == 'procedure_report' || $table == 'procedure_result') {
1072 return "Lab Result";
1073 } else if ($event == 'security-administration') {