3 function newEvent($event, $user, $groupname, $success, $comments="", $patient_id=null, $log_from = 'open-emr', $menu_item = 'dashboard', $ccda_doc_id = 0) {
4 $adodb = $GLOBALS['adodb']['db'];
5 $crt_user=isset($_SERVER['SSL_CLIENT_S_DN_CN']) ? $_SERVER['SSL_CLIENT_S_DN_CN'] : null;
8 // Special case delete for lists table
10 $category = eventCategoryFinder($comments, $event, '');
12 // deal with comments encryption, if turned on
13 $encrypt_comment = 'No';
14 if (!empty($comments)) {
15 if ($GLOBALS["enable_auditlog_encryption"]) {
16 $comments = aes256Encrypt($comments);
17 $encrypt_comment = 'Yes';
21 if ($log_from == 'patient-portal') {
22 $sqlMenuItems = "SELECT * FROM patient_portal_menu";
24 $resMenuItems = sqlStatement($sqlMenuItems);
25 for($iter=0; $rowMenuItem=sqlFetchArray($resMenuItems); $iter++) {
26 $menuItems[$rowMenuItem['patient_portal_menu_id']] = $rowMenuItem['menu_name'];
29 $menuItemId = array_search($menu_item, $menuItems);
30 $sql = "insert into log ( date, event,category, user, patient_id, groupname, success, comments,
31 log_from, menu_item_id, crt_user, ccda_doc_id) values ( NOW(), ?,'Patient Portal', ?, ?, ?, ?, ?, ?, ?,?, ?)";
32 $ret = sqlStatementNoLog($sql, array($event, $user, $patient_id, $groupname, $success, $comments,$log_from, $menuItemId,$crt_user, $ccda_doc_id));
35 /* More details added to the log */
36 $sql = "insert into log ( date, event,category, user, groupname, success, comments, crt_user, patient_id) " .
37 "values ( NOW(), " . $adodb->qstr($event) . ",". $adodb->qstr($category) . "," . $adodb->qstr($user) .
38 "," . $adodb->qstr($groupname) . "," . $adodb->qstr($success) . "," .
39 $adodb->qstr($comments) ."," .
40 $adodb->qstr($crt_user) ."," . $adodb->qstr($patient_id). ")";
42 $ret = sqlInsertClean_audit($sql);
45 // Send item to log_comment_encrypt for comment encyption tracking
46 $last_log_id = $GLOBALS['adodb']['db']->Insert_ID();
47 $encryptLogQry = "INSERT INTO log_comment_encrypt (log_id, encrypt, checksum) ".
49 $adodb->qstr($last_log_id) . "," .
50 $adodb->qstr($encrypt_comment) . "," .
52 sqlInsertClean_audit($encryptLogQry);
54 if(($patient_id=="NULL") || ($patient_id==null))$patient_id=0;
56 send_atna_audit_msg($user, $groupname, $event, $patient_id, $success, $comments);
59 function getEventByDate($date, $user="", $cols="DISTINCT date, event, user, groupname, patient_id, success, comments, checksum")
61 $sql = "SELECT $cols FROM log WHERE date >= '$date 00:00:00' AND date <= '$date 23:59:59'";
62 if ($user) $sql .= " AND user LIKE '$user'";
63 $sql .= " ORDER BY date DESC LIMIT 5000";
64 $res = sqlStatement($sql);
65 for($iter=0; $row=sqlFetchArray($res); $iter++) {
72 * Get records from the LOG and Extended_Log table
73 * using the optional parameters:
74 * date : a specific date (defaults to today)
75 * user : a specific user (defaults to none)
76 * cols : gather specific columns (defaults to date,event,user,groupname,comments)
77 * sortby : sort the results by (defaults to none)
81 function getEvents($params)
83 // parse the parameters
84 $cols = "DISTINCT date, event, category, user, groupname, patient_id, success, comments,checksum,crt_user, id ";
85 if (isset($params['cols']) && $params['cols'] != "") $cols = $params['cols'];
87 $date1 = date("Y-m-d H:i:s", time());
88 if (isset($params['sdate']) && $params['sdate'] != "") $date1= $params['sdate'];
90 $date2 = date("Y-m-d H:i:s", time());
91 if (isset($params['edate']) && $params['edate'] != "") $date2= $params['edate'];
94 if (isset($params['user']) && $params['user'] != "") $user= $params['user'];
96 //VicarePlus :: For Generating log with patient id.
98 if (isset($params['patient']) && $params['patient'] != "") $patient= $params['patient'];
101 if (isset($params['sortby']) && $params['sortby'] != "") $sortby = $params['sortby'];
104 if (isset($params['levent']) && $params['levent'] != "") $levent = $params['levent'];
107 if (isset($params['tevent']) && $params['tevent'] != "") $tevent = $params['tevent'];
110 if (isset($params['direction']) && $params['direction'] != "") $direction = $params['direction'];
113 if (isset($params['event']) && $params['event'] != "") $event = $params['event'];
115 if ($sortby == "comments") $sortby = "description";
116 if ($sortby == "groupname") $sortby = ""; //VicarePlus :: since there is no groupname in extended_log
117 if ($sortby == "success") $sortby = ""; //VicarePlus :: since there is no success field in extended_log
118 if ($sortby == "checksum") $sortby = ""; //VicarePlus :: since there is no checksum field in extended_log
119 if ($sortby == "category") $sortby = ""; //VicarePlus :: since there is no category field in extended_log
120 $columns = "DISTINCT date, event, user, recipient,patient_id,description";
121 $sql = "SELECT $columns FROM extended_log WHERE date >= '$date1' AND date <= '$date2'";
122 if ($user != "") $sql .= " AND user LIKE '$user'";
123 if ($patient != "") $sql .= " AND patient_id LIKE '$patient'";
124 if ($levent != "") $sql .= " AND event LIKE '$levent%'";
125 if ($sortby != "") $sql .= " ORDER BY ".$sortby." DESC"; // descending order
126 $sql .= " LIMIT 5000";
131 $sql = "SELECT $cols FROM log WHERE date >= '$date1' AND date <= '$date2'";
132 if ($user != "") $sql .= " AND user LIKE '$user'";
133 if ($patient != "") $sql .= " AND patient_id LIKE '$patient'";
134 if ($levent != "") $sql .= " AND event LIKE '$levent%'";
135 if ($tevent != "") $sql .= " AND event LIKE '%$tevent'";
136 if ($sortby != "") $sql .= " ORDER BY ".$sortby." ".escape_sort_order($direction); // descending order
137 $sql .= " LIMIT 5000";
139 $res = sqlStatement($sql);
140 for($iter=0; $row=sqlFetchArray($res); $iter++) {
146 /* Given an SQL insert/update that was just performeds:
147 * - Find the table and primary id of the row that was created/modified
148 * - Calculate the SHA1 checksum of that row (with all the
149 * column values concatenated together).
150 * - Return the SHA1 checksum as a 40 char hex string.
151 * If this is not an insert/update query, return "".
152 * If multiple rows were modified, return "".
153 * If we're unable to determine the row modified, return "".
155 * TODO: May need to incorporate the binded stuff (still analyzing)
158 function sql_checksum_of_modified_row($statement)
163 $tokens = preg_split("/[\s,(\'\"]+/", $statement);
164 /* Identifying the id for insert/replace statements for calculating the checksum */
165 if((strcasecmp($tokens[0],"INSERT")==0) || (strcasecmp($tokens[0],"REPLACE")==0)){
167 $rid = generic_sql_insert_id();
168 /* For handling the table that doesn't have auto-increment column */
169 if ($rid === 0 || $rid === FALSE) {
170 if($table == "gacl_aco_map" || $table == "gacl_aro_groups_map" || $table == "gacl_aro_map" || $table == "gacl_axo_groups_map" || $table == "gacl_axo_map")
172 else if($table == "gacl_groups_aro_map" || $table == "gacl_groups_axo_map")
176 /* To handle insert statements */
177 if($tokens[3] == $id){
178 for($i=4;$i<count($tokens);$i++){
179 if(strcasecmp($tokens[$i],"VALUES")==0){
185 /* To handle replace statements */
186 else if(strcasecmp($tokens[3],"SET")==0){
187 if((strcasecmp($tokens[4],"ID")==0) || (strcasecmp($tokens[4],"`ID`")==0)){
197 /* Identifying the id for update statements for calculating the checksum */
198 else if(strcasecmp($tokens[0],"UPDATE")==0){
202 $total = count($tokens);
204 /* Identifying the primary key column for the updated record */
205 if ($table == "form_physical_exam") {
208 else if ($table == "claims"){
211 else if ($table == "openemr_postcalendar_events") {
214 else if ($table == "lang_languages"){
217 else if ($table == "openemr_postcalendar_categories" || $table == "openemr_postcalendar_topics"){
220 else if ($table == "openemr_postcalendar_limits"){
223 else if($table == "gacl_aco_map" || $table == "gacl_aro_groups_map" || $table == "gacl_aro_map" || $table == "gacl_axo_groups_map" || $table == "gacl_axo_map"){
226 else if($table == "gacl_groups_aro_map" || $table == "gacl_groups_axo_map"){
233 /* Identifying the primary key value for the updated record */
234 while ($offset < $total) {
235 /* There are 4 possible ways that the id=123 can be parsed:
243 if (($tokens[$offset] == "$id=") && ($offset + 1 < $total)) {
244 $rid = $tokens[$offset+1];
247 /* 'id', '=', '123' */
248 else if ($tokens[$offset] == "$id" && $tokens[$offset+1] == "=" && ($offset+2 < $total)) {
249 $rid = $tokens[$offset+2];
253 else if (strpos($tokens[$offset], "$id=") === 0) {
254 $tid = substr($tokens[$offset], strlen($id)+1);
260 else if($tokens[$offset] == "$id") {
261 $tid = substr($tokens[$offset+1],1);
267 }//while ($offset < $total)
268 }// else if ($tokens[0] == 'update' || $tokens[0] == 'UPDATE' )
270 if ($table == "" || $rid == "") {
273 /* Framing sql statements for calculating checksum */
274 if ($table == "form_physical_exam") {
275 $sql = "select * from $table where forms_id = $rid";
277 else if ($table == "claims"){
278 $sql = "select * from $table where patient_id = $rid";
280 else if ($table == "openemr_postcalendar_events") {
281 $sql = "select * from $table where pc_eid = $rid";
283 else if ($table == "lang_languages") {
284 $sql = "select * from $table where lang_id = $rid";
286 else if ($table == "openemr_postcalendar_categories" || $table == "openemr_postcalendar_topics"){
287 $sql = "select * from $table where pc_catid = $rid";
289 else if ($table == "openemr_postcalendar_limits"){
290 $sql = "select * from $table where pc_limitid = $rid";
292 else if ($table == "gacl_aco_map" || $table == "gacl_aro_groups_map" || $table == "gacl_aro_map" || $table == "gacl_axo_groups_map" || $table == "gacl_axo_map"){
293 $sql = "select * from $table where acl_id = $rid";
295 else if($table == "gacl_groups_aro_map" || $table == "gacl_groups_axo_map"){
296 $sql = "select * from $table where group_id = $rid";
299 $sql = "select * from $table where id = $rid";
301 // When this function is working perfectly, can then shift to the
302 // sqlQueryNoLog() function.
303 $results = sqlQueryNoLogIgnoreError($sql);
305 /* Concatenating the column values for the row inserted/updated */
306 if (is_array($results)) {
307 foreach ($results as $field_name => $field) {
308 $column_values .= $field;
311 // ViCarePlus: As per NIST standard, the encryption algorithm SHA1 is used
313 //error_log("COLUMN_VALUES: ".$column_values,0);
314 return sha1($column_values);
317 /* Create an XML audit record corresponding to RFC 3881.
318 * The parameters passed are the column values (from table 'log')
319 * for a single audit record.
321 function create_rfc3881_msg($user, $group, $event, $patient_id, $outcome, $comments)
324 /* Event action codes indicate whether the event is read/write.
325 * C = create, R = read, U = update, D = delete, E = execute
327 $eventActionCode = 'E';
328 if (substr($event, -7) == "-create") {
329 $eventActionCode = 'C';
331 else if (substr($event, -7) == "-insert") {
332 $eventActionCode = 'C';
334 else if (substr($event, -7) == "-select") {
335 $eventActionCode = 'R';
337 else if (substr($event, -7) == "-update") {
338 $eventActionCode = 'U';
340 else if (substr($event, -7) == "-delete") {
341 $eventActionCode = 'D';
344 $date_obj = new DateTime();
345 $eventDateTime = $date_obj->format(DATE_ATOM);
347 /* For EventOutcomeIndicator, 0 = success and 4 = minor error */
348 $eventOutcome = ($outcome === 1) ? 0 : 4;
350 /* The choice of event codes is up to OpenEMR.
351 * We're using the same event codes as
352 * https://iheprofiles.projects.openhealthtools.org/
354 $eventIDcodeSystemName = "DCM";
356 $eventIDdisplayName = $event;
358 if (strpos($event, 'patient-record') !== FALSE) {
359 $eventIDcode = 110110;
360 $eventIDdisplayName = 'Patient Record';
362 else if (strpos($event, 'view') !== FALSE) {
363 $eventIDCode = 110110;
364 $eventIDdisplayName = 'Patient Record';
366 else if (strpos($event, 'login') !== FALSE) {
367 $eventIDcode = 110122;
368 $eventIDdisplayName = 'Login';
370 else if (strpos($event, 'logout') !== FALSE) {
371 $eventIDcode = 110123;
372 $eventIDdisplayName = 'Logout';
374 else if (strpos($event, 'scheduling') !== FALSE) {
375 $eventIDcode = 110111;
376 $eventIDdisplayName = 'Patient Care Assignment';
378 else if (strpos($event, 'security-administration') !== FALSE) {
379 $eventIDcode = 110129;
380 $eventIDdisplayName = 'Security Administration';
386 /* Variables used in ActiveParticipant section, which identifies
387 * the IP address and application of the source and destination.
389 $srcUserID = $_SERVER['SERVER_NAME'] . '|OpenEMR';
390 $srcNetwork = $_SERVER['SERVER_ADDR'];
391 $destUserID = $GLOBALS['atna_audit_host'];
392 $destNetwork = $GLOBALS['atna_audit_host'];
398 $userDisplayName = 'User Identifier';
401 $patientTypeCode = "";
404 $patientDisplayName = "";
406 if ($eventIDdisplayName == 'Patient Record') {
407 $patientID = $patient_id;
408 $pattientTypeCode = 1;
411 $patientDisplayName = 'Patient Number';
414 /* Construct the XML audit message, and save to $msg */
415 $msg = '<?xml version="1.0" encoding="ASCII"?>';
416 $msg .= '<AuditMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ';
417 $msg .= 'xsi:noNamespaceSchemaLocation="healthcare-security-audit.xsd">';
419 /* Indicate the event code, text name, read/write type, and date/time */
420 $msg .= "<EventIdentification EventActionCode=\"$eventActionCode\" ";
421 $msg .= "EventDateTime=\"$eventDateTime\" ";
422 $msg .= "EventOutcomeIndicator=\"$eventOutcome\">";
423 $msg .= "<EventID code=\"eventIDcode\" displayName=\"$eventIDdisplayName\" ";
424 $msg .= "codeSystemName=\"DCM\" />";
425 $msg .= "</EventIdentification>";
427 /* Indicate the IP address and application of the source and destination */
428 $msg .= "<ActiveParticipant UserID=\"$srcUserID\" UserIsRequestor=\"true\" ";
429 $msg .= "NetworkAccessPointID=\"$srcNetwork\" NetworkAccessPointTypeCode=\"2\" >";
430 $msg .= "<RoleIDCode code=\"110153\" displayName=\"Source\" codeSystemName=\"DCM\" />";
431 $msg .= "</ActiveParticipant>";
432 $msg .= "<ActiveParticipant UserID=\"$destUserID\" UserIsRequestor=\"false\" ";
433 $msg .= "NetworkAccessPointID=\"$destNetwork\" NetworkAccessPointTypeCode=\"2\" >";
434 $msg .= "<RoleIDCode code=\"110152\" displayName=\"Destination\" codeSystemName=\"DCM\" />";
435 $msg .= "</ActiveParticipant>";
437 $msg .= "<AuditSourceIdentification AuditSourceID=\"$srcUserID\" />";
439 /* Indicate the username who generated this audit record */
440 $msg .= "<ParticipantObjectIdentification ParticipantObjectID=\"$user\" ";
441 $msg .= "ParticipantObjectTypeCode=\"1\" ";
442 $msg .= "ParticipantObjectTypeCodeRole=\"6\" >";
443 $msg .= "<ParticipantObjectIDTypeCode code=\"11\" ";
444 $msg .= "displayName=\"User Identifier\" ";
445 $msg .= "codeSystemName=\"RFC-3881\" /></ParticipantObjectIdentification>";
447 if ($eventIDdisplayName == 'Patient Record' && $patient_id != 0) {
448 $msg .= "<ParticipantObjectIdentification ParticipantObjectID=\"$patient_id\" ";
449 $msg .= "ParticipantObjectTypeCode=\"1\" ";
450 $msg .= "ParticipantObjectTypeCodeRole=\"1\" >";
451 $msg .= "<ParticipantObjectIDTypeCode code=\"2\" ";
452 $msg .= "displayName=\"Patient Number\" ";
453 $msg .= "codeSystemName=\"RFC-3881\" /></ParticipantObjectIdentification>";
455 $msg .= "</AuditMessage>";
457 /* Add the syslog header */
458 $date_obj = new DateTime($date);
459 $datestr= $date_obj->format(DATE_ATOM);
460 $msg = "<13> " . $datestr . " " . $_SERVER['SERVER_NAME'] . " " . $msg;
465 /* Create a TLS (SSLv3) connection to the given host/port.
466 * $localcert is the path to a PEM file with a client certificate and private key.
467 * $cafile is the path to the CA certificate file, for
468 * authenticating the remote machine's certificate.
469 * If $cafile is "", the remote machine's certificate is not verified.
470 * If $localcert is "", we don't pass a client certificate in the connection.
472 * Return a stream resource that can be used with fwrite(), fread(), etc.
473 * Returns FALSE on error.
475 function create_tls_conn($host, $port, $localcert, $cafile) {
477 if ($cafile !== null && $cafile != "") {
478 $sslopts['cafile'] = $cafile;
479 $sslopts['verify_peer'] = TRUE;
480 $sslopts['verify_depth'] = 10;
482 if ($localcert !== null && $localcert != "") {
483 $sslopts['local_cert'] = $localcert;
485 $opts = array('tls' => $sslopts, 'ssl' => $sslopts);
486 $ctx = stream_context_create($opts);
488 $flags = STREAM_CLIENT_CONNECT;
490 $olderr = error_reporting(0);
491 $conn = stream_socket_client('tls://' . $host . ":" . $port, $errno, $errstr,
492 $timeout, $flags, $ctx);
493 error_reporting($olderr);
498 /* This function is used to send audit records to an Audit Repository Server,
499 * as described in the Audit Trail and Node Authentication (ATNA) standard.
500 * Given the fields in a single audit record:
501 * - Create an XML audit message according to RFC 3881, including the RFC5425 syslog header.
502 * - Create a TLS connection that performs bi-directions certificate authentication,
503 * according to RFC 5425.
504 * - Send the XML message on the TLS connection.
506 function send_atna_audit_msg($user, $group, $event, $patient_id, $outcome, $comments)
508 /* If no ATNA repository server is configured, return */
509 if (empty($GLOBALS['atna_audit_host']) || empty($GLOBALS['enable_atna_audit']) ) {
513 $host = $GLOBALS['atna_audit_host'];
514 $port = $GLOBALS['atna_audit_port'];
515 $localcert = $GLOBALS['atna_audit_localcert'];
516 $cacert = $GLOBALS['atna_audit_cacert'];
517 $conn = create_tls_conn($host, $port, $localcert, $cacert);
518 if ($conn !== FALSE) {
519 $msg = create_rfc3881_msg($user, $group, $event, $patient_id, $outcome, $comments);
527 /* Add an entry into the audit log table, indicating that an
528 * SQL query was performed. $outcome is true if the statement
529 * successfully completed. Determine the event type based on
530 * the tables present in the SQL query.
532 function auditSQLEvent($statement, $outcome, $binds=NULL)
534 $user = isset($_SESSION['authUser']) ? $_SESSION['authUser'] : "";
535 /* Don't log anything if the audit logging is not enabled. Exception for "emergency" users */
536 if (!isset($GLOBALS['enable_auditlog']) || !($GLOBALS['enable_auditlog']))
538 if ((soundex($user) != soundex("emergency")) && (soundex($user) != soundex("breakglass")))
543 $statement = trim($statement);
545 /* Don't audit SQL statements done to the audit log,
546 * or we'll have an infinite loop.
548 if ((stripos($statement, "insert into log") !== FALSE) ||
549 (stripos($statement, "FROM log ") !== FALSE) ) {
553 $group = isset($_SESSION['authGroup']) ? $_SESSION['authGroup'] : "";
554 $comments = $statement;
556 $processed_binds = "";
557 if (is_array($binds)) {
558 // Need to include the binded variable elements in the logging
560 foreach ($binds as $value_bind) {
563 $processed_binds .= "'" . add_escape_custom($value_bind) . "'";
568 $processed_binds .= ",'" . add_escape_custom($value_bind) . "'";
571 if (!empty($processed_binds)) {
572 $processed_binds = "(" . $processed_binds . ")";
573 $comments .= " " . $processed_binds;
579 if ($outcome === FALSE) {
582 if ($outcome !== FALSE) {
583 // Should use the $statement rather than the processed
584 // variables, which includes the binded stuff. If do
585 // indeed need the binded values, then will need
586 // to include this as a separate array.
588 //error_log("STATEMENT: ".$statement,0);
589 //error_log("BINDS: ".$processed_binds,0);
590 $checksum = sql_checksum_of_modified_row($statement);
591 //error_log("CHECKSUM: ".$checksum,0);
593 /* Determine the query type (select, update, insert, delete) */
594 $querytype = "select";
595 $querytypes = array("select", "update", "insert", "delete","replace");
596 foreach ($querytypes as $qtype) {
597 if (stripos($statement, $qtype) === 0) {
602 /* Determine the audit event based on the database tables */
605 $tables = array("billing" => "patient-record",
606 "claims" => "patient-record",
607 "employer_data" => "patient-record",
608 "forms" => "patient-record",
609 "form_encounter" => "patient-record",
610 "form_dictation" => "patient-record",
611 "form_misc_billing_options" => "patient-record",
612 "form_reviewofs" => "patient-record",
613 "form_ros" => "patient-record",
614 "form_soap" => "patient-record",
615 "form_vitals" => "patient-record",
616 "history_data" => "patient-record",
617 "immunizations" => "patient-record",
618 "insurance_data" => "patient-record",
619 "issue_encounter" => "patient-record",
620 "lists" => "patient-record",
621 "patient_data" => "patient-record",
622 "payments" => "patient-record",
623 "pnotes" => "patient-record",
624 "onotes" => "patient-record",
625 "prescriptions" => "order",
626 "transactions" => "patient-record",
627 "amendments" => "patient-record",
628 "amendments_history" => "patient-record",
629 "facility" => "security-administration",
630 "pharmacies" => "security-administration",
631 "addresses" => "security-administration",
632 "phone_numbers" => "security-administration",
633 "x12_partners" => "security-administration",
634 "insurance_companies" => "security-administration",
635 "codes" => "security-administration",
636 "registry" => "security-administration",
637 "users" => "security-administration",
638 "groups" => "security-administration",
639 "openemr_postcalendar_events" => "scheduling",
640 "openemr_postcalendar_categories" => "security-administration",
641 "openemr_postcalendar_limits" => "security-administration",
642 "openemr_postcalendar_topics" => "security-administration",
643 "gacl_acl" => "security-administration",
644 "gacl_acl_sections" => "security-administration",
645 "gacl_acl_seq" => "security-administration",
646 "gacl_aco" => "security-administration",
647 "gacl_aco_map" => "security-administration",
648 "gacl_aco_sections" => "security-administration",
649 "gacl_aco_sections_seq" => "security-administration",
650 "gacl_aco_seq" => "security-administration",
651 "gacl_aro" => "security-administration",
652 "gacl_aro_groups" => "security-administration",
653 "gacl_aro_groups_id_seq" => "security-administration",
654 "gacl_aro_groups_map" => "security-administration",
655 "gacl_aro_map" => "security-administration",
656 "gacl_aro_sections" => "security-administration",
657 "gacl_aro_sections_seq" => "security-administration",
658 "gacl_aro_seq" => "security-administration",
659 "gacl_axo" => "security-administration",
660 "gacl_axo_groups" => "security-administration",
661 "gacl_axo_groups_map" => "security-administration",
662 "gacl_axo_map" => "security-administration",
663 "gacl_axo_sections" => "security-administration",
664 "gacl_groups_aro_map" => "security-administration",
665 "gacl_groups_axo_map" => "security-administration",
666 "gacl_phpgacl" => "security-administration",
667 "procedure_order" => "lab-order",
668 "procedure_order_code" => "lab-order",
669 "procedure_report" => "lab-results",
670 "procedure_result" => "lab-results");
672 /* When searching for table names, truncate the SQL statement,
673 * removing any WHERE, SET, or VALUE clauses.
675 $truncated_sql = $statement;
676 $truncated_sql = str_replace("\n", " ", $truncated_sql);
677 if ($querytype == "select") {
678 $startwhere = stripos($truncated_sql, " where ");
679 if ($startwhere > 0) {
680 $truncated_sql = substr($truncated_sql, 0, $startwhere);
684 $startparen = stripos($truncated_sql, "(" );
685 $startset = stripos($truncated_sql, " set ");
686 $startvalues = stripos($truncated_sql, " values ");
688 if ($startparen > 0) {
689 $truncated_sql = substr($truncated_sql, 0, $startparen);
691 if ($startvalues > 0) {
692 $truncated_sql = substr($truncated_sql, 0, $startvalues);
695 $truncated_sql = substr($truncated_sql, 0, $startset);
698 foreach ($tables as $table => $value) {
699 if (strpos($truncated_sql, $table) !== FALSE) {
701 $category = eventCategoryFinder($comments, $event,$table);
704 else if (strpos($truncated_sql, "form_") !== FALSE) {
705 $event = "patient-record";
706 $category = eventCategoryFinder($comments, $event,$table);
711 /* Avoid filling the audit log with trivial SELECT statements.
712 * Skip SELECTs from unknown tables.
713 * Skip SELECT count() statements.
714 * Skip the SELECT made by the authCheckSession() function.
716 if ($querytype == "select") {
717 if ($event == "other")
719 if (stripos($statement, "SELECT count(" ) === 0)
721 if (stripos($statement, "select username, password from users") === 0)
726 /* If the event is a patient-record, then note the patient id */
728 if ($event == "patient-record") {
729 if (array_key_exists('pid', $_SESSION) && $_SESSION['pid'] != '') {
730 $pid = $_SESSION['pid'];
734 /* If query events are not enabled, don't log them */
735 if (($querytype == "select") && !(array_key_exists('audit_events_query', $GLOBALS) && $GLOBALS['audit_events_query']))
737 if ((soundex($user) != soundex("emergency")) && (soundex($user) != soundex("breakglass")))
741 if (!($GLOBALS["audit_events_${event}"]))
743 if ((soundex($user) != soundex("emergency")) && (soundex($user) != soundex("breakglass")))
748 $event = $event . "-" . $querytype;
750 $adodb = $GLOBALS['adodb']['db'];
752 // ViSolve : Don't log sequences - to avoid the affect due to GenID calls
753 if (strpos($comments, "sequences") !== FALSE) return;
755 $encrypt_comment = 'No';
756 //July 1, 2014: Ensoftek: Check and encrypt audit logging
757 if (array_key_exists('enable_auditlog_encryption', $GLOBALS) && $GLOBALS["enable_auditlog_encryption"]) {
758 $comments = aes256Encrypt($comments);
759 $encrypt_comment = 'Yes';
762 $current_datetime = date("Y-m-d H:i:s");
763 $SSL_CLIENT_S_DN_CN=isset($_SERVER['SSL_CLIENT_S_DN_CN']) ? $_SERVER['SSL_CLIENT_S_DN_CN'] : '';
764 $sql = "insert into log (date, event,category, user, groupname, comments, patient_id, success, checksum,crt_user) " .
766 $adodb->qstr($current_datetime). ", ".
767 $adodb->qstr($event) . ", " .
768 $adodb->qstr($category) . ", " .
769 $adodb->qstr($user) . "," .
770 $adodb->qstr($group) . "," .
771 $adodb->qstr($comments) . "," .
772 $adodb->qstr($pid) . "," .
773 $adodb->qstr($success) . "," .
774 $adodb->qstr($checksum) . "," .
775 $adodb->qstr($SSL_CLIENT_S_DN_CN) .")";
776 sqlInsertClean_audit($sql);
778 $last_log_id = $GLOBALS['adodb']['db']->Insert_ID();
779 $checksumGenerate = '';
780 //July 1, 2014: Ensoftek: Record the encryption checksum in a secondary table(log_comment_encrypt)
781 if ($querytype == 'update') {
782 $concatLogColumns = $current_datetime.$event.$user.$group.$comments.$pid.$success.$checksum.$SSL_CLIENT_S_DN_CN;
783 $checksumGenerate = sha1($concatLogColumns);
785 $encryptLogQry = "INSERT INTO log_comment_encrypt (log_id, encrypt, checksum) ".
787 $adodb->qstr($last_log_id) . "," .
788 $adodb->qstr($encrypt_comment) . "," .
789 $adodb->qstr($checksumGenerate) .")";
790 sqlInsertClean_audit($encryptLogQry);
792 send_atna_audit_msg($user, $group, $event, $pid, $success, $comments);
796 // May-29-2014: Ensoftek: For Auditable events and tamper-resistance (MU2)
797 // Insert Audit Logging Status into the LOG table.
798 function auditSQLAuditTamper($enable)
800 $user = isset($_SESSION['authUser']) ? $_SESSION['authUser'] : "";
801 $group = isset($_SESSION['authGroup']) ? $_SESSION['authGroup'] : "";
805 $event = "security-administration" . "-" . "insert";
808 $adodb = $GLOBALS['adodb']['db'];
812 $comments = "Audit Logging Enabled.";
816 $comments = "Audit Logging Disabled.";
819 $SSL_CLIENT_S_DN_CN=isset($_SERVER['SSL_CLIENT_S_DN_CN']) ? $_SERVER['SSL_CLIENT_S_DN_CN'] : '';
820 $sql = "insert into log (date, event, user, groupname, comments, patient_id, success, checksum,crt_user) " .
822 $adodb->qstr($event) . ", " .
823 $adodb->qstr($user) . "," .
824 $adodb->qstr($group) . "," .
825 $adodb->qstr($comments) . "," .
826 $adodb->qstr($pid) . "," .
827 $adodb->qstr($success) . "," .
828 $adodb->qstr($checksum) . "," .
829 $adodb->qstr($SSL_CLIENT_S_DN_CN) .")";
831 sqlInsertClean_audit($sql);
832 send_atna_audit_msg($user, $group, $event, $pid, $success, $comments);
836 * Record the patient disclosures.
837 * @param $dates - The date when the disclosures are sent to the thrid party.
838 * @param $event - The type of the disclosure.
839 * @param $pid - The id of the patient for whom the disclosures are recorded.
840 * @param $comment - The recipient name and description of the disclosure.
841 * @uname - The username who is recording the disclosure.
843 function recordDisclosure($dates,$event,$pid,$recipient,$description,$user)
845 $adodb = $GLOBALS['adodb']['db'];
846 $crt_user= $_SERVER['SSL_CLIENT_S_DN_CN'];
847 $groupname=$_SESSION['authProvider'];
849 $sql = "insert into extended_log ( date, event, user, recipient, patient_id, description) " .
850 "values (" . $adodb->qstr($dates) . "," . $adodb->qstr($event) . "," . $adodb->qstr($user) .
851 "," . $adodb->qstr($recipient) . ",".
852 $adodb->qstr($pid) ."," .
853 $adodb->qstr($description) .")";
854 $ret = sqlInsertClean_audit($sql);
857 * Edit the disclosures that is recorded.
858 * @param $dates - The date when the disclosures are sent to the thrid party.
859 * @param $event - The type of the disclosure.
860 * param $comment - The recipient and the description of the disclosure are appended.
861 * $logeventid - The id of the record which is to be edited.
863 function updateRecordedDisclosure($dates,$event,$recipient,$description,$disclosure_id)
865 $adodb = $GLOBALS['adodb']['db'];
866 $sql="update extended_log set
867 event=" . $adodb->qstr($event) . ",
868 date=" . $adodb->qstr($dates) . ",
869 recipient=" . $adodb->qstr($recipient) . ",
870 description=" . $adodb->qstr($description) . "
871 where id=" . $adodb->qstr($disclosure_id) . "";
872 $ret = sqlInsertClean_audit($sql);
875 * Delete the disclosures that is recorded.
876 * $deleteid - The id of the record which is to be deleted.
878 function deleteDisclosure($deletelid)
880 $sql="delete from extended_log where id='" . add_escape_custom($deletelid) . "'";
881 $ret = sqlInsertClean_audit($sql);
884 //July 1, 2014: Ensoftek: Function to AES256 encrypt a given string
885 function aes256Encrypt($sValue){
886 $sSecretKey = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
891 $sSecretKey, $sValue,
904 //July 1, 2014: Ensoftek: Function to AES256 decrypt a given string
905 function aes256Decrypt($sValue){
906 $sSecretKey = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
911 base64_decode($sValue),
924 //July 1, 2014: Ensoftek: Utility function to get data from table(log_comment_encrypt)
925 function logCommentEncryptData($log_id){
926 $encryptRow = array();
927 $logRes = sqlStatement("SELECT * FROM log_comment_encrypt WHERE log_id=?", array($log_id));
928 while($logRow = sqlFetchArray($logRes)){
929 $encryptRow['encrypt'] = $logRow['encrypt'];
930 $encryptRow['checksum'] = $logRow['checksum'];
936 * Function used to determine category of the event
939 function eventCategoryFinder($sql,$event,$table){
940 if($event == 'delete'){
941 if(strpos($sql, "lists:") === 0){
942 $fieldValues = explode("'",$sql);
943 if(in_array('medical_problem',$fieldValues) === TRUE) return 'Problem List';
944 else if(in_array('medication',$fieldValues) === TRUE) return 'Medication';
945 else if(in_array('allergy', $fieldValues) === TRUE) return 'Allergy';
948 if($table == 'lists' || $table == 'lists_touch'){
949 $trimSQL = stristr($sql, $table);
950 $fieldValues = explode("'",$trimSQL);
951 if(in_array('medical_problem',$fieldValues) === TRUE) return 'Problem List';
952 else if(in_array('medication',$fieldValues) === TRUE) return 'Medication';
953 else if(in_array('allergy', $fieldValues) === TRUE) return 'Allergy';
955 else if($table == 'immunizations') return "Immunization";
956 else if($table == 'form_vitals') return "Vitals";
957 else if($table == 'history_data') return "Social and Family History";
958 else if($table == 'forms' || $table == 'form_encounter' || strpos($table,'form_') === 0) return "Encounter Form";
959 else if($table == 'insurance_data') return "Patient Insurance";
960 else if($table == 'patient_data' || $table == 'employer_data') return "Patient Demographics";
961 else if($table == 'payments' || $table == "billing" || $table == "claims") return "Billing";
962 else if($table == 'pnotes') return "Clinical Mail";
963 else if($table == 'prescriptions') return "Medication";
964 else if($table == 'transactions'){
965 $trimSQL = stristr($sql, "transactions");
966 $fieldValues = explode("'",$trimSQL);
967 if(in_array("LBTref", $fieldValues)) return "Referral";
970 else if($table == 'amendments' || $table == 'amendments_history') return "Amendments";
971 else if($table == 'openemr_postcalendar_events') return "Scheduling";
972 else if($table == 'procedure_order' || $table == 'procedure_order_code') return "Lab Order";
973 else if($table == 'procedure_report' || $table == 'procedure_result') return "Lab Result";
974 else if($event == 'security-administration') return "Security";